diff --git a/.github/workflows/biome.yml b/.github/workflows/biome.yml
index 88744f16ca7d6..fea9c9c8bd7bc 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 9f30f048cfdbe..5669779946e06 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 67261933798cb..8587ef0179442 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 1d6b1dc9d2b27..688134b42515c 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 fcbcaa7092ee2..2e1def95c36e8 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 d01a4b50c9810..f9d61125b50c1 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 }}
@@ -15,9 +17,11 @@ 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
+ - name: Build unix port
+ run: source tools/ci.sh && ci_unix_build_helper
- name: Build docs
run: make -C docs/ html
diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml
index 6613f106625a2..d16122b720b6c 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 ee91b6360b9b4..359d888286400 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 b6768a46c3cdc..4043b63288a0c 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 1f262b0ba4bee..d4e89bd1aa924 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 0e96e7d816e50..a06b3f96ffae4 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 f178a140587db..b60ff370daf9d 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 4c07f89437757..b86c6a76f82c7 100644
--- a/.github/workflows/ports_esp32.yml
+++ b/.github/workflows/ports_esp32.yml
@@ -25,9 +25,10 @@ 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
+ - 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 5236edf40b959..3293abed5980d 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 7743e036ab377..ae9a80ec5806c 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 76727c9d1f6bd..ce86617af05ce 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 c41b13e5ddffe..81f71ca8a96f4 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 ac09dde86408b..857645776629a 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 b9fa74331dc0b..bf99ed25fedae 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 748f38e143893..22d2a9688015f 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 5bf1826cd17bb..dbea255c79ad4 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 8800f145189b8..43659a5d5dd5d 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 f3f613a789af3..8fd8e1aec23d4 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 880f15ab34469..14399950b569e 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 }}
@@ -20,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 f33277d471d3c..6b492640a1fa0 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 eb85af6a36154..9ce7034398669 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 }}
@@ -29,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 4c4a2a3162ed6..633b0cdf82ef4 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 .
diff --git a/docs/differences/python_36.rst b/docs/differences/python_36.rst
index 3315b0594dafc..18da79f8f8431 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/docs/esp32/quickref.rst b/docs/esp32/quickref.rst
index c394414a76f51..2c667a0f014db 100644
--- a/docs/esp32/quickref.rst
+++ b/docs/esp32/quickref.rst
@@ -566,6 +566,42 @@ 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.
+
+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
----------------
@@ -802,6 +838,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/docs/library/bluetooth.rst b/docs/library/bluetooth.rst
index b09c370abd46d..251ff399ecaa2 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'``.
diff --git a/docs/library/btree.rst b/docs/library/btree.rst
index 9d1dcf1110dc7..5746695511157 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.
diff --git a/docs/library/esp32.rst b/docs/library/esp32.rst
index 4be6dc2671c52..e5f39c7f59aa1 100644
--- a/docs/library/esp32.rst
+++ b/docs/library/esp32.rst
@@ -195,6 +195,151 @@ 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.
+
+See the :ref:`machine.Counter ` and
+:ref:`machine.Encoder ` classes for simpler abstractions of
+common pulse counting applications.
+
.. _esp32.RMT:
RMT
diff --git a/docs/library/machine.Counter.rst b/docs/library/machine.Counter.rst
new file mode 100644
index 0000000000000..f89a6d5b4f17a
--- /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 0000000000000..fc2de32084867
--- /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.I2CTarget.rst b/docs/library/machine.I2CTarget.rst
new file mode 100644
index 0000000000000..0e4af84cb687c
--- /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 76d111f11ef3d..7acaddde815bc 100644
--- a/docs/library/machine.rst
+++ b/docs/library/machine.rst
@@ -264,9 +264,12 @@ Classes
machine.UART.rst
machine.SPI.rst
machine.I2C.rst
+ machine.I2CTarget.rst
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/library/re.rst b/docs/library/re.rst
index 19b15d2d2c299..b8aeefd90cfa4 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/docs/library/rp2.StateMachine.rst b/docs/library/rp2.StateMachine.rst
index 1cb87e90b6e2d..4984be0b2183c 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.
diff --git a/docs/reference/glossary.rst b/docs/reference/glossary.rst
index efaa8f607f72a..ab0b22049bd67 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
diff --git a/docs/reference/mpremote.rst b/docs/reference/mpremote.rst
index bee008c637319..8aecc3a5d4831 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
diff --git a/docs/zephyr/tutorial/repl.rst b/docs/zephyr/tutorial/repl.rst
index 199dda2b7aeee..1b16c5ad21d7b 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/examples/bluetooth/ble_advertising.py b/examples/bluetooth/ble_advertising.py
index 2fe17d640b9d2..f0b97d7f5d93e 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("
-
-// 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 d310fc339f162..0000000000000
--- 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 2122f30f044e3..0000000000000
--- 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/machine_i2c_target.c b/extmod/machine_i2c_target.c
new file mode 100644
index 0000000000000..f63be183c86b6
--- /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/modframebuf.c b/extmod/modframebuf.c
index 5c4b9abf0c0b8..593125aa16f91 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) {
@@ -787,39 +785,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/extmod/modlwip.c b/extmod/modlwip.c
index b84b3b7626bfe..4b1c1b8f3a5ec 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.
@@ -1802,10 +1790,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 +1807,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 +1818,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
diff --git a/extmod/modmachine.c b/extmod/modmachine.c
index f2570123e3751..28b60683b1e58 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 26010be8e18f9..ef507aca7408c 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);
diff --git a/extmod/modre.c b/extmod/modre.c
index d17ec68d50eda..85e5d1b0f74f6 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/extmod/modtime.c b/extmod/modtime.c
index 999b81230bcb9..ee898828a4ab1 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/extmod/modtls_mbedtls.c b/extmod/modtls_mbedtls.c
index 418275440f309..58634257328da 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
diff --git a/extmod/network_ppp_lwip.c b/extmod/network_ppp_lwip.c
index 2c3dac92012a0..12205521f670f 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;
@@ -117,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/lib/micropython-lib b/lib/micropython-lib
index 5b496e944ec04..34c4ee1647ac4 160000
--- a/lib/micropython-lib
+++ b/lib/micropython-lib
@@ -1 +1 @@
-Subproject commit 5b496e944ec045177afa1620920a168410b7f60b
+Subproject commit 34c4ee1647ac4b177ae40adf0ec514660e433dc0
diff --git a/mpy-cross/main.c b/mpy-cross/main.c
index 16f749ae4dc35..b7771ce6e798f 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 94a598c9954b8..0a6478b4f3d24 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)
@@ -84,11 +85,12 @@
#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)
-#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/ports/alif/alif.mk b/ports/alif/alif.mk
index 265418aa07456..d9e7c32578ce8 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
diff --git a/ports/alif/fatfs_port.c b/ports/alif/fatfs_port.c
index 5883c9f3b9447..20b7920ebf0f1 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);
}
diff --git a/ports/alif/irq.h b/ports/alif/irq.h
index 02df524a49c86..86b739795c173 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.c b/ports/alif/machine_i2c.c
index a710aeeb01132..356c893dc7021 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.
diff --git a/ports/alif/machine_i2c_target.c b/ports/alif/machine_i2c_target.c
new file mode 100644
index 0000000000000..cdc106049a08d
--- /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/machine_rtc.c b/ports/alif/machine_rtc.c
index 6473d1d80fbff..739fcab4b6cb0 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/main.c b/ports/alif/main.c
index ab5e85d5b9db6..308d8df900e66 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/mbedtls/mbedtls_port.c b/ports/alif/mbedtls/mbedtls_port.c
index a8c155e31ae65..14e24f021f9e9 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
diff --git a/ports/alif/modtime.c b/ports/alif/modtime.c
new file mode 100644
index 0000000000000..6d40ec2cc37cf
--- /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 ddfc551bf8983..6b30ea2e6245a 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)
@@ -132,6 +134,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/alif/mphalport.c b/ports/alif/mphalport.c
index 39528a4b9f272..3bf8bb0aed7e3 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,
diff --git a/ports/alif/mphalport.h b/ports/alif/mphalport.h
index f03dc7c1c1f84..731ac14fc57e6 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);
diff --git a/ports/cc3200/mods/modtime.c b/ports/cc3200/mods/modtime.c
index 254678fb2ddc5..d111667001115 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/embed/port/mpconfigport_common.h b/ports/embed/port/mpconfigport_common.h
index 8e19859ed2ed9..aa65640fcd58c 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
diff --git a/ports/esp32/README.md b/ports/esp32/README.md
index 4adff66328df2..dd4584772cf84 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 0000000000000..da0931a0e4459
--- /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 0000000000000..b8ed10069feb3
--- /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/board_init.c b/ports/esp32/boards/ESP32_GENERIC_C2/board_init.c
new file mode 100644
index 0000000000000..355fe2bf0b24d
--- /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
new file mode 100644
index 0000000000000..7a8b0e0b3d4af
--- /dev/null
+++ b/ports/esp32/boards/ESP32_GENERIC_C2/mpconfigboard.cmake
@@ -0,0 +1,14 @@
+set(IDF_TARGET esp32c2)
+
+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
+)
+
+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
new file mode 100644
index 0000000000000..999465373e817
--- /dev/null
+++ b/ports/esp32/boards/ESP32_GENERIC_C2/mpconfigboard.h
@@ -0,0 +1,10 @@
+// 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)
+
+#define MICROPY_BOARD_STARTUP GENERIC_C2_board_startup
+void GENERIC_C2_board_startup(void);
diff --git a/ports/esp32/boards/sdkconfig.base b/ports/esp32/boards/sdkconfig.base
index 30740af434ddb..a045a45b4283a 100644
--- a/ports/esp32/boards/sdkconfig.base
+++ b/ports/esp32/boards/sdkconfig.base
@@ -117,7 +117,9 @@ 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_TOUCH_SUPPRESS_DEPRECATE_WARN=y
CONFIG_ETH_USE_SPI_ETHERNET=y
CONFIG_ETH_SPI_ETHERNET_W5500=y
@@ -125,9 +127,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
@@ -139,3 +141,6 @@ 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_ENABLE_SLAVE_DRIVER_VERSION_2=y
diff --git a/ports/esp32/boards/sdkconfig.c2 b/ports/esp32/boards/sdkconfig.c2
new file mode 100644
index 0000000000000..194d815b6f27d
--- /dev/null
+++ b/ports/esp32/boards/sdkconfig.c2
@@ -0,0 +1,17 @@
+#
+# Main XTAL Config
+#
+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
+
+# Decrease mDNS stack size to save RAM
+CONFIG_MDNS_TASK_STACK_SIZE=3072
diff --git a/ports/esp32/boards/sdkconfig.free_ram b/ports/esp32/boards/sdkconfig.free_ram
new file mode 100644
index 0000000000000..36c6455e3e6da
--- /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
diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake
index 09b120391305f..79a60adac9f36 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
@@ -264,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/esp32_pcnt.c b/ports/esp32/esp32_pcnt.c
new file mode 100644
index 0000000000000..36d43ab4559c6
--- /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/esp32_rmt.c b/ports/esp32/esp32_rmt.c
index 6890e16bf79f7..f3bfbecdd11d1 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 02acaa22da035..c5575d45ec7cc 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 6296ff06708c1..ed7fcc407df6c 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 259101ee7e145..89d2cf502a5a8 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
@@ -28,126 +29,95 @@
#include "py/mphal.h"
#include "py/mperrno.h"
#include "extmod/modmachine.h"
+#include "machine_i2c.h"
-#include "driver/i2c.h"
+#include "driver/i2c_master.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
-#define I2C_SCLK_FREQ XTAL_CLK_FREQ
-#elif SOC_I2C_SUPPORT_APB
-#define I2C_SCLK_FREQ APB_CLK_FREQ
-#else
-#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;
+ i2c_master_bus_handle_t bus_handle;
+ 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, uint32_t freq, uint32_t timeout_us, bool first_init) {
- if (!first_init) {
- i2c_driver_delete(self->port);
+static void machine_hw_i2c_init(machine_hw_i2c_obj_t *self, bool first_init) {
+
+ if (!first_init && self->bus_handle) {
+ i2c_del_master_bus(self->bus_handle);
+ self->bus_handle = NULL;
}
- i2c_config_t conf = {
- .mode = I2C_MODE_MASTER,
- .sda_io_num = self->sda,
- .sda_pullup_en = GPIO_PULLUP_ENABLE,
+
+ i2c_master_bus_config_t bus_cfg = {
+ .i2c_port = self->port,
.scl_io_num = self->scl,
- .scl_pullup_en = GPIO_PULLUP_ENABLE,
- .master.clk_speed = freq,
+ .sda_io_num = self->sda,
+ .clk_source = I2C_CLK_SRC_DEFAULT,
+ .glitch_ignore_cnt = 7,
+ .flags.enable_internal_pullup = true,
};
- i2c_param_config(self->port, &conf);
- int timeout = I2C_SCLK_FREQ / 1000000 * 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);
+ ESP_ERROR_CHECK(i2c_new_master_bus(&bus_cfg, &self->bus_handle));
}
-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) {
+static int machine_i2c_transfer_single(mp_obj_base_t *self_in, uint16_t addr, size_t len, uint8_t *buf, unsigned int flags) {
machine_hw_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in);
- i2c_cmd_handle_t cmd = i2c_cmd_link_create();
- int data_len = 0;
-
- if (flags & MP_MACHINE_I2C_FLAG_WRITE1) {
- i2c_master_start(cmd);
- i2c_master_write_byte(cmd, addr << 1, true);
- i2c_master_write(cmd, bufs->buf, bufs->len, true);
- data_len += bufs->len;
- --n;
- ++bufs;
+ // 1. 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
}
- i2c_master_start(cmd);
- i2c_master_write_byte(cmd, addr << 1 | (flags & MP_MACHINE_I2C_FLAG_READ), true);
-
- for (; n--; ++bufs) {
+ if (len > 0) {
+ // 2. Create a temporary device handle for this transaction
+ // This step for every transaction can be omitted in
+ // esp idf v5.5+, which supports handle address changing.
+ 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);
+ if (err != ESP_OK) {
+ return -MP_ENODEV;
+ }
+ // 3. Transfer data
if (flags & MP_MACHINE_I2C_FLAG_READ) {
- i2c_master_read(cmd, bufs->buf, bufs->len, n == 0 ? I2C_MASTER_LAST_NACK : I2C_MASTER_ACK);
+ err = i2c_master_receive(dev_handle, buf, len, self->timeout_us / 1000);
} else {
- if (bufs->len != 0) {
- i2c_master_write(cmd, bufs->buf, bufs->len, true);
- }
+ err = i2c_master_transmit(dev_handle, buf, len, self->timeout_us / 1000);
+ }
+ // 4. Destroy the temporary handle
+ i2c_master_bus_rm_device(dev_handle);
+ // 5. Map errors
+ if (err == ESP_FAIL) {
+ return -MP_ENODEV;
+ }
+ if (err == ESP_ERR_TIMEOUT) {
+ return -MP_ETIMEDOUT;
+ }
+ if (err != ESP_OK) {
+ return -abs(err);
}
- data_len += bufs->len;
- }
-
- if (flags & MP_MACHINE_I2C_FLAG_STOP) {
- i2c_master_stop(cmd);
- }
-
- // TODO proper timeout
- esp_err_t err = i2c_master_cmd_begin(self->port, cmd, 100 * (1 + data_len) / portTICK_PERIOD_MS);
- i2c_cmd_link_delete(cmd);
-
- if (err == ESP_FAIL) {
- return -MP_ENODEV;
- } else if (err == ESP_ERR_TIMEOUT) {
- return -MP_ETIMEDOUT;
- } else if (err != ESP_OK) {
- return -abs(err);
}
-
- return data_len;
+ return len;
}
-/******************************************************************************/
-// 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) {
@@ -162,55 +132,49 @@ 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);
}
static const mp_machine_i2c_p_t machine_hw_i2c_p = {
- .transfer_supports_write1 = true,
- .transfer = machine_hw_i2c_transfer,
+ .transfer = mp_machine_i2c_transfer_adaptor,
+ .transfer_single = machine_i2c_transfer_single,
};
MP_DEFINE_CONST_OBJ_TYPE(
@@ -223,4 +187,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/machine_i2c.h b/ports/esp32/machine_i2c.h
new file mode 100644
index 0000000000000..299baa6dd6843
--- /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
diff --git a/ports/esp32/machine_i2c_target.c b/ports/esp32/machine_i2c_target.c
new file mode 100644
index 0000000000000..84795e6f138f8
--- /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/machine_pin.c b/ports/esp32/machine_pin.c
index 4ab79f0a26460..9999223b59d50 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 47f1ddebebf44..6fe9ef0e7787b 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/machine_timer.c b/ports/esp32/machine_timer.c
index a104288f6ee3c..b0292a0379fea 100644
--- a/ports/esp32/machine_timer.c
+++ b/ports/esp32/machine_timer.c
@@ -38,13 +38,13 @@
#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_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;
@@ -52,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);
@@ -66,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
@@ -163,8 +171,23 @@ 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);
- timer_ll_set_clock_source(self->hal_context.dev, self->index, GPTIMER_CLK_SRC_DEFAULT);
+ 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);
@@ -224,7 +247,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) {
@@ -232,7 +255,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;
@@ -268,7 +291,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 10fe2f39c90ae..5dd0ce95ac802 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 661c07138e535..c4dab2cae5c81 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;
}
diff --git a/ports/esp32/main.c b/ports/esp32/main.c
index f048aa85f5f7f..1523e07f916d6 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"
@@ -60,6 +61,7 @@
#include "uart.h"
#include "usb.h"
#include "usb_serial_jtag.h"
+#include "modesp32.h"
#include "modmachine.h"
#include "modnetwork.h"
@@ -180,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
@@ -199,7 +205,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/modesp32.c b/ports/esp32/modesp32.c
index fcd6ed9fa83b8..858be2ed05f81 100644
--- a/ports/esp32/modesp32.c
+++ b/ports/esp32/modesp32.c
@@ -299,7 +299,12 @@ 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
#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/modesp32.h b/ports/esp32/modesp32.h
index a685b7b38fe6f..81ab94dc633ae 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/modespnow.c b/ports/esp32/modespnow.c
index 7873ff8977876..ab50032ffeb51 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/modmachine.c b/ports/esp32/modmachine.c
index 0d7ea44c66960..06360e8e8999c 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/modtime.c b/ports/esp32/modtime.c
index 991f2cf578771..64f9359db69af 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/esp32/modules/machine.py b/ports/esp32/modules/machine.py
new file mode 100644
index 0000000000000..9cfda12f17753
--- /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)
diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h
index 721f22de11c55..9b12dbd34a736 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)
@@ -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
@@ -104,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)
@@ -138,6 +135,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)
@@ -165,6 +169,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
@@ -188,6 +194,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)
diff --git a/ports/esp32/network_ppp.c b/ports/esp32/network_ppp.c
index 8b700c98ef386..18e0c8816889a 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;
@@ -123,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;
@@ -163,7 +168,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;
}
diff --git a/ports/esp32/network_wlan.c b/ports/esp32/network_wlan.c
index e85d1328fdc2b..e20af4806c43d 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");
diff --git a/ports/esp8266/boards/ESP8266_GENERIC/mpconfigboard.h b/ports/esp8266/boards/ESP8266_GENERIC/mpconfigboard.h
index 1f679961e8786..cea2267c7ccd5 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/modtime.c b/ports/esp8266/modtime.c
index e99d920fde80b..c0c1dccfe474d 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/esp8266/mpconfigport.h b/ports/esp8266/mpconfigport.h
index bc2957190262b..0321de45d7f22 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)
@@ -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)
@@ -106,6 +107,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/mimxrt/boards/common.ld b/ports/mimxrt/boards/common.ld
index 477ba38bc89a3..dcbc0a42366a3 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 d170804f4f058..aa128e6ff6fe2 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 0000000000000..c6d561ca585f1
--- /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 0000000000000..aa408071f31bd
--- /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 6b9d4fa17afe4..7166171f17c2d 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/modtime.c b/ports/mimxrt/modtime.c
index 3bcfac411c0b3..fe77b8a733be2 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/mimxrt/mpconfigport.h b/ports/mimxrt/mpconfigport.h
index e1c605f452a32..d6694badbad95 100644
--- a/ports/mimxrt/mpconfigport.h
+++ b/ports/mimxrt/mpconfigport.h
@@ -92,6 +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
@@ -138,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/nrf/drivers/bluetooth/download_ble_stack.sh b/ports/nrf/drivers/bluetooth/download_ble_stack.sh
index 7886682b718e0..5004d7a28eff0 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 -
}
diff --git a/ports/nrf/mpconfigport.h b/ports/nrf/mpconfigport.h
index 963e1e8836db7..d944fc8a11e71 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 7e6e1c4e02b17..e95f25aa0b689 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 25d85c9e61a72..091e94bdafdf2 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/renesas-ra/modtime.c b/ports/renesas-ra/modtime.c
index e1358f82bc5a4..b778ab2fe5e2a 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/renesas-ra/mpconfigport.h b/ports/renesas-ra/mpconfigport.h
index 868cdbc7d6ada..70d38e7d20e0e 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)
diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt
index 49ba8d77d6352..120d07bcce1be 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}
)
diff --git a/ports/rp2/boards/ARDUINO_NANO_RP2040_CONNECT/mpconfigboard.h b/ports/rp2/boards/ARDUINO_NANO_RP2040_CONNECT/mpconfigboard.h
index 7783c0a17c64d..11aa663296f82 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/machine_i2c.c b/ports/rp2/machine_i2c.c
index 94212fb487043..99a94ec2f1a00 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 0000000000000..da8cb5f8567d0
--- /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
diff --git a/ports/rp2/machine_i2c_target.c b/ports/rp2/machine_i2c_target.c
new file mode 100644
index 0000000000000..dc3010727b522
--- /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 0f10f63c6d296..1ffcabdfa0d14 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/modtime.c b/ports/rp2/modtime.c
index 7d6dd8fd97112..a860903285e55 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/rp2/modules/rp2.py b/ports/rp2/modules/rp2.py
index 6068926036baf..442a802b3e9d7 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/mpconfigport.h b/ports/rp2/mpconfigport.h
index 35afea4fac5f4..0c226538cda1a 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"
@@ -171,6 +169,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/rp2/rp2_flash.c b/ports/rp2/rp2_flash.c
index d6b9e13653315..4d41d45733c04 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) {
diff --git a/ports/rp2/rp2_pio.c b/ports/rp2/rp2_pio.c
index d936553b55573..611e74a1587d2 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,
@@ -683,8 +684,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 +719,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);
}
diff --git a/ports/samd/boards/ADAFRUIT_NEOKEY_TRINKEY/mpconfigboard.h b/ports/samd/boards/ADAFRUIT_NEOKEY_TRINKEY/mpconfigboard.h
index eb4704ff8cb97..bf44bd661c0a3 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 172518523d208..50548b62a7d68 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 0000000000000..054ca81dd1a34
--- /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 a7da95582f76c..475f57703d076 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 f0a7a73e0c027..a29d5c0a04db4 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 8cce90b886c07..a1ff208eb5939 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/modtime.c b/ports/samd/modtime.c
index 0bed3cb83a868..6168c645d6313 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/samd/mpconfigport.h b/ports/samd/mpconfigport.h
index 514f383948838..7b423bf0babff 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 5d05b6d18d5c1..35cac27aa5700 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 83839a0503255..bc65e8ae23bf9 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 e78032513c216..fb6eb2083a304 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]);
diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile
index 37d70dcdbf028..abca3a05f8c7a 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 \
@@ -610,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))
@@ -628,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))
diff --git a/ports/stm32/boards/NUCLEO_G0B1RE/mpconfigboard.h b/ports/stm32/boards/NUCLEO_G0B1RE/mpconfigboard.h
index 092ee177925eb..ead36ed6c763f 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 54d5acc6cea9d..8022fd274ce97 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
@@ -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
@@ -485,12 +485,18 @@ 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;
} 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 c252770740d01..ad199b1fb91d9 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)
@@ -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;
@@ -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.
diff --git a/ports/stm32/dma.h b/ports/stm32/dma.h
index f05b22b5d05cd..75e007bc9fa98 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)
diff --git a/ports/stm32/eth_phy.h b/ports/stm32/eth_phy.h
index 5036905c1f582..dccfb7951add8 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)
diff --git a/ports/stm32/i2c.c b/ports/stm32/i2c.c
index a1fde7e6ba114..4effb23438cf6 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,4 +595,144 @@ int i2c_find_peripheral(mp_obj_t id) {
return i2c_id;
}
+#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);
+ #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);
+ #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)
+
+#if defined(MICROPY_HW_I2C2_SCL)
+void I2C2_EV_IRQHandler(void) {
+ MP_STATIC_ASSERT(I2C2_EV_IRQn > 0);
+ IRQ_ENTER(I2C2_EV_IRQn);
+ #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);
+ #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)
+
+#if defined(MICROPY_HW_I2C3_SCL)
+void I2C3_EV_IRQHandler(void) {
+ MP_STATIC_ASSERT(I2C3_EV_IRQn > 0);
+ IRQ_ENTER(I2C3_EV_IRQn);
+ #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);
+ #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)
+
+#if defined(MICROPY_HW_I2C4_SCL)
+void I2C4_EV_IRQHandler(void) {
+ MP_STATIC_ASSERT(I2C4_EV_IRQn > 0);
+ IRQ_ENTER(I2C4_EV_IRQn);
+ #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);
+ #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)
+
+#endif // MICROPY_PY_PYB_LEGACY
+
#endif // MICROPY_HW_ENABLE_HW_I2C
diff --git a/ports/stm32/i2c.h b/ports/stm32/i2c.h
index a48076842cbbf..26c55ec00e6eb 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/i2cslave.c b/ports/stm32/i2cslave.c
index a575c53085168..0e4fbf48913de 100644
--- a/ports/stm32/i2cslave.c
+++ b/ports/stm32/i2cslave.c
@@ -28,15 +28,29 @@
#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;
}
-void i2c_slave_ev_irq_handler(i2c_slave_t *i2c) {
+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
@@ -45,10 +59,12 @@ void i2c_slave_ev_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)
@@ -70,7 +86,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
@@ -78,12 +94,17 @@ void i2c_slave_ev_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) {
- 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 cc4e7f9be92e3..edead6cb2c50a 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);
}
-void i2c_slave_ev_irq_handler(i2c_slave_t *i2c);
+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/irq.h b/ports/stm32/irq.h
index dfe901ff74b24..3348175420c41 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 0000000000000..83031677ebb27
--- /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 137e132817483..af4d7f8bbb6d3 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 2be8793351e86..e40413e4e7a27 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)
@@ -805,9 +806,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 +910,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) {
@@ -1755,7 +1758,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
diff --git a/ports/stm32/modtime.c b/ports/stm32/modtime.c
index 87a4536b04374..e7051065187bc 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/stm32/mpconfigboard_common.h b/ports/stm32/mpconfigboard_common.h
index 9fa9bf7714877..a8e50be5c6a8a 100644
--- a/ports/stm32/mpconfigboard_common.h
+++ b/ports/stm32/mpconfigboard_common.h
@@ -639,8 +639,16 @@
#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)
#endif
// Enable CAN if there are any peripherals defined
diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h
index 35deb93c6a0bf..fac261f7e2480 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)
@@ -129,6 +126,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)
diff --git a/ports/stm32/stm32_it.c b/ports/stm32/stm32_it.c
index 3639e2f0499dd..9eda3cb23974f 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
diff --git a/ports/unix/coverage.c b/ports/unix/coverage.c
index f071049eded50..b7c3d2c25ef6e 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/ports/unix/main.c b/ports/unix/main.c
index 530e20a3863b4..51d99ce5f1510 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;
@@ -138,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));
}
@@ -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.
@@ -616,19 +613,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/ports/unix/mpthreadport.c b/ports/unix/mpthreadport.c
index 141cd0218d93e..a41b3ec9f4701 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 0000000000000..f6159bb69d529
--- /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
diff --git a/ports/unix/variants/mpconfigvariant_common.h b/ports/unix/variants/mpconfigvariant_common.h
index 65c874317666a..1ac59c95572dd 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/ports/webassembly/main.c b/ports/webassembly/main.c
index 770dfbe0ca5c7..e0e2d59ae6bd0 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/modtime.c b/ports/webassembly/modtime.c
index 1b1e63d4ddf3a..b6c0cda96da12 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.
diff --git a/ports/webassembly/mpconfigport.h b/ports/webassembly/mpconfigport.h
index eea6f02a02633..0783aacbbcd24 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)
@@ -54,6 +53,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 28fef901477c3..a8b21a744565c 100644
--- a/ports/webassembly/objjsproxy.c
+++ b/ports/webassembly/objjsproxy.c
@@ -266,8 +266,26 @@ 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_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;
@@ -566,6 +584,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
diff --git a/ports/webassembly/proxy_js.js b/ports/webassembly/proxy_js.js
index 9e7c233e30bfc..cbd6e5b0088cc 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/ports/zephyr/README.md b/ports/zephyr/README.md
index 17c1f613de4ed..8384fabcf0261 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/ports/zephyr/boards/nucleo_wb55rg.conf b/ports/zephyr/boards/nucleo_wb55rg.conf
index adfab367c892a..1c9c2f794cb05 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 6b31bc9f98bcb..683279ddc2c1c 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 0000000000000..236f1334883a1
--- /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 eaef34a7868d3..f7ac997d92afd 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 62226a2ded738..fbf8dbcc7a709 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,24 +53,21 @@
#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"
#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)
@@ -80,7 +82,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 +97,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 +117,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)
diff --git a/ports/zephyr/mpconfigport_minimal.h b/ports/zephyr/mpconfigport_minimal.h
index a0a7f97394610..24e0c9f1adc05 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
diff --git a/py/asmrv32.c b/py/asmrv32.c
index 158b5521917a6..723d32cdd7c89 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
diff --git a/py/asmthumb.c b/py/asmthumb.c
index 18c3db9e4e28f..58cc7aea88085 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);
@@ -492,8 +491,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));
}
}
diff --git a/py/binary.c b/py/binary.c
index 48d3421bca963..ef2857b4318dc 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/builtin.h b/py/builtin.h
index 6efe3e8facabd..388bc8470053d 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/builtinimport.c b/py/builtinimport.c
index 0611926fdd52a..a2737a03e8dc2 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/emitcommon.c b/py/emitcommon.c
index a9eb6e2021fc8..1f701db80a0a9 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/formatfloat.c b/py/formatfloat.c
index 7cd471018da98..1ea34f84bf7fb 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 9a1643b4ddff5..7b1414672b77b 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 e034485838954..081163cadf9fd 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 *******************************************************/
@@ -41,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 *************************************************/
@@ -277,6 +284,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 *************/
@@ -517,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/py/mkrules.cmake b/py/mkrules.cmake
index 27d8b24f67ac8..e3d769cc59b5c 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}
)
diff --git a/py/mpconfig.h b/py/mpconfig.h
index 619bce2ab290a..877b262c8b7ae 100644
--- a/py/mpconfig.h
+++ b/py/mpconfig.h
@@ -30,7 +30,7 @@
// 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 1
@@ -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
@@ -861,6 +868,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__
@@ -962,6 +990,16 @@ 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)
+#endif
+
// Whether modules can use MP_REGISTER_MODULE_DELEGATION() to delegate failed
// attribute lookups to a custom handler function.
#ifndef MICROPY_MODULE_ATTR_DELEGATION
@@ -1129,7 +1167,15 @@ 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 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
#ifndef MICROPY_PY_DESCRIPTORS
@@ -1386,16 +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 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)
-#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)
@@ -1565,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)
@@ -1817,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
@@ -1829,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
diff --git a/py/mphal.h b/py/mphal.h
index a4f222d0b1e11..d52e10be44c3c 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
diff --git a/py/mpprint.c b/py/mpprint.c
index f1d8bd0c57366..bd7a250878b89 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 583f00bda8099..250ea24b87840 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/obj.h b/py/obj.h
index 4ac0cc0c611e5..69d6e626db3c1 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/objboundmeth.c b/py/objboundmeth.c
index e3503ff154a65..6df67f7bf9e0c 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
diff --git a/py/objcomplex.c b/py/objcomplex.c
index 85b5852845737..805899edf4396 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 81b0daa620916..125b576fb61c3 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/objint_longlong.c b/py/objint_longlong.c
index 339ce7cfd8e96..1a6242b97921e 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:
@@ -308,8 +325,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/py/objint_mpz.c b/py/objint_mpz.c
index 6f2ea616c779c..ea4e409a257e8 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/py/objmodule.c b/py/objmodule.c
index 5ce373b83d3f1..5ee2f7dc86023 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);
diff --git a/py/objringio.c b/py/objringio.c
index ba1ec25307ea4..0025b26be3b07 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/py/objtype.c b/py/objtype.c
index f2173c79a173e..d40f619fae241 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);
@@ -661,8 +662,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 +961,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 +975,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);
@@ -1122,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);
@@ -1210,20 +1253,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);
@@ -1231,8 +1292,7 @@ mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict)
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)) {
@@ -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);
}
diff --git a/py/parse.c b/py/parse.c
index db89fb58450e3..1a50b13b5c790 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);
+ nlr_pop();
+ #if MICROPY_PY_BUILTINS_COMPLEX
+ if (mp_obj_is_type(tmp, &mp_type_complex)) {
+ return false;
+ }
+ #endif
+ *res = tmp;
+ 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,45 @@ 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 (op == MP_BINARY_OP_POWER && mp_obj_int_sign(arg1) < 0) {
- // ** can't have negative rhs
+ if (!binary_op_maybe(op, arg0, arg1, &arg0)) {
return false;
}
- arg0 = mp_binary_op(op, arg0, arg1);
}
} 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 /
- return false;
- }
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 +878,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 +889,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/py/parsenum.c b/py/parsenum.c
index fcc69091737d0..e18002306a259 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,27 +209,77 @@ 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_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
-#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_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
+ // 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_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
@@ -244,6 +296,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_large_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 +426,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 f444632d23021..d532cb194a5d8 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_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
+
#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);
diff --git a/tests/basics/builtin_pow3_intbig.py b/tests/basics/builtin_pow3_intbig.py
index bedc8b36b7edd..41d2acbc0cc75 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")
diff --git a/tests/basics/class_descriptor.py b/tests/basics/class_descriptor.py
index 83d31674301d5..feaed2fbb2a43 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 0000000000000..77c0409346282
--- /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 0000000000000..4c9934c3bf068
--- /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))
diff --git a/tests/basics/int_64_basics.py b/tests/basics/int_64_basics.py
index 289ea49b65ece..2a161dac0ba14 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)
diff --git a/tests/basics/io_buffered_writer.py b/tests/basics/io_buffered_writer.py
index 60cf2c837d10f..3cfee0103f777 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 2edb7136a9691..543c12ad42ab2 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 4d4c60c1363b7..92e715178116c 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 414ac90a3b083..f60a6a9a6041e 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 d3824c177f3b3..c01ca6a5073d6 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 7d355930f5a29..889e3ce697377 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 0f65fb3fabc3b..c8890dab73ab7 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 a3aa6ec84e066..0155ad5382dcd 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 695abccef4421..5af1de7a6c3aa 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/basics/sys_tracebacklimit.py.native.exp b/tests/basics/sys_tracebacklimit.py.native.exp
new file mode 100644
index 0000000000000..f9d30c058564b
--- /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/cpydiff/types_float_rounding.py b/tests/cpydiff/types_float_rounding.py
deleted file mode 100644
index 206e359ed9be7..0000000000000
--- 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)
diff --git a/tests/extmod/framebuf_scroll.py b/tests/extmod/framebuf_scroll.py
index db9b6ea1e9649..d7c07b1c65772 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()
diff --git a/tests/extmod/machine_spi_rate.py b/tests/extmod/machine_spi_rate.py
index c65095f22a1a5..fe15b66fe648a 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/extmod/re_start_end_pos.py b/tests/extmod/re_start_end_pos.py
new file mode 100644
index 0000000000000..bd16584374b89
--- /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)
diff --git a/tests/extmod_hardware/machine_counter.py b/tests/extmod_hardware/machine_counter.py
new file mode 100644
index 0000000000000..62ac1fed47ce7
--- /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 0000000000000..9bd2bb464178c
--- /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()
diff --git a/tests/extmod_hardware/machine_i2c_target.py b/tests/extmod_hardware/machine_i2c_target.py
new file mode 100644
index 0000000000000..763e6f4771e0f
--- /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()
diff --git a/tests/feature_check/float.py b/tests/feature_check/float.py
deleted file mode 100644
index d6d2a99d2429d..0000000000000
--- 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 900731ffd51ff..0000000000000
--- a/tests/feature_check/float.py.exp
+++ /dev/null
@@ -1 +0,0 @@
-64
diff --git a/tests/feature_check/io_module.py b/tests/feature_check/io_module.py
deleted file mode 100644
index 9094e605316ba..0000000000000
--- 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 e69de29bb2d1d..0000000000000
diff --git a/tests/feature_check/target_info.py b/tests/feature_check/target_info.py
index 9501d808ef214..962ebf4154b5c 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, len("α") == 1)
diff --git a/tests/float/float_format.py b/tests/float/float_format.py
index 98ed0eb096fa4..0eb8b232b063a 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 0000000000000..f9467f9c05d89
--- /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 df4444166c5fa..7b7b30c4b340b 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_parse_doubleprec.py b/tests/float/float_parse_doubleprec.py
index 81fcadcee88b4..c1b0b4823b038 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/float/float_struct_e.py b/tests/float/float_struct_e.py
index 403fbc5db4cde..ba4134f3393ed 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("> -2)")
test_syntax("A = const(1 % 0)")
diff --git a/tests/micropython/const_error.py.exp b/tests/micropython/const_error.py.exp
index 3edc3efe9c3e9..bef69eb32ea7d 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 0000000000000..c3a0df0276bf8
--- /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 0000000000000..17a86a6d936c2
--- /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 0000000000000..7ee5edc6d3240
--- /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 0000000000000..d3827e75a5cad
--- /dev/null
+++ b/tests/micropython/const_math.py.exp
@@ -0,0 +1 @@
+1.0
diff --git a/tests/micropython/emg_exc.py.native.exp b/tests/micropython/emg_exc.py.native.exp
new file mode 100644
index 0000000000000..9677c526a9cc8
--- /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 0000000000000..d6ac26aa829e1
--- /dev/null
+++ b/tests/micropython/heapalloc_traceback.py.native.exp
@@ -0,0 +1,3 @@
+StopIteration
+StopIteration:
+
diff --git a/tests/micropython/opt_level_lineno.py b/tests/micropython/opt_level_lineno.py
index d8253e54b41f0..dda9092d868cf 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 469b90ba7938a..b50f0628c81fb 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/micropython/ringio.py b/tests/micropython/ringio.py
index a0102ef63dafa..4109288798448 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 65bae06472f8d..c391529a41e54 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 0000000000000..d55c4c00b7c0d
--- /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 0000000000000..72af34b383872
--- /dev/null
+++ b/tests/micropython/ringio_big.py.exp
@@ -0,0 +1,2 @@
+
+
diff --git a/tests/micropython/viper_large_jump.py b/tests/micropython/viper_large_jump.py
new file mode 100644
index 0000000000000..1c5913dec1ea2
--- /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 0000000000000..e9f960cf4ac4e
--- /dev/null
+++ b/tests/micropython/viper_large_jump.py.exp
@@ -0,0 +1 @@
+600
diff --git a/tests/micropython/viper_ptr16_store_boundary_intbig.py b/tests/micropython/viper_ptr16_store_boundary.py
similarity index 51%
rename from tests/micropython/viper_ptr16_store_boundary_intbig.py
rename to tests/micropython/viper_ptr16_store_boundary.py
index 1694c61ac0a61..3501a05685ec3 100644
--- a/tests/micropython/viper_ptr16_store_boundary_intbig.py
+++ b/tests/micropython/viper_ptr16_store_boundary.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})))
"""
@@ -13,9 +15,28 @@ 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
dest[i] = val
+ assert int(saved) == int(dest)
def get_index(src, i):
@@ -23,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 = (
@@ -32,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 54%
rename from tests/micropython/viper_ptr32_store_boundary_intbig.py
rename to tests/micropython/viper_ptr32_store_boundary.py
index 5109abb9dcaa8..8c207278ec538 100644
--- a/tests/micropython/viper_ptr32_store_boundary_intbig.py
+++ b/tests/micropython/viper_ptr32_store_boundary.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})))
"""
@@ -12,10 +14,28 @@ 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):
+ saved = dest
dest[i] = val
+ assert int(saved) == int(dest)
def get_index(src, i):
@@ -28,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 = (
@@ -37,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.py b/tests/micropython/viper_ptr8_store_boundary.py
new file mode 100644
index 0000000000000..d3cba17e16318
--- /dev/null
+++ b/tests/micropython/viper_ptr8_store_boundary.py
@@ -0,0 +1,55 @@
+# Test boundary conditions for various architectures
+
+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})))
+"""
+
+BIT_THRESHOLDS = (5, 8, 11, 12)
+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):
+ saved = dest
+ dest[i] = val
+ assert int(dest) == int(saved)
+
+
+def get_index(src: ptr8, i: int):
+ return src[i]
+
+
+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()))
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
diff --git a/tests/micropython/viper_ptr8_store_boundary_intbig.py b/tests/micropython/viper_ptr8_store_boundary_intbig.py
deleted file mode 100644
index e1fe6dcae3279..0000000000000
--- a/tests/micropython/viper_ptr8_store_boundary_intbig.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# Test boundary conditions for various architectures
-
-SET_TEMPLATE = """
-@micropython.viper
-def set{off}(dest: ptr8):
- dest[{off}] = {val}
-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: uint):
- dest[i] = val
-
-
-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/misc/print_exception.py b/tests/misc/print_exception.py
index 92754733b58b1..d41478360faf3 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)
diff --git a/tests/misc/print_exception.py.native.exp b/tests/misc/print_exception.py.native.exp
new file mode 100644
index 0000000000000..59e856ae3c44a
--- /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/misc/rge_sm.py b/tests/misc/rge_sm.py
index f5b0910dd3a3f..56dad5749776e 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
diff --git a/tests/multi_bluetooth/stress_deepsleep_reconnect.py b/tests/multi_bluetooth/stress_deepsleep_reconnect.py
index 7c34c0360670f..b588b4000b4ed 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)
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 0000000000000..eafd9dfdca838
--- /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 0000000000000..a17c8f43858b3
--- /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 0000000000000..6b3f0d03eb7fe
--- /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 0000000000000..71386cfe769d9
--- /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
diff --git a/tests/multi_net/asyncio_tls_server_client.py b/tests/multi_net/asyncio_tls_server_client.py
index 98f15c6625f0e..016f57970c092 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 178ad39274032..eac9a98beae6c 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 da5f1afee2ad6..6093628ce5b7b 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 362f0fc8ecf72..6dbff0482c8ae 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 2c5734e052661..8ecbd4d34f6e8 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 d148c8ebcfea8..b99493b0ae5d8 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 d85363f00bd63..6bd911ddfd7cc 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 e9d96be248c70..e2d2a4251163b 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 64a4215c75b44..48ff6376fad9b 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 5dc461e7708a8..07dcc690b7ccc 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 fbefdecf9f430..7b986322d15bd 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 70145d91a794b..a0df88be4af1b 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 0c83abb733378..77f68da496a77 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):
diff --git a/tests/ports/esp32/esp32_idf_heap_info.py b/tests/ports/esp32/esp32_idf_heap_info.py
index fdd89161f4310..2f45295938df7 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 2b63bf3259fc6..7fc50fc1624df 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]
[]
diff --git a/tests/ports/unix/extra_coverage.py.exp b/tests/ports/unix/extra_coverage.py.exp
index e20871273d709..85e0e18cbfd8a 100644
--- a/tests/ports/unix/extra_coverage.py.exp
+++ b/tests/ports/unix/extra_coverage.py.exp
@@ -127,10 +127,6 @@ TypeError: can't convert NoneType to int
TypeError: can't convert NoneType to int
ValueError:
Warning: test
-# format float
-?
-+1e+00
-+1e+00
# binary
123
456
diff --git a/tests/ports/unix/ffi_float2.py b/tests/ports/unix/ffi_float2.py
index bbed6966627e1..eac6cd106cf43 100644
--- a/tests/ports/unix/ffi_float2.py
+++ b/tests/ports/unix/ffi_float2.py
@@ -29,4 +29,5 @@ def ffi_open(names):
for fun in (tgammaf,):
for val in (0.5, 1, 1.0, 1.5, 4, 4.0):
- print("%.6f" % fun(val))
+ # limit to 5 decimals in order to pass with REPR_C with FORMAT_IMPL_BASIC
+ print("%.5f" % fun(val))
diff --git a/tests/ports/unix/ffi_float2.py.exp b/tests/ports/unix/ffi_float2.py.exp
index 58fc6a01acb07..4c750e223a3a6 100644
--- a/tests/ports/unix/ffi_float2.py.exp
+++ b/tests/ports/unix/ffi_float2.py.exp
@@ -1,6 +1,6 @@
-1.772454
-1.000000
-1.000000
-0.886227
-6.000000
-6.000000
+1.77245
+1.00000
+1.00000
+0.88623
+6.00000
+6.00000
diff --git a/tests/ports/webassembly/py_proxy_identity.mjs b/tests/ports/webassembly/py_proxy_identity.mjs
index d4a720b738a6c..97dab2e78367a 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 01ccf0d892653..344a0a202364c 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 ad6c49e336e5c..4dff64a605384 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
diff --git a/tests/run-internalbench.py b/tests/run-internalbench.py
index c9f783e474c9c..99c6304afe9d6 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-multitests.py b/tests/run-multitests.py
index 92bd64193d8ce..4412d0fde7fda 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):
diff --git a/tests/run-natmodtests.py b/tests/run-natmodtests.py
index f9d2074f6f02b..55adb6049a3e8 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", ""))
diff --git a/tests/run-tests.py b/tests/run-tests.py
index a428665db03e0..aeea0bad5e779 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")
+# 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.
+ # 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 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_cov.py",
+ "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 = {
@@ -208,6 +250,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)
@@ -248,12 +294,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 = 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
@@ -261,6 +309,8 @@ 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
+ args.unicode = unicode
print("platform={}".format(platform), end="")
if arch:
@@ -269,6 +319,10 @@ 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="")
+ if unicode:
+ print(" unicode", end="")
print()
@@ -350,7 +404,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",
@@ -367,10 +421,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
@@ -502,7 +553,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 = []
@@ -637,15 +688,12 @@ 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
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
@@ -696,11 +744,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":
@@ -733,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(
@@ -773,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")
@@ -781,19 +819,26 @@ 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
+ skip_tests.add(
+ "float/float_struct_e.py"
+ ) # requires fp32, there's float_struct_e_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
- 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")
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")
@@ -819,6 +864,12 @@ 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, ()))
# Skip platform-specific tests.
skip_tests.update(platform_tests_to_skip.get(args.platform, ()))
@@ -833,46 +884,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("\\", "/")
@@ -901,7 +912,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")
@@ -916,7 +926,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
@@ -978,7 +987,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:
@@ -1145,29 +1158,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 +1178,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 (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.
+""",
+ 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:
@@ -1325,45 +1348,26 @@ def main():
"micropython",
"misc",
"extmod",
+ "stress",
)
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")
- elif args.platform == "mimxrt":
- test_dirs += ("float", "stress")
- elif args.platform == "renesas-ra":
- test_dirs += ("float", "ports/renesas-ra")
- elif args.platform == "rp2":
- test_dirs += ("float", "stress", "ports/rp2")
- elif args.platform == "esp32":
- test_dirs += ("float", "stress")
- elif args.platform in ("esp8266", "minimal", "samd", "nrf"):
+ if args.float_prec > 0:
test_dirs += ("float",)
- elif args.platform == "WiPy":
- # run WiPy tests
- test_dirs += ("ports/cc3200",)
- elif args.platform in PC_PLATFORMS:
+ 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,)
+ if args.platform in PC_PLATFORMS:
# run PC tests
test_dirs += (
- "float",
"import",
"io",
- "stress",
- "unicode",
"cmdline",
- "ports/unix",
- )
- elif args.platform == "qemu":
- test_dirs += (
- "float",
- "ports/qemu",
)
- elif args.platform == "webassembly":
- test_dirs += ("float", "ports/webassembly")
else:
# run tests from these directories
test_dirs = args.test_dirs
diff --git a/tests/stress/dict_copy.py b/tests/stress/dict_copy.py
index 73d3a5b51d601..f9b742e20f716 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 e9db40a8e6c5b..91a83a12f9d85 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])
diff --git a/tests/stress/recursive_iternext.py b/tests/stress/recursive_iternext.py
index bbc389e726237..c737f1e36d70a 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)
diff --git a/tests/thread/thread_exc2.py.native.exp b/tests/thread/thread_exc2.py.native.exp
new file mode 100644
index 0000000000000..9b2e715ef8dfc
--- /dev/null
+++ b/tests/thread/thread_exc2.py.native.exp
@@ -0,0 +1,3 @@
+Unhandled exception in thread started by
+ValueError:
+done
diff --git a/tools/ci.sh b/tools/ci.sh
index d787cbcf0733d..e165cb2cf3f48 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
@@ -511,14 +518,14 @@ CI_UNIX_OPTS_QEMU_RISCV64=(
)
CI_UNIX_OPTS_SANITIZE_ADDRESS=(
- VARIANT=coverage
- 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=(
- VARIANT=coverage
- 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"
)
@@ -692,7 +699,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 {
@@ -758,23 +765,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 {
@@ -879,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}
diff --git a/tools/codeformat.py b/tools/codeformat.py
index c65174ddc1278..7f13a059f45b4 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:
@@ -92,36 +96,39 @@ 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.
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]
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
diff --git a/tools/mpremote/mpremote/main.py b/tools/mpremote/mpremote/main.py
index 0aec1efad1431..b31186ba2e1c9 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)
diff --git a/tools/mpremote/mpremote/mp_errno.py b/tools/mpremote/mpremote/mp_errno.py
index 37cb1e0cb9b04..e2554ef1db6e8 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
diff --git a/tools/mpremote/mpremote/transport_serial.py b/tools/mpremote/mpremote/transport_serial.py
index 53fc48553b167..e2490a7caf8be 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
@@ -790,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}")
diff --git a/tools/mpremote/requirements.txt b/tools/mpremote/requirements.txt
index 6209cde5c331d..0aa2bb42a951e 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