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