diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000000..15186fd5c1916 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: micropython diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000000000..ec6b4c7f1962a --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,18 @@ +name: Build docs + +on: + pull_request: + paths: + - docs/** + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v1 + - name: Install Python packages + run: pip install Sphinx + - name: Build docs + run: make -C docs/ html diff --git a/.gitignore b/.gitignore index 50bd30e877e87..c52f59eec74e6 100644 --- a/.gitignore +++ b/.gitignore @@ -27,8 +27,7 @@ build-*/ # Test failure outputs ###################### -tests/*.exp -tests/*.out +tests/results/* # Python cache files ###################### diff --git a/.travis.yml b/.travis.yml index 5460bf601f484..c9fcc21336efd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,24 +25,52 @@ jobs: - stage: test os: linux dist: bionic - env: NAME="code formatting" + name: "code formatting" before_install: - sudo apt-add-repository --yes --update ppa:pybricks/ppa install: - sudo apt-get install uncrustify python3-pip - uncrustify --version + - pip3 install --user setuptools - pip3 install --user black - black --version script: - tools/codeformat.py - git diff --exit-code + # zephyr port + - stage: test + name: "zephyr port build" + services: + - docker + before_install: + - docker pull zephyrprojectrtos/ci:v0.11.8 + - > + docker run --name zephyr-ci -d -it + -v "$(pwd)":/micropython + -e ZEPHYR_SDK_INSTALL_DIR=/opt/sdk/zephyr-sdk-0.11.3 + -e ZEPHYR_TOOLCHAIN_VARIANT=zephyr + -w /micropython/ports/zephyr + zephyrprojectrtos/ci:v0.11.8 + - docker ps -a + install: + - docker exec zephyr-ci west init --mr v2.4.0 /zephyrproject + - docker exec -w /zephyrproject zephyr-ci west update + - docker exec -w /zephyrproject zephyr-ci west zephyr-export + script: + - docker exec zephyr-ci bash -c "make clean; ./make-minimal ${MAKEOPTS}" + - docker exec zephyr-ci bash -c "make clean; ./make-minimal ${MAKEOPTS} BOARD=frdm_k64f" + - docker exec zephyr-ci bash -c "make clean; make ${MAKEOPTS}" + - docker exec zephyr-ci bash -c "make clean; make ${MAKEOPTS} BOARD=frdm_k64f" + - docker exec zephyr-ci bash -c "make clean; make ${MAKEOPTS} BOARD=mimxrt1050_evk" + - docker exec zephyr-ci bash -c "make clean; make ${MAKEOPTS} BOARD=reel_board" + # unix port on OSX (first in list because the build VM takes a long time to start) - stage: test os: osx osx_image: xcode11.3 + name: "unix port build with clang on OSX" env: - - NAME="unix port build with clang on OSX" - PKG_CONFIG_PATH=/usr/local/opt/libffi/lib/pkgconfig script: - make ${MAKEOPTS} -C mpy-cross @@ -59,13 +87,12 @@ jobs: # stm32 port - stage: test - env: NAME="stm32 port build" + name: "stm32 port build" install: # need newer gcc version for Cortex-M7 support - sudo add-apt-repository -y ppa:team-gcc-arm-embedded/ppa - sudo apt-get update -qq || true - - sudo apt-get install gcc-arm-embedded - - sudo apt-get install libnewlib-arm-none-eabi + - sudo apt-get install gcc-arm-embedded libnewlib-arm-none-eabi - arm-none-eabi-gcc --version script: - make ${MAKEOPTS} -C mpy-cross @@ -79,27 +106,29 @@ jobs: - make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_L073RZ - make ${MAKEOPTS} -C ports/stm32 BOARD=STM32L476DISC - make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_WB55 + - make ${MAKEOPTS} -C ports/stm32/mboot BOARD=PYBV10 CFLAGS_EXTRA='-DMBOOT_FSLOAD=1 -DMBOOT_VFS_LFS2=1' - make ${MAKEOPTS} -C ports/stm32/mboot BOARD=PYBD_SF6 + - make ${MAKEOPTS} -C ports/stm32/mboot BOARD=NUCLEO_WB55 # qemu-arm port - stage: test dist: bionic # needed for more recent version of qemu-system-arm with mps2-an385 target - env: NAME="qemu-arm port build and tests" + name: "qemu-arm port build and tests" install: - - sudo apt-get install gcc-arm-none-eabi - - sudo apt-get install libnewlib-arm-none-eabi - - sudo apt-get install qemu-system + - sudo apt-get install gcc-arm-none-eabi libnewlib-arm-none-eabi qemu-system - arm-none-eabi-gcc --version - qemu-system-arm --version script: - make ${MAKEOPTS} -C mpy-cross + - make ${MAKEOPTS} -C ports/qemu-arm CFLAGS_EXTRA=-DMP_ENDIANNESS_BIG=1 + - make ${MAKEOPTS} -C ports/qemu-arm clean - make ${MAKEOPTS} -C ports/qemu-arm -f Makefile.test test after_failure: - grep --text "FAIL" ports/qemu-arm/build/console.out # unix coverage - stage: test - env: NAME="unix coverage build and tests" + name: "unix coverage build and tests" install: - sudo apt-get install python3-pip - sudo pip install cpp-coveralls @@ -134,7 +163,7 @@ jobs: # unix coverage 32-bit - stage: test - env: NAME="unix coverage 32-bit build and tests" + name: "unix coverage 32-bit build and tests" install: - sudo apt-get install gcc-multilib libffi-dev:i386 - sudo apt-get install python3-pip @@ -166,7 +195,7 @@ jobs: # standard unix port - stage: test - env: NAME="unix port build and tests" + name: "unix port build and tests" script: - make ${MAKEOPTS} -C mpy-cross - make ${MAKEOPTS} -C ports/unix submodules @@ -179,7 +208,7 @@ jobs: # unix nanbox/float (and using Python 2 to check it can run the build scripts) - stage: test - env: NAME="unix nanbox/float port build and tests" + name: "unix nanbox/float port build and tests" install: - sudo apt-get install gcc-multilib libffi-dev:i386 script: @@ -196,7 +225,7 @@ jobs: # unix stackless/float with clang - stage: test - env: NAME="unix stackless/float port build and tests with clang" + name: "unix stackless/float port build and tests with clang" install: - sudo apt-get install clang script: @@ -212,7 +241,7 @@ jobs: # unix with sys.settrace - stage: test - env: NAME="unix port with sys.settrace build and tests" + name: "unix port with sys.settrace build and tests" script: - make ${MAKEOPTS} -C mpy-cross - make ${MAKEOPTS} -C ports/unix MICROPY_PY_BTREE=0 MICROPY_PY_FFI=0 MICROPY_PY_USSL=0 CFLAGS_EXTRA="-DMICROPY_PY_SYS_SETTRACE=1" test || travis_terminate 1 @@ -223,7 +252,7 @@ jobs: # minimal unix port with tests - stage: test - env: NAME="minimal unix port build and tests" + name: "minimal unix port build and tests" script: - make ${MAKEOPTS} -C ports/unix VARIANT=minimal - (cd tests && MICROPY_CPYTHON3=python3 MICROPY_MICROPYTHON=../ports/unix/micropython-minimal ./run-tests -e exception_chain -e self_type_check -e subclass_native_init -d basics) @@ -232,7 +261,7 @@ jobs: # windows port via mingw - stage: test - env: NAME="windows port build via mingw" + name: "windows port build via mingw" install: - sudo apt-get install gcc-mingw-w64 script: @@ -241,7 +270,7 @@ jobs: # esp32 w/ESP-IDFv3 port - stage: test - env: NAME="esp32 ESP-IDFv3 port build" + name: "esp32 ESP-IDFv3 port build" install: - sudo apt-get install python3-pip - sudo pip3 install 'pyparsing<2.4' @@ -258,7 +287,7 @@ jobs: # esp32 w/ESP-IDFv4 port - stage: test - env: NAME="esp32 ESP-IDFv4 port build" + name: "esp32 ESP-IDFv4 port build" install: - sudo apt-get install python3-pip - sudo pip3 install 'pyparsing<2.4' @@ -275,7 +304,7 @@ jobs: # esp8266 port - stage: test - env: NAME="esp8266 port build" + name: "esp8266 port build" install: - wget https://github.com/jepler/esp-open-sdk/releases/download/2018-06-10/xtensa-lx106-elf-standalone.tar.gz - zcat xtensa-lx106-elf-standalone.tar.gz | tar x @@ -289,24 +318,27 @@ jobs: # nrf port - stage: test - env: NAME="nrf port build" + name: "nrf port build" install: - - sudo apt-get install gcc-arm-none-eabi + # need newer gcc version for Cortex-M33 support + - sudo add-apt-repository -y ppa:team-gcc-arm-embedded/ppa + - sudo apt-get update -qq || true + - sudo apt-get install gcc-arm-embedded - sudo apt-get install libnewlib-arm-none-eabi - arm-none-eabi-gcc --version script: + - ports/nrf/drivers/bluetooth/download_ble_stack.sh s140_nrf52_6_1_1 - make ${MAKEOPTS} -C ports/nrf submodules - make ${MAKEOPTS} -C ports/nrf BOARD=pca10040 - make ${MAKEOPTS} -C ports/nrf BOARD=microbit - - make ${MAKEOPTS} -C ports/nrf BOARD=pca10056 + - make ${MAKEOPTS} -C ports/nrf BOARD=pca10056 SD=s140 + - make ${MAKEOPTS} -C ports/nrf BOARD=pca10090 # bare-arm and minimal ports, with size-diff check - stage: test - env: NAME="bare-arm and minimal ports build and size-diff check" + name: "bare-arm and minimal ports build and size-diff check" install: - - sudo apt-get install gcc-multilib libffi-dev:i386 - - sudo apt-get install gcc-arm-none-eabi - - sudo apt-get install libnewlib-arm-none-eabi + - sudo apt-get install gcc-multilib libffi-dev:i386 gcc-arm-none-eabi libnewlib-arm-none-eabi - gcc --version - arm-none-eabi-gcc --version script: @@ -330,38 +362,35 @@ jobs: # cc3200 port - stage: test - env: NAME="cc3200 port build" + name: "cc3200 port build" install: - - sudo apt-get install gcc-arm-none-eabi - - sudo apt-get install libnewlib-arm-none-eabi + - sudo apt-get install gcc-arm-none-eabi libnewlib-arm-none-eabi script: - make ${MAKEOPTS} -C ports/cc3200 BTARGET=application BTYPE=release - make ${MAKEOPTS} -C ports/cc3200 BTARGET=bootloader BTYPE=release # samd port - stage: test - env: NAME="samd port build" + name: "samd port build" install: - - sudo apt-get install gcc-arm-none-eabi - - sudo apt-get install libnewlib-arm-none-eabi + - sudo apt-get install gcc-arm-none-eabi libnewlib-arm-none-eabi script: - make ${MAKEOPTS} -C ports/samd submodules - make ${MAKEOPTS} -C ports/samd # teensy port - stage: test - env: NAME="teensy port build" + name: "teensy port build" install: - - sudo apt-get install gcc-arm-none-eabi - - sudo apt-get install libnewlib-arm-none-eabi + - sudo apt-get install gcc-arm-none-eabi libnewlib-arm-none-eabi script: - make ${MAKEOPTS} -C ports/teensy # powerpc port - stage: test - env: NAME="powerpc port build" + name: "powerpc port build" install: - - sudo apt-get install gcc-powerpc64le-linux-gnu - - sudo apt-get install libc6-dev-ppc64el-cross + - sudo apt-get install gcc-powerpc64le-linux-gnu libc6-dev-ppc64el-cross script: - - make ${MAKEOPTS} -C ports/powerpc CROSS_COMPILE=powerpc64le-linux-gnu- + - make ${MAKEOPTS} -C ports/powerpc UART=potato + - make ${MAKEOPTS} -C ports/powerpc UART=lpc_serial diff --git a/CODECONVENTIONS.md b/CODECONVENTIONS.md index b18c9818ade25..78fb912a6ab75 100644 --- a/CODECONVENTIONS.md +++ b/CODECONVENTIONS.md @@ -27,9 +27,12 @@ change beyond 5 lines would likely require such detailed description. To get good practical examples of good commits and their messages, browse the `git log` of the project. -MicroPython doesn't require explicit sign-off for patches ("Signed-off-by" -lines and similar). Instead, the commit message, and your name and email -address on it construes your sign-off of the following: +When committing you are encouraged to sign-off your commit by adding +"Signed-off-by" lines and similar, eg using "git commit -s". If you don't +explicitly sign-off in this way then the commit message, which includes your +name and email address in the "Author" line, implies your sign-off. In either +case, of explicit or implicit sign-off, you are certifying and signing off +against the following: * That you wrote the change yourself, or took it from a project with a compatible license (in the latter case the commit message, and possibly @@ -43,8 +46,11 @@ address on it construes your sign-off of the following: copyright for your changes (for smaller changes, the commit message conveys your copyright; if you make significant changes to a particular source module, you're welcome to add your name to the file header). -* Your signature for all of the above, which is the 'Author' line in - the commit message, and which should include your full real name and +* Your contribution including commit message will be publicly and + indefinitely available for anyone to access, including redistribution + under the terms of the project's license. +* Your signature for all of the above, which is the "Signed-off-by" line + or the "Author" line in the commit message, includes your full real name and a valid and active email address by which you can be contacted in the foreseeable future. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 06f970607b9bb..73004ac5181ab 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,6 +3,6 @@ make sure that you are acquainted with Contributor Guidelines: https://github.com/micropython/micropython/wiki/ContributorGuidelines -and Code Conventions: +as well as the Code Conventions, which includes details of how to commit: https://github.com/micropython/micropython/blob/master/CODECONVENTIONS.md diff --git a/LICENSE b/LICENSE index e6a54cf269947..8c5e4fe4c359a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013-2019 Damien P. George +Copyright (c) 2013-2020 Damien P. George Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/docs/Makefile b/docs/Makefile index e9c128e900788..05709617c35b9 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -3,7 +3,7 @@ # You can set these variables from the command line. PYTHON = python3 -SPHINXOPTS = +SPHINXOPTS = -W --keep-going SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build/$(MICROPY_PORT) diff --git a/docs/conf.py b/docs/conf.py index 36f99cd2c441b..460886b4da2e8 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -66,7 +66,7 @@ # General information about the project. project = 'MicroPython' -copyright = '2014-2019, Damien P. George, Paul Sokolovsky, and contributors' +copyright = '2014-2020, Damien P. George, Paul Sokolovsky, and contributors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -74,7 +74,7 @@ # # We don't follow "The short X.Y version" vs "The full version, including alpha/beta/rc tags" # breakdown, so use the same version identifier for both to avoid confusion. -version = release = '1.12' +version = release = '1.13' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/develop/natmod.rst b/docs/develop/natmod.rst index dc9f82e773874..a4c188719a4d9 100644 --- a/docs/develop/natmod.rst +++ b/docs/develop/natmod.rst @@ -21,7 +21,8 @@ language which can be compiled to stand-alone machine code can be put into a A native .mpy module is built using the ``mpy_ld.py`` tool, which is found in the ``tools/`` directory of the project. This tool takes a set of object files -(.o files) and links them together to create a native .mpy files. +(.o files) and links them together to create a native .mpy files. It requires +CPython 3 and the library pyelftools v0.25 or greater. Supported features and limitations ---------------------------------- @@ -179,6 +180,14 @@ The file ``Makefile`` contains: Compiling the module -------------------- +The prerequisite tools needed to build a native .mpy file are: + +* The MicroPython repository (at least the ``py/`` and ``tools/`` directories). +* CPython 3, and the library pyelftools (eg ``pip install 'pyelftools>=0.25'``). +* GNU make. +* A C compiler for the target architecture (if C source is used). +* Optionally ``mpy-cross``, built from the MicroPython repository (if .py source is used). + Be sure to select the correct ``ARCH`` for the target you are going to run on. Then build with:: diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index 8861ca4ac87ed..79e61a10b6dfd 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -118,17 +118,21 @@ Use the :mod:`time ` module:: Timers ------ -Virtual (RTOS-based) timers are supported. Use the :ref:`machine.Timer ` class -with timer ID of -1:: +The ESP32 port has four hardware timers. Use the :ref:`machine.Timer ` class +with a timer ID from 0 to 3 (inclusive):: from machine import Timer - tim = Timer(-1) - tim.init(period=5000, mode=Timer.ONE_SHOT, callback=lambda t:print(1)) - tim.init(period=2000, mode=Timer.PERIODIC, callback=lambda t:print(2)) + tim0 = Timer(0) + tim0.init(period=5000, mode=Timer.ONE_SHOT, callback=lambda t:print(0)) + + tim1 = Timer(1) + tim1.init(period=2000, mode=Timer.PERIODIC, callback=lambda t:print(1)) The period is in milliseconds. +Virtual timers are not currently supported on this port. + .. _Pins_and_GPIO: Pins and GPIO @@ -172,9 +176,10 @@ PWM (pulse width modulation) PWM can be enabled on all output-enabled pins. The base frequency can range from 1Hz to 40MHz but there is a tradeoff; as the base frequency -*increases* the duty resolution *decreases*. See +*increases* the duty resolution *decreases*. See `LED Control `_ for more details. +Currently the duty cycle has to be in the range of 0-1023. Use the ``machine.PWM`` class:: @@ -244,16 +249,15 @@ ESP32 specific ADC class method reference: Software SPI bus ---------------- -There are two SPI drivers. One is implemented in software (bit-banging) -and works on all pins, and is accessed via the :ref:`machine.SPI ` -class:: +Software SPI (using bit-banging) works on all pins, and is accessed via the +:ref:`machine.SoftSPI ` class:: - from machine import Pin, SPI + from machine import Pin, SoftSPI - # construct an SPI bus on the given pins + # construct a SoftSPI bus on the given pins # polarity is the idle state of SCK # phase=0 means sample on the first edge of SCK, phase=1 means the second - spi = SPI(baudrate=100000, polarity=1, phase=0, sck=Pin(0), mosi=Pin(2), miso=Pin(4)) + spi = SoftSPI(baudrate=100000, polarity=1, phase=0, sck=Pin(0), mosi=Pin(2), miso=Pin(4)) spi.init(baudrate=200000) # set the baudrate @@ -272,7 +276,7 @@ class:: .. Warning:: Currently *all* of ``sck``, ``mosi`` and ``miso`` *must* be specified when - initialising Software SPI. + initialising Software SPI. Hardware SPI bus ---------------- @@ -293,39 +297,54 @@ mosi 13 23 miso 12 19 ===== =========== ============ -Hardware SPI has the same methods as Software SPI above:: +Hardware SPI is accessed via the :ref:`machine.SPI ` class and +has the same methods as software SPI above:: from machine import Pin, SPI hspi = SPI(1, 10000000, sck=Pin(14), mosi=Pin(13), miso=Pin(12)) vspi = SPI(2, baudrate=80000000, polarity=0, phase=0, bits=8, firstbit=0, sck=Pin(18), mosi=Pin(23), miso=Pin(19)) +Software I2C bus +---------------- -I2C bus -------- +Software I2C (using bit-banging) works on all output-capable pins, and is +accessed via the :ref:`machine.SoftI2C ` class:: -The I2C driver has both software and hardware implementations, and the two -hardware peripherals have identifiers 0 and 1. Any available output-capable -pins can be used for SCL and SDA. The driver is accessed via the -:ref:`machine.I2C ` class:: + from machine import Pin, SoftI2C - from machine import Pin, I2C + i2c = SoftI2C(scl=Pin(5), sda=Pin(4), freq=100000) - # construct a software I2C bus - i2c = I2C(scl=Pin(5), sda=Pin(4), freq=100000) + i2c.scan() # scan for devices - # construct a hardware I2C bus - i2c = I2C(0) - i2c = I2C(1, scl=Pin(5), sda=Pin(4), freq=400000) - - i2c.scan() # scan for slave devices - - i2c.readfrom(0x3a, 4) # read 4 bytes from slave device with address 0x3a - i2c.writeto(0x3a, '12') # write '12' to slave device with address 0x3a + i2c.readfrom(0x3a, 4) # read 4 bytes from device with address 0x3a + i2c.writeto(0x3a, '12') # write '12' to device with address 0x3a buf = bytearray(10) # create a buffer with 10 bytes i2c.writeto(0x3a, buf) # write the given buffer to the slave +Hardware I2C bus +---------------- + +There are two hardware I2C peripherals with identifiers 0 and 1. Any available +output-capable pins can be used for SCL and SDA but the defaults are given +below. + +===== =========== ============ +\ I2C(0) I2C(1) +===== =========== ============ +scl 18 25 +sda 19 26 +===== =========== ============ + +The driver is accessed via the :ref:`machine.I2C ` class and +has the same methods as software I2C above:: + + from machine import Pin, I2C + + i2c = I2C(0) + i2c = I2C(1, scl=Pin(5), sda=Pin(4), freq=400000) + Real time clock (RTC) --------------------- @@ -444,11 +463,11 @@ Use the ``TouchPad`` class in the ``machine`` module:: from machine import TouchPad, Pin t = TouchPad(Pin(14)) - t.read() # Returns a smaller number when touched + t.read() # Returns a smaller number when touched ``TouchPad.read`` returns a value relative to the capacitive variation. Small numbers (typically in -the *tens*) are common when a pin is touched, larger numbers (above *one thousand*) when -no touch is present. However the values are *relative* and can vary depending on the board +the *tens*) are common when a pin is touched, larger numbers (above *one thousand*) when +no touch is present. However the values are *relative* and can vary depending on the board and surrounding composition so some calibration may be required. There are ten capacitive touch-enabled pins that can be used on the ESP32: 0, 2, 4, 12, 13 diff --git a/docs/esp8266/quickref.rst b/docs/esp8266/quickref.rst index 9c7db3205a7e6..1a22bd50a5ac2 100644 --- a/docs/esp8266/quickref.rst +++ b/docs/esp8266/quickref.rst @@ -138,6 +138,45 @@ Also note that Pin(16) is a special pin (used for wakeup from deepsleep mode) and may be not available for use with higher-level classes like ``Neopixel``. +UART (serial bus) +----------------- + +See :ref:`machine.UART `. :: + + from machine import UART + uart = UART(0, baudrate=9600) + uart.write('hello') + uart.read(5) # read up to 5 bytes + +Two UARTs are available. UART0 is on Pins 1 (TX) and 3 (RX). UART0 is +bidirectional, and by default is used for the REPL. UART1 is on Pins 2 +(TX) and 8 (RX) however Pin 8 is used to connect the flash chip, so +UART1 is TX only. + +When UART0 is attached to the REPL, all incoming chars on UART(0) go +straight to stdin so uart.read() will always return None. Use +sys.stdin.read() if it's needed to read characters from the UART(0) +while it's also used for the REPL (or detach, read, then reattach). +When detached the UART(0) can be used for other purposes. + +If there are no objects in any of the dupterm slots when the REPL is +started (on hard or soft reset) then UART(0) is automatically attached. +Without this, the only way to recover a board without a REPL would be to +completely erase and reflash (which would install the default boot.py which +attaches the REPL). + +To detach the REPL from UART0, use:: + + import uos + uos.dupterm(None, 1) + +The REPL is attached by default. If you have detached it, to reattach +it use:: + + import uos, machine + uart = machine.UART(0, 115200) + uos.dupterm(uart, 1) + PWM (pulse width modulation) ---------------------------- @@ -175,15 +214,15 @@ Software SPI bus ---------------- There are two SPI drivers. One is implemented in software (bit-banging) -and works on all pins, and is accessed via the :ref:`machine.SPI ` +and works on all pins, and is accessed via the :ref:`machine.SoftSPI ` class:: - from machine import Pin, SPI + from machine import Pin, SoftSPI # construct an SPI bus on the given pins # polarity is the idle state of SCK # phase=0 means sample on the first edge of SCK, phase=1 means the second - spi = SPI(-1, baudrate=100000, polarity=1, phase=0, sck=Pin(0), mosi=Pin(2), miso=Pin(4)) + spi = SoftSPI(baudrate=100000, polarity=1, phase=0, sck=Pin(0), mosi=Pin(2), miso=Pin(4)) spi.init(baudrate=200000) # set the baudrate @@ -219,7 +258,8 @@ I2C bus ------- The I2C driver is implemented in software and works on all pins, -and is accessed via the :ref:`machine.I2C ` class:: +and is accessed via the :ref:`machine.I2C ` class (which is an +alias of :ref:`machine.SoftI2C `):: from machine import Pin, I2C diff --git a/docs/library/btree.rst b/docs/library/btree.rst index a1f7a5420f15e..ba910852102d1 100644 --- a/docs/library/btree.rst +++ b/docs/library/btree.rst @@ -76,7 +76,7 @@ Example:: Functions --------- -.. function:: open(stream, \*, flags=0, pagesize=0, cachesize=0, minkeypage=0) +.. function:: open(stream, *, flags=0, pagesize=0, cachesize=0, minkeypage=0) Open a database from a random-access `stream` (like an open file). All other parameters are optional and keyword-only, and allow to tweak advanced @@ -118,7 +118,7 @@ Methods .. method:: btree.__getitem__(key) btree.get(key, default=None, /) btree.__setitem__(key, val) - btree.__detitem__(key) + btree.__delitem__(key) btree.__contains__(key) Standard dictionary methods. diff --git a/docs/library/esp.rst b/docs/library/esp.rst index cb2bc7af8d538..b9ae57bd9757f 100644 --- a/docs/library/esp.rst +++ b/docs/library/esp.rst @@ -31,7 +31,7 @@ Functions The system enters the set sleep mode automatically when possible. -.. function:: deepsleep(time=0, /) +.. function:: deepsleep(time_us=0, /) **Note**: ESP8266 only - use `machine.deepsleep()` on ESP32 diff --git a/docs/library/esp32.rst b/docs/library/esp32.rst index f3be3692e3267..c6777b8a7da33 100644 --- a/docs/library/esp32.rst +++ b/docs/library/esp32.rst @@ -84,9 +84,9 @@ methods to enable over-the-air (OTA) updates. Returns a 6-tuple ``(type, subtype, addr, size, label, encrypted)``. .. method:: Partition.readblocks(block_num, buf) -.. method:: Partition.readblocks(block_num, buf, offset) + Partition.readblocks(block_num, buf, offset) .. method:: Partition.writeblocks(block_num, buf) -.. method:: Partition.writeblocks(block_num, buf, offset) + Partition.writeblocks(block_num, buf, offset) .. method:: Partition.ioctl(cmd, arg) These methods implement the simple and :ref:`extended @@ -151,6 +151,11 @@ used to transmit or receive many other types of digital signals:: r = esp32.RMT(0, pin=Pin(18), clock_div=8) r # RMT(channel=0, pin=18, source_freq=80000000, clock_div=8) + + # To use carrier frequency + r = esp32.RMT(0, pin=Pin(18), clock_div=8, carrier_freq=38000) + r # RMT(channel=0, pin=18, source_freq=80000000, clock_div=8, carrier_freq=38000, carrier_duty_percent=50) + # The channel resolution is 100ns (1/(source_freq/clock_div)). r.write_pulses((1, 20, 2, 40), start=0) # Send 0 for 100ns, 1 for 2000ns, 0 for 200ns, 1 for 4000ns @@ -164,6 +169,9 @@ define the pulses. multiplying the resolution by a 15-bit (0-32,768) number. There are eight channels (0-7) and each can have a different clock divider. +To enable the carrier frequency feature of the esp32 hardware, specify the +``carrier_freq`` as something like 38000, a typical IR carrier frequency. + So, in the example above, the 80MHz clock is divided by 8. Thus the resolution is (1/(80Mhz/8)) 100ns. Since the ``start`` level is 0 and toggles with each number, the bitstream is ``0101`` with durations of [100ns, 2000ns, @@ -174,17 +182,20 @@ For more details see Espressif's `ESP-IDF RMT documentation. .. Warning:: The current MicroPython RMT implementation lacks some features, most notably - receiving pulses and carrier transmit. RMT should be considered a + receiving pulses. RMT should be considered a *beta feature* and the interface may change in the future. -.. class:: RMT(channel, \*, pin=None, clock_div=8) +.. class:: RMT(channel, *, pin=None, clock_div=8, carrier_freq=0, carrier_duty_percent=50) This class provides access to one of the eight RMT channels. *channel* is required and identifies which RMT channel (0-7) will be configured. *pin*, also required, configures which Pin is bound to the RMT channel. *clock_div* is an 8-bit clock divider that divides the source clock (80MHz) to the RMT - channel allowing the resolution to be specified. + channel allowing the resolution to be specified. *carrier_freq* is used to + enable the carrier feature and specify its frequency, default value is ``0`` + (not enabled). To enable, specify a positive integer. *carrier_duty_percent* + defaults to 50. .. method:: RMT.source_freq() @@ -198,19 +209,21 @@ For more details see Espressif's `ESP-IDF RMT documentation. .. method:: RMT.wait_done(timeout=0) - Returns True if `RMT.write_pulses` has completed. + Returns ``True`` if the channel is currently transmitting a stream of pulses + started with a call to `RMT.write_pulses`. If *timeout* (defined in ticks of ``source_freq / clock_div``) is specified - the method will wait for *timeout* or until `RMT.write_pulses` is complete, - returning ``False`` if the channel continues to transmit. - -.. Warning:: - Avoid using ``wait_done()`` if looping is enabled. + the method will wait for *timeout* or until transmission is complete, + returning ``False`` if the channel continues to transmit. If looping is + enabled with `RMT.loop` and a stream has started, then this method will + always (wait and) return ``False``. .. method:: RMT.loop(enable_loop) - Configure looping on the channel, allowing a stream of pulses to be - indefinitely repeated. *enable_loop* is bool, set to True to enable looping. + Configure looping on the channel. *enable_loop* is bool, set to ``True`` to + enable looping on the *next* call to `RMT.write_pulses`. If called with + ``False`` while a looping stream is currently being transmitted then the + current set of pulses will be completed before transmission stops. .. method:: RMT.write_pulses(pulses, start) @@ -219,6 +232,15 @@ For more details see Espressif's `ESP-IDF RMT documentation. resolution ``(1 / (source_freq / clock_div))``. *start* defines whether the stream starts at 0 or 1. + If transmission of a stream is currently in progress then this method will + block until transmission of that stream has ended before beginning sending + *pulses*. + + If looping is enabled with `RMT.loop`, the stream of pulses will be repeated + indefinitely. Further calls to `RMT.write_pulses` will end the previous + stream - blocking until the last set of pulses has been transmitted - + before starting the next stream. + Ultra-Low-Power co-processor ---------------------------- diff --git a/docs/library/index.rst b/docs/library/index.rst index 7b1636ce61b33..43d9e87f3cda7 100644 --- a/docs/library/index.rst +++ b/docs/library/index.rst @@ -23,7 +23,7 @@ into MicroPython. There are a few categories of such modules: * Modules which implement a subset of Python functionality, with a provision for extension by the user (via Python code). * Modules which implement MicroPython extensions to the Python standard libraries. -* Modules specific to a particular `MicroPython port` and thus not portable. +* Modules specific to a particular :term:`MicroPython port` and thus not portable. Note about the availability of the modules and their contents: This documentation in general aspires to describe all modules and functions/classes which are @@ -38,7 +38,7 @@ in a module (or even the entire module) described in this documentation **may be unavailable** in a particular build of MicroPython on a particular system. The best place to find general information of the availability/non-availability of a particular feature is the "General Information" section which contains -information pertaining to a specific `MicroPython port`. +information pertaining to a specific :term:`MicroPython port`. On some ports you are able to discover the available, built-in libraries that can be imported by entering the following at the REPL:: @@ -77,7 +77,6 @@ it will fallback to loading the built-in ``ujson`` module. cmath.rst gc.rst math.rst - sys.rst uarray.rst uasyncio.rst ubinascii.rst @@ -93,6 +92,7 @@ it will fallback to loading the built-in ``ujson`` module. usocket.rst ussl.rst ustruct.rst + usys.rst utime.rst uzlib.rst _thread.rst diff --git a/docs/library/lcd160cr.rst b/docs/library/lcd160cr.rst index e31ed94651ca5..85e4b8f07a1f6 100644 --- a/docs/library/lcd160cr.rst +++ b/docs/library/lcd160cr.rst @@ -37,7 +37,7 @@ For example:: Constructors ------------ -.. class:: LCD160CR(connect=None, \*, pwr=None, i2c=None, spi=None, i2c_addr=98) +.. class:: LCD160CR(connect=None, *, pwr=None, i2c=None, spi=None, i2c_addr=98) Construct an LCD160CR object. The parameters are: diff --git a/docs/library/machine.ADCWiPy.rst b/docs/library/machine.ADCWiPy.rst index 4a4f0524c8f0b..e500d00890f3a 100644 --- a/docs/library/machine.ADCWiPy.rst +++ b/docs/library/machine.ADCWiPy.rst @@ -22,7 +22,7 @@ Usage:: Constructors ------------ -.. class:: ADCWiPy(id=0, \*, bits=12) +.. class:: ADCWiPy(id=0, *, bits=12) Create an ADC object associated with the given pin. This allows you to then read analog values on that pin. @@ -39,7 +39,7 @@ Constructors Methods ------- -.. method:: ADCWiPy.channel(id, \*, pin) +.. method:: ADCWiPy.channel(id, *, pin) Create an analog pin. If only channel ID is given, the correct pin will be selected. Alternatively, only the pin can be passed and the correct diff --git a/docs/library/machine.I2C.rst b/docs/library/machine.I2C.rst index 2cbfec1ba83bf..f9b951564611b 100644 --- a/docs/library/machine.I2C.rst +++ b/docs/library/machine.I2C.rst @@ -12,6 +12,14 @@ when created, or initialised later on. Printing the I2C object gives you information about its configuration. +Both hardware and software I2C implementations exist via the +:ref:`machine.I2C ` and `machine.SoftI2C` classes. Hardware I2C uses +underlying hardware support of the system to perform the reads/writes and is +usually efficient and fast but may have restrictions on which pins can be used. +Software I2C is implemented by bit-banging and can be used on any pin but is not +as efficient. These classes have the same methods available and differ primarily +in the way they are constructed. + Example usage:: from machine import I2C @@ -33,26 +41,38 @@ Example usage:: Constructors ------------ -.. class:: I2C(id=-1, \*, scl, sda, freq=400000) +.. class:: I2C(id, *, scl, sda, freq=400000) Construct and return a new I2C object using the following parameters: - - *id* identifies a particular I2C peripheral. The default - value of -1 selects a software implementation of I2C which can - work (in most cases) with arbitrary pins for SCL and SDA. - If *id* is -1 then *scl* and *sda* must be specified. Other - allowed values for *id* depend on the particular port/board, - and specifying *scl* and *sda* may or may not be required or - allowed in this case. + - *id* identifies a particular I2C peripheral. Allowed values for + depend on the particular port/board + - *scl* should be a pin object specifying the pin to use for SCL. + - *sda* should be a pin object specifying the pin to use for SDA. + - *freq* should be an integer which sets the maximum frequency + for SCL. + + 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. + +.. _machine.SoftI2C: +.. class:: SoftI2C(scl, sda, *, freq=400000, timeout=255) + + Construct a new software I2C object. The parameters are: + - *scl* should be a pin object specifying the pin to use for SCL. - *sda* should be a pin object specifying the pin to use for SDA. - *freq* should be an integer which sets the maximum frequency for SCL. + - *timeout* is the maximum time in microseconds to wait for clock + stretching (SCL held low by another device on the bus), after + which an ``OSError(ETIMEDOUT)`` exception is raised. General Methods --------------- -.. method:: I2C.init(scl, sda, \*, freq=400000) +.. method:: I2C.init(scl, sda, *, freq=400000) Initialise the I2C bus with the given arguments: @@ -79,7 +99,7 @@ The following methods implement the primitive I2C master bus operations and can be combined to make any I2C transaction. They are provided if you need more control over the bus, otherwise the standard methods (see below) can be used. -These methods are available on software I2C only. +These methods are only available on the `machine.SoftI2C` class. .. method:: I2C.start() @@ -153,14 +173,14 @@ from and written to. In this case there are two addresses associated with an I2C transaction: the slave address and the memory address. The following methods are convenience functions to communicate with such devices. -.. method:: I2C.readfrom_mem(addr, memaddr, nbytes, \*, addrsize=8) +.. method:: I2C.readfrom_mem(addr, memaddr, nbytes, *, addrsize=8) Read *nbytes* from the slave specified by *addr* starting from the memory address specified by *memaddr*. The argument *addrsize* specifies the address size in bits. Returns a `bytes` object with the data read. -.. method:: I2C.readfrom_mem_into(addr, memaddr, buf, \*, addrsize=8) +.. method:: I2C.readfrom_mem_into(addr, memaddr, buf, *, addrsize=8) Read into *buf* from the slave specified by *addr* starting from the memory address specified by *memaddr*. The number of bytes read is the @@ -170,7 +190,7 @@ methods are convenience functions to communicate with such devices. The method returns ``None``. -.. method:: I2C.writeto_mem(addr, memaddr, buf, \*, addrsize=8) +.. method:: I2C.writeto_mem(addr, memaddr, buf, *, addrsize=8) Write *buf* to the slave specified by *addr* starting from the memory address specified by *memaddr*. diff --git a/docs/library/machine.Pin.rst b/docs/library/machine.Pin.rst index 3055491ebbba8..095240fe1499a 100644 --- a/docs/library/machine.Pin.rst +++ b/docs/library/machine.Pin.rst @@ -42,7 +42,7 @@ Usage Model:: Constructors ------------ -.. class:: Pin(id, mode=-1, pull=-1, \*, value, drive, alt) +.. class:: Pin(id, mode=-1, pull=-1, *, value, drive, alt) Access the pin peripheral (GPIO pin) associated with the given ``id``. If additional arguments are given in the constructor then they are used to initialise @@ -106,7 +106,7 @@ Constructors Methods ------- -.. method:: Pin.init(mode=-1, pull=-1, \*, value, drive, alt) +.. method:: Pin.init(mode=-1, pull=-1, *, value, drive, alt) Re-initialise the pin using the given parameters. Only those arguments that are specified will be set. The rest of the pin peripheral state will remain @@ -179,7 +179,7 @@ Methods Availability: WiPy. -.. method:: Pin.irq(handler=None, trigger=(Pin.IRQ_FALLING | Pin.IRQ_RISING), \*, priority=1, wake=None, hard=False) +.. method:: Pin.irq(handler=None, trigger=(Pin.IRQ_FALLING | Pin.IRQ_RISING), *, priority=1, wake=None, hard=False) Configure an interrupt handler to be called when the trigger source of the pin is active. If the pin mode is ``Pin.IN`` then the trigger source is diff --git a/docs/library/machine.RTC.rst b/docs/library/machine.RTC.rst index 548ad007e0dc3..d5a4f390b512b 100644 --- a/docs/library/machine.RTC.rst +++ b/docs/library/machine.RTC.rst @@ -38,7 +38,7 @@ Methods Resets the RTC to the time of January 1, 2015 and starts running it again. -.. method:: RTC.alarm(id, time, \*, repeat=False) +.. method:: RTC.alarm(id, time, *, repeat=False) Set the RTC alarm. Time might be either a millisecond value to program the alarm to current time + time_in_ms in the future, or a datetimetuple. If the time passed is in @@ -52,7 +52,7 @@ Methods Cancel a running alarm. -.. method:: RTC.irq(\*, trigger, handler=None, wake=machine.IDLE) +.. method:: RTC.irq(*, trigger, handler=None, wake=machine.IDLE) Create an irq object triggered by a real time clock alarm. diff --git a/docs/library/machine.SDCard.rst b/docs/library/machine.SDCard.rst index cf86b1fcb25e6..b1cf42ec0285b 100644 --- a/docs/library/machine.SDCard.rst +++ b/docs/library/machine.SDCard.rst @@ -23,7 +23,7 @@ arguments that might need to be set in order to use either a non-standard slot or a non-standard pin assignment. The exact subset of arguments supported will vary from platform to platform. -.. class:: SDCard(slot=1, width=1, cd=None, wp=None, sck=None, miso=None, mosi=None, cs=None) +.. class:: SDCard(slot=1, width=1, cd=None, wp=None, sck=None, miso=None, mosi=None, cs=None, freq=20000000) This class provides access to SD or MMC storage cards using either a dedicated SD/MMC interface hardware or through an SPI channel. @@ -50,6 +50,8 @@ vary from platform to platform. - *mosi* can be used to specify an SPI mosi pin. - *cs* can be used to specify an SPI chip select pin. + + - *freq* selects the SD/MMC interface frequency in Hz (only supported on the ESP32). Implementation-specific details ------------------------------- diff --git a/docs/library/machine.SPI.rst b/docs/library/machine.SPI.rst index a9fcc719c8956..7565241eb1cd2 100644 --- a/docs/library/machine.SPI.rst +++ b/docs/library/machine.SPI.rst @@ -11,25 +11,39 @@ SS (Slave Select), to select a particular device on a bus with which communication takes place. Management of an SS signal should happen in user code (via machine.Pin class). +Both hardware and software SPI implementations exist via the +:ref:`machine.SPI ` and `machine.SoftSPI` classes. Hardware SPI uses underlying +hardware support of the system to perform the reads/writes and is usually +efficient and fast but may have restrictions on which pins can be used. +Software SPI is implemented by bit-banging and can be used on any pin but +is not as efficient. These classes have the same methods available and +differ primarily in the way they are constructed. + Constructors ------------ .. class:: SPI(id, ...) - Construct an SPI object on the given bus, ``id``. Values of ``id`` depend + Construct an SPI object on the given bus, *id*. Values of *id* depend on a particular port and its hardware. Values 0, 1, etc. are commonly used - to select hardware SPI block #0, #1, etc. Value -1 can be used for - bitbanging (software) implementation of SPI (if supported by a port). + to select hardware SPI block #0, #1, etc. With no additional parameters, the SPI object is created but not initialised (it has the settings from the last initialisation of the bus, if any). If extra arguments are given, the bus is initialised. See ``init`` for parameters of initialisation. +.. _machine.SoftSPI: +.. class:: SoftSPI(baudrate=500000, *, polarity=0, phase=0, bits=8, firstbit=MSB, sck=None, mosi=None, miso=None) + + Construct a new software SPI object. Additional parameters must be + given, usually at least *sck*, *mosi* and *miso*, and these are used + to initialise the bus. See `SPI.init` for a description of the parameters. + Methods ------- -.. method:: SPI.init(baudrate=1000000, \*, polarity=0, phase=0, bits=8, firstbit=SPI.MSB, sck=None, mosi=None, miso=None, pins=(SCK, MOSI, MISO)) +.. method:: SPI.init(baudrate=1000000, *, polarity=0, phase=0, bits=8, firstbit=SPI.MSB, sck=None, mosi=None, miso=None, pins=(SCK, MOSI, MISO)) Initialise the SPI bus with the given parameters: diff --git a/docs/library/machine.Signal.rst b/docs/library/machine.Signal.rst index a1a29164b2cfd..1e1fcb5483a86 100644 --- a/docs/library/machine.Signal.rst +++ b/docs/library/machine.Signal.rst @@ -55,7 +55,7 @@ Following is the guide when Signal vs Pin should be used: * Use Pin: If you implement a higher-level protocol or bus to communicate with more complex devices. -The split between Pin and Signal come from the usecases above and the +The split between Pin and Signal come from the use cases above and the architecture of MicroPython: Pin offers the lowest overhead, which may be important when bit-banging protocols. But Signal adds additional flexibility on top of Pin, at the cost of minor overhead (much smaller @@ -75,7 +75,7 @@ Constructors ------------ .. class:: Signal(pin_obj, invert=False) - Signal(pin_arguments..., \*, invert=False) + Signal(pin_arguments..., *, invert=False) Create a Signal object. There're two ways to create it: diff --git a/docs/library/machine.Timer.rst b/docs/library/machine.Timer.rst index b16ad52d597f5..9991d3aebc2fc 100644 --- a/docs/library/machine.Timer.rst +++ b/docs/library/machine.Timer.rst @@ -31,11 +31,13 @@ Constructors Construct a new timer object of the given id. Id of -1 constructs a virtual timer (if supported by a board). + + See ``init`` for parameters of initialisation. Methods ------- -.. method:: Timer.init(\*, mode=Timer.PERIODIC, period=-1, callback=None) +.. method:: Timer.init(*, mode=Timer.PERIODIC, period=-1, callback=None) Initialise the timer. Example:: diff --git a/docs/library/machine.TimerWiPy.rst b/docs/library/machine.TimerWiPy.rst index 904a958c69b16..f5b748c62e1d5 100644 --- a/docs/library/machine.TimerWiPy.rst +++ b/docs/library/machine.TimerWiPy.rst @@ -39,7 +39,7 @@ Constructors Methods ------- -.. method:: TimerWiPy.init(mode, \*, width=16) +.. method:: TimerWiPy.init(mode, *, width=16) Initialise the timer. Example:: @@ -64,7 +64,7 @@ Methods Deinitialises the timer. Stops the timer, and disables the timer peripheral. -.. method:: TimerWiPy.channel(channel, \**, freq, period, polarity=TimerWiPy.POSITIVE, duty_cycle=0) +.. method:: TimerWiPy.channel(channel, **, freq, period, polarity=TimerWiPy.POSITIVE, duty_cycle=0) If only a channel identifier passed, then a previously initialized channel object is returned (or ``None`` if there is no previous channel). @@ -113,7 +113,7 @@ TimerChannel objects are created using the Timer.channel() method. Methods ------- -.. method:: timerchannel.irq(\*, trigger, priority=1, handler=None) +.. method:: timerchannel.irq(*, trigger, priority=1, handler=None) The behavior of this callback is heavily dependent on the operating mode of the timer channel: diff --git a/docs/library/machine.UART.rst b/docs/library/machine.UART.rst index 70984dfb2fff1..957d58ca18256 100644 --- a/docs/library/machine.UART.rst +++ b/docs/library/machine.UART.rst @@ -43,7 +43,7 @@ Constructors Methods ------- -.. method:: UART.init(baudrate=9600, bits=8, parity=None, stop=1, \*, ...) +.. method:: UART.init(baudrate=9600, bits=8, parity=None, stop=1, *, ...) Initialise the UART bus with the given parameters: diff --git a/docs/library/machine.rst b/docs/library/machine.rst index b580353d6b564..18dc6f2afaa59 100644 --- a/docs/library/machine.rst +++ b/docs/library/machine.rst @@ -79,7 +79,7 @@ Power related functions If *time_ms* is specified then this will be the maximum time in milliseconds that the sleep will last for. Otherwise the sleep can last indefinitely. - With or without a timout, execution may resume at any time if there are events + With or without a timeout, execution may resume at any time if there are events that require processing. Such events, or wake sources, should be configured before sleeping, like `Pin` change or `RTC` timeout. diff --git a/docs/library/micropython.rst b/docs/library/micropython.rst index ded52deb0d268..7106a1a2ff16d 100644 --- a/docs/library/micropython.rst +++ b/docs/library/micropython.rst @@ -100,7 +100,7 @@ Functions unlocked. Note: `heap_locked()` is not enabled on most ports by default, - requires `MICROPY_PY_MICROPYTHON_HEAP_LOCKED`. + requires ``MICROPY_PY_MICROPYTHON_HEAP_LOCKED``. .. function:: kbd_intr(chr) diff --git a/docs/library/network.CC3K.rst b/docs/library/network.CC3K.rst index 212a1e3bd771f..18210e2d26d59 100644 --- a/docs/library/network.CC3K.rst +++ b/docs/library/network.CC3K.rst @@ -51,7 +51,7 @@ Constructors Methods ------- -.. method:: CC3K.connect(ssid, key=None, \*, security=WPA2, bssid=None) +.. method:: CC3K.connect(ssid, key=None, *, security=WPA2, bssid=None) Connect to a WiFi access point using the given SSID, and other security parameters. diff --git a/docs/library/network.WLAN.rst b/docs/library/network.WLAN.rst index 46a27a8fe462d..fcdaa41b36345 100644 --- a/docs/library/network.WLAN.rst +++ b/docs/library/network.WLAN.rst @@ -32,7 +32,7 @@ Methods argument is passed. Otherwise, query current state if no argument is provided. Most other methods require active interface. -.. method:: WLAN.connect(ssid=None, password=None, \*, bssid=None) +.. method:: WLAN.connect(ssid=None, password=None, *, bssid=None) Connect to the specified wireless network, using the specified password. If *bssid* is given then the connection will be restricted to the @@ -101,7 +101,7 @@ Methods nic.ifconfig(('192.168.0.4', '255.255.255.0', '192.168.0.1', '8.8.8.8')) .. method:: WLAN.config('param') -.. method:: WLAN.config(param=value, ...) + WLAN.config(param=value, ...) Get or set general network interface parameters. These methods allow to work with additional parameters beyond standard IP configuration (as dealt with by @@ -117,7 +117,7 @@ Methods print(ap.config('channel')) Following are commonly supported parameters (availability of a specific parameter - depends on network technology type, driver, and `MicroPython port`). + depends on network technology type, driver, and :term:`MicroPython port`). ============= =========== Parameter Description diff --git a/docs/library/network.WLANWiPy.rst b/docs/library/network.WLANWiPy.rst index b6e3279597ac9..2a5ba118454bc 100644 --- a/docs/library/network.WLANWiPy.rst +++ b/docs/library/network.WLANWiPy.rst @@ -43,7 +43,7 @@ Constructors Methods ------- -.. method:: WLANWiPy.init(mode, \*, ssid, auth, channel, antenna) +.. method:: WLANWiPy.init(mode, *, ssid, auth, channel, antenna) Set or get the WiFi network processor configuration. @@ -69,7 +69,7 @@ Methods # configure as an station wlan.init(mode=WLAN.STA) -.. method:: WLANWiPy.connect(ssid, \*, auth=None, bssid=None, timeout=None) +.. method:: WLANWiPy.connect(ssid, *, auth=None, bssid=None, timeout=None) Connect to a WiFi access point using the given SSID, and other security parameters. @@ -131,7 +131,7 @@ Methods Get or set a 6-byte long bytes object with the MAC address. -.. method:: WLANWiPy.irq(\*, handler, wake) +.. method:: WLANWiPy.irq(*, handler, wake) Create a callback to be triggered when a WLAN event occurs during ``machine.SLEEP`` mode. Events are triggered by socket activity or by WLAN connection/disconnection. diff --git a/docs/library/network.rst b/docs/library/network.rst index 01e6f61369a40..bd3bc6f34ba81 100644 --- a/docs/library/network.rst +++ b/docs/library/network.rst @@ -39,7 +39,7 @@ Common network adapter interface ================================ This section describes an (implied) abstract base class for all network -interface classes implemented by `MicroPython ports ` +interface classes implemented by :term:`MicroPython ports ` for different hardware. This means that MicroPython does not actually provide ``AbstractNIC`` class, but any actual NIC class, as described in the following sections, implements methods as described here. @@ -58,7 +58,7 @@ parameter should be `id`. interface (behavior of calling them on inactive interface is undefined). -.. method:: AbstractNIC.connect([service_id, key=None, \*, ...]) +.. method:: AbstractNIC.connect([service_id, key=None, *, ...]) Connect the interface to a network. This method is optional, and available only for interfaces which are not "always connected". @@ -82,7 +82,7 @@ parameter should be `id`. Returns ``True`` if connected to network, otherwise returns ``False``. -.. method:: AbstractNIC.scan(\*, ...) +.. method:: AbstractNIC.scan(*, ...) Scan for the available network services/connections. Returns a list of tuples with discovered service parameters. For various diff --git a/docs/library/pyb.CAN.rst b/docs/library/pyb.CAN.rst index ba935abfd5c4e..8078e29e0cc8d 100644 --- a/docs/library/pyb.CAN.rst +++ b/docs/library/pyb.CAN.rst @@ -49,7 +49,7 @@ Class Methods Methods ------- -.. method:: CAN.init(mode, extframe=False, prescaler=100, \*, sjw=1, bs1=6, bs2=8, auto_restart=False) +.. method:: CAN.init(mode, extframe=False, prescaler=100, *, sjw=1, bs1=6, bs2=8, auto_restart=False) Initialise the CAN bus with the given parameters: @@ -135,7 +135,7 @@ Methods - number of pending RX messages on fifo 0 - number of pending RX messages on fifo 1 -.. method:: CAN.setfilter(bank, mode, fifo, params, \*, rtr) +.. method:: CAN.setfilter(bank, mode, fifo, params, *, rtr) Configure a filter bank: @@ -187,7 +187,7 @@ Methods Return ``True`` if any message waiting on the FIFO, else ``False``. -.. method:: CAN.recv(fifo, list=None, \*, timeout=5000) +.. method:: CAN.recv(fifo, list=None, *, timeout=5000) Receive data on the bus: @@ -220,7 +220,7 @@ Methods # No heap memory is allocated in the following call can.recv(0, lst) -.. method:: CAN.send(data, id, \*, timeout=0, rtr=False) +.. method:: CAN.send(data, id, *, timeout=0, rtr=False) Send a message on the bus: diff --git a/docs/library/pyb.DAC.rst b/docs/library/pyb.DAC.rst index 9a465b9ce25d8..bf07119ada166 100644 --- a/docs/library/pyb.DAC.rst +++ b/docs/library/pyb.DAC.rst @@ -49,7 +49,7 @@ To output a continuous sine-wave at 12-bit resolution:: Constructors ------------ -.. class:: pyb.DAC(port, bits=8, \*, buffering=None) +.. class:: pyb.DAC(port, bits=8, *, buffering=None) Construct a new DAC object. @@ -76,7 +76,7 @@ Constructors Methods ------- -.. method:: DAC.init(bits=8, \*, buffering=None) +.. method:: DAC.init(bits=8, *, buffering=None) Reinitialise the DAC. *bits* can be 8 or 12. *buffering* can be ``None``, ``False`` or ``True``; see above constructor for the meaning @@ -103,7 +103,7 @@ Methods value is 2\*\*``bits``-1, where ``bits`` is set when creating the DAC object or by using the ``init`` method. -.. method:: DAC.write_timed(data, freq, \*, mode=DAC.NORMAL) +.. method:: DAC.write_timed(data, freq, *, mode=DAC.NORMAL) Initiates a burst of RAM to DAC using a DMA transfer. The input data is treated as an array of bytes in 8-bit mode, and diff --git a/docs/library/pyb.Flash.rst b/docs/library/pyb.Flash.rst index 2d2f83da1c6ad..4b0c2ce2aaa2f 100644 --- a/docs/library/pyb.Flash.rst +++ b/docs/library/pyb.Flash.rst @@ -25,7 +25,8 @@ Constructors This constructor is deprecated and will be removed in a future version of MicroPython. -.. class:: pyb.Flash(\*, start=-1, len=-1) +.. class:: pyb.Flash(*, start=-1, len=-1) + :noindex: Create and return a block device that accesses the flash at the specified offset. The length defaults to the remaining size of the device. @@ -35,9 +36,9 @@ Methods ------- .. method:: Flash.readblocks(block_num, buf) -.. method:: Flash.readblocks(block_num, buf, offset) + Flash.readblocks(block_num, buf, offset) .. method:: Flash.writeblocks(block_num, buf) -.. method:: Flash.writeblocks(block_num, buf, offset) + Flash.writeblocks(block_num, buf, offset) .. method:: Flash.ioctl(cmd, arg) These methods implement the simple and :ref:`extended diff --git a/docs/library/pyb.I2C.rst b/docs/library/pyb.I2C.rst index d549c1a81207d..641dcb8815873 100644 --- a/docs/library/pyb.I2C.rst +++ b/docs/library/pyb.I2C.rst @@ -84,7 +84,7 @@ Methods Turn off the I2C bus. -.. method:: I2C.init(mode, \*, addr=0x12, baudrate=400000, gencall=False, dma=False) +.. method:: I2C.init(mode, *, addr=0x12, baudrate=400000, gencall=False, dma=False) Initialise the I2C bus with the given parameters: @@ -100,7 +100,7 @@ Methods Check if an I2C device responds to the given address. Only valid when in master mode. -.. method:: I2C.mem_read(data, addr, memaddr, \*, timeout=5000, addr_size=8) +.. method:: I2C.mem_read(data, addr, memaddr, *, timeout=5000, addr_size=8) Read from the memory of an I2C device: @@ -113,7 +113,7 @@ Methods Returns the read data. This is only valid in master mode. -.. method:: I2C.mem_write(data, addr, memaddr, \*, timeout=5000, addr_size=8) +.. method:: I2C.mem_write(data, addr, memaddr, *, timeout=5000, addr_size=8) Write to the memory of an I2C device: @@ -126,7 +126,7 @@ Methods Returns ``None``. This is only valid in master mode. -.. method:: I2C.recv(recv, addr=0x00, \*, timeout=5000) +.. method:: I2C.recv(recv, addr=0x00, *, timeout=5000) Receive data on the bus: @@ -138,7 +138,7 @@ Methods Return value: if ``recv`` is an integer then a new buffer of the bytes received, otherwise the same buffer that was passed in to ``recv``. -.. method:: I2C.send(send, addr=0x00, \*, timeout=5000) +.. method:: I2C.send(send, addr=0x00, *, timeout=5000) Send data on the bus: diff --git a/docs/library/pyb.SPI.rst b/docs/library/pyb.SPI.rst index a1910be49b7fb..24e2ec5a73d2c 100644 --- a/docs/library/pyb.SPI.rst +++ b/docs/library/pyb.SPI.rst @@ -51,7 +51,7 @@ Methods Turn off the SPI bus. -.. method:: SPI.init(mode, baudrate=328125, \*, prescaler, polarity=1, phase=0, bits=8, firstbit=SPI.MSB, ti=False, crc=None) +.. method:: SPI.init(mode, baudrate=328125, *, prescaler, polarity=1, phase=0, bits=8, firstbit=SPI.MSB, ti=False, crc=None) Initialise the SPI bus with the given parameters: @@ -64,6 +64,7 @@ Methods respectively. - ``bits`` can be 8 or 16, and is the number of bits in each transferred word. - ``firstbit`` can be ``SPI.MSB`` or ``SPI.LSB``. + - ``ti`` True indicates Texas Instruments, as opposed to Motorola, signal conventions. - ``crc`` can be None for no CRC, or a polynomial specifier. Note that the SPI clock frequency will not always be the requested baudrate. @@ -76,7 +77,7 @@ Methods Printing the SPI object will show you the computed baudrate and the chosen prescaler. -.. method:: SPI.recv(recv, \*, timeout=5000) +.. method:: SPI.recv(recv, *, timeout=5000) Receive data on the bus: @@ -87,7 +88,7 @@ Methods Return value: if ``recv`` is an integer then a new buffer of the bytes received, otherwise the same buffer that was passed in to ``recv``. -.. method:: SPI.send(send, \*, timeout=5000) +.. method:: SPI.send(send, *, timeout=5000) Send data on the bus: @@ -96,7 +97,7 @@ Methods Return value: ``None``. -.. method:: SPI.send_recv(send, recv=None, \*, timeout=5000) +.. method:: SPI.send_recv(send, recv=None, *, timeout=5000) Send and receive data on the bus at the same time: diff --git a/docs/library/pyb.Timer.rst b/docs/library/pyb.Timer.rst index bb7e7e52d5648..34fe71155fc15 100644 --- a/docs/library/pyb.Timer.rst +++ b/docs/library/pyb.Timer.rst @@ -62,7 +62,7 @@ Constructors Methods ------- -.. method:: Timer.init(\*, freq, prescaler, period) +.. method:: Timer.init(*, freq, prescaler, period, mode=Timer.UP, div=1, callback=None, deadtime=0) Initialise the timer. Initialisation must be either by frequency (in Hz) or by prescaler and period:: diff --git a/docs/library/pyb.UART.rst b/docs/library/pyb.UART.rst index ab7ab2fb882a4..a1d6e59002551 100644 --- a/docs/library/pyb.UART.rst +++ b/docs/library/pyb.UART.rst @@ -48,13 +48,16 @@ Constructors .. class:: pyb.UART(bus, ...) - Construct a UART object on the given bus. ``bus`` can be 1-6, or 'XA', 'XB', 'YA', or 'YB'. + Construct a UART object on the given bus. + For Pyboard ``bus`` can be 1-4, 6, 'XA', 'XB', 'YA', or 'YB'. + For Pyboard Lite ``bus`` can be 1, 2, 6, 'XB', or 'YA'. + For Pyboard D ``bus`` can be 1-4, 'XA', 'YA' or 'YB'. With no additional parameters, the UART object is created but not initialised (it has the settings from the last initialisation of the bus, if any). If extra arguments are given, the bus is initialised. See ``init`` for parameters of initialisation. - The physical pins of the UART busses are: + The physical pins of the UART busses on Pyboard are: - ``UART(4)`` is on ``XA``: ``(TX, RX) = (X1, X2) = (PA0, PA1)`` - ``UART(1)`` is on ``XB``: ``(TX, RX) = (X9, X10) = (PB6, PB7)`` @@ -62,14 +65,26 @@ Constructors - ``UART(3)`` is on ``YB``: ``(TX, RX) = (Y9, Y10) = (PB10, PB11)`` - ``UART(2)`` is on: ``(TX, RX) = (X3, X4) = (PA2, PA3)`` - The Pyboard Lite supports UART(1), UART(2) and UART(6) only. Pins are as above except: + The Pyboard Lite supports UART(1), UART(2) and UART(6) only, pins are: + - ``UART(1)`` is on ``XB``: ``(TX, RX) = (X9, X10) = (PB6, PB7)`` + - ``UART(6)`` is on ``YA``: ``(TX, RX) = (Y1, Y2) = (PC6, PC7)`` - ``UART(2)`` is on: ``(TX, RX) = (X1, X2) = (PA2, PA3)`` + The Pyboard D supports UART(1), UART(2), UART(3) and UART(4) only, pins are: + + - ``UART(4)`` is on ``XA``: ``(TX, RX) = (X1, X2) = (PA0, PA1)`` + - ``UART(1)`` is on ``YA``: ``(TX, RX) = (Y1, Y2) = (PA9, PA10)`` + - ``UART(3)`` is on ``YB``: ``(TX, RX) = (Y9, Y10) = (PB10, PB11)`` + - ``UART(2)`` is on: ``(TX, RX) = (X3, X4) = (PA2, PA3)`` + + *Note:* Pyboard D has ``UART(1)`` on ``YA``, unlike Pyboard and Pyboard Lite that both + have ``UART(1)`` on ``XB`` and ``UART(6)`` on ``YA``. + Methods ------- -.. method:: UART.init(baudrate, bits=8, parity=None, stop=1, \*, timeout=0, flow=0, timeout_char=0, read_buf_len=64) +.. method:: UART.init(baudrate, bits=8, parity=None, stop=1, *, timeout=0, flow=0, timeout_char=0, read_buf_len=64) Initialise the UART bus with the given parameters: diff --git a/docs/library/pyb.USB_HID.rst b/docs/library/pyb.USB_HID.rst index 649dc3df4a605..7e23d1313d0da 100644 --- a/docs/library/pyb.USB_HID.rst +++ b/docs/library/pyb.USB_HID.rst @@ -21,7 +21,7 @@ Constructors Methods ------- -.. method:: USB_HID.recv(data, \*, timeout=5000) +.. method:: USB_HID.recv(data, *, timeout=5000) Receive data on the bus: diff --git a/docs/library/pyb.USB_VCP.rst b/docs/library/pyb.USB_VCP.rst index b16924cf60b2e..bbcbc0701b3b4 100644 --- a/docs/library/pyb.USB_VCP.rst +++ b/docs/library/pyb.USB_VCP.rst @@ -21,7 +21,7 @@ Constructors Methods ------- -.. method:: USB_VCP.init(\*, flow=-1) +.. method:: USB_VCP.init(*, flow=-1) Configure the USB VCP port. If the *flow* argument is not -1 then the value sets the flow control, which can be a bitwise-or of ``USB_VCP.RTS`` and ``USB_VCP.CTS``. @@ -89,7 +89,7 @@ Methods Returns the number of bytes written. -.. method:: USB_VCP.recv(data, \*, timeout=5000) +.. method:: USB_VCP.recv(data, *, timeout=5000) Receive data on the bus: @@ -100,7 +100,7 @@ Methods Return value: if ``data`` is an integer then a new buffer of the bytes received, otherwise the number of bytes read into ``data`` is returned. -.. method:: USB_VCP.send(data, \*, timeout=5000) +.. method:: USB_VCP.send(data, *, timeout=5000) Send data over the USB VCP: diff --git a/docs/library/pyb.rst b/docs/library/pyb.rst index 34103195ddd1c..addcd20a91aeb 100644 --- a/docs/library/pyb.rst +++ b/docs/library/pyb.rst @@ -210,7 +210,7 @@ Miscellaneous functions It only makes sense to call this function from within boot.py. -.. function:: mount(device, mountpoint, \*, readonly=False, mkfs=False) +.. function:: mount(device, mountpoint, *, readonly=False, mkfs=False) .. note:: This function is deprecated. Mounting and unmounting devices should be performed by :meth:`uos.mount` and :meth:`uos.umount` instead. diff --git a/docs/library/uasyncio.rst b/docs/library/uasyncio.rst index 0f363a076ea3b..a81e532d7fd25 100644 --- a/docs/library/uasyncio.rst +++ b/docs/library/uasyncio.rst @@ -74,7 +74,13 @@ Additional functions This is a coroutine. -.. function:: gather(\*awaitables, return_exceptions=False) +.. function:: wait_for_ms(awaitable, timeout) + + Similar to `wait_for` but *timeout* is an integer in milliseconds. + + This is a coroutine, and a MicroPython extension. + +.. function:: gather(*awaitables, return_exceptions=False) Run all *awaitables* concurrently. Any *awaitables* that are not tasks are promoted to tasks. diff --git a/docs/library/ubluetooth.rst b/docs/library/ubluetooth.rst index fd2efe87d913f..f94ad3a612c07 100644 --- a/docs/library/ubluetooth.rst +++ b/docs/library/ubluetooth.rst @@ -6,8 +6,8 @@ This module provides an interface to a Bluetooth controller on a board. Currently this supports Bluetooth Low Energy (BLE) in Central, Peripheral, -Broadcaster, and Observer roles, and a device may operate in multiple -roles concurrently. +Broadcaster, and Observer roles, as well as GATT Server and Client. A device +may operate in multiple roles concurrently. This API is intended to match the low-level Bluetooth protocol and provide building-blocks for higher-level abstractions such as specific device types. @@ -28,15 +28,15 @@ Constructor Configuration ------------- -.. method:: BLE.active([active]) +.. method:: BLE.active([active], /) Optionally changes the active state of the BLE radio, and returns the current state. The radio must be made active before using any other methods on this class. -.. method:: BLE.config('param') - BLE.config(param=value, ...) +.. method:: BLE.config('param', /) + BLE.config(*, param=value, ...) Get or set configuration values of the BLE interface. To get a value the parameter name should be quoted as a string, and just one parameter is @@ -45,11 +45,22 @@ Configuration Currently supported values are: - - ``'mac'``: Returns the device MAC address. If a device has a fixed address - (e.g. PYBD) then it will be returned. Otherwise (e.g. ESP32) a random - address will be generated when the BLE interface is made active. - Note: on some ports, accessing this value requires that the interface is - active (so that the MAC address can be queried from the controller). + - ``'mac'``: The current address in use, depending on the current address mode. + This returns a tuple of ``(addr_type, addr)``. + + See :meth:`gatts_write ` for details about address type. + + This may only be queried while the interface is currently active. + + - ``'addr_mode'``: Sets the address mode. Values can be: + + * 0x00 - PUBLIC - Use the controller's public address. + * 0x01 - RANDOM - Use a generated static address. + * 0x02 - RPA - Use resolvable private addresses. + * 0x03 - NRPA - Use non-resolvable private addresses. + + By default the interface mode will use a PUBLIC address if available, otherwise + it will use a RANDOM address. - ``'gap_name'``: Get/set the GAP device name used by service 0x1800, characteristic 0x2a00. This can be set at any time and changed multiple @@ -59,27 +70,40 @@ Configuration incoming events. This buffer is global to the entire BLE driver and so handles incoming data for all events, including all characteristics. Increasing this allows better handling of bursty incoming data (for - example scan results) and the ability for a central role to receive - larger characteristic values. + example scan results) and the ability to receive larger characteristic values. + + - ``'mtu'``: Get/set the MTU that will be used during an MTU exchange. The + resulting MTU will be the minimum of this and the remote device's MTU. + MTU exchange will not happen automatically (unless the remote device initiates + it), and must be manually initiated with + :meth:`gattc_exchange_mtu`. + Use the ``_IRQ_MTU_EXCHANGED`` event to discover the MTU for a given connection. Event Handling -------------- -.. method:: BLE.irq(handler, trigger=0xffff) +.. method:: BLE.irq(handler, /) Registers a callback for events from the BLE stack. The *handler* takes two arguments, ``event`` (which will be one of the codes below) and ``data`` (which is an event-specific tuple of values). - The optional *trigger* parameter allows you to set a mask of events that - your program is interested in. The default is all events. + **Note:** As an optimisation to prevent unnecessary allocations, the ``addr``, + ``adv_data``, ``char_data``, ``notify_data``, and ``uuid`` entries in the + tuples are read-only memoryview instances pointing to ubluetooth's internal + ringbuffer, and are only valid during the invocation of the IRQ handler + function. If your program needs to save one of these values to access after + the IRQ handler has returned (e.g. by saving it in a class instance or global + variable), then it needs to take a copy of the data, either by using ``bytes()`` + or ``bluetooth.UUID()``, like this:: - Note: the ``addr``, ``adv_data``, ``char_data``, ``notify_data``, and - ``uuid`` entries in the tuples are - references to data managed by the :mod:`ubluetooth` module (i.e. the same - instance will be re-used across multiple calls to the event handler). If - your program wants to use this data outside of the handler, then it must - copy them first, e.g. by using ``bytes(addr)`` or ``bluetooth.UUID(uuid)``. + connected_addr = bytes(addr) # equivalently: adv_data, char_data, or notify_data + matched_uuid = bluetooth.UUID(uuid) + + For example, the IRQ handler for a scan result might inspect the ``adv_data`` + to decide if it's the correct device, and only then copy the address data to be + used elsewhere in the program. And to print data from within the IRQ handler, + ``print(bytes(addr))`` will be needed. An event handler showing all possible events:: @@ -91,17 +115,17 @@ Event Handling # A central has disconnected from this peripheral. conn_handle, addr_type, addr = data elif event == _IRQ_GATTS_WRITE: - # A central has written to this characteristic or descriptor. + # A client has written to this characteristic or descriptor. conn_handle, attr_handle = data elif event == _IRQ_GATTS_READ_REQUEST: - # A central has issued a read. Note: this is a hard IRQ. + # A client has issued a read. Note: this is a hard IRQ. # Return None to deny the read. # Note: This event is not supported on ESP32. conn_handle, attr_handle = data elif event == _IRQ_SCAN_RESULT: # A single scan result. addr_type, addr, adv_type, rssi, adv_data = data - elif event == _IRQ_SCAN_COMPLETE: + elif event == _IRQ_SCAN_DONE: # Scan duration finished or manually stopped. pass elif event == _IRQ_PERIPHERAL_CONNECT: @@ -113,43 +137,75 @@ Event Handling elif event == _IRQ_GATTC_SERVICE_RESULT: # Called for each service found by gattc_discover_services(). conn_handle, start_handle, end_handle, uuid = data + elif event == _IRQ_GATTC_SERVICE_DONE: + # Called once service discovery is complete. + # Note: Status will be zero on success, implementation-specific value otherwise. + conn_handle, status = data elif event == _IRQ_GATTC_CHARACTERISTIC_RESULT: # Called for each characteristic found by gattc_discover_services(). conn_handle, def_handle, value_handle, properties, uuid = data + elif event == _IRQ_GATTC_CHARACTERISTIC_DONE: + # Called once service discovery is complete. + # Note: Status will be zero on success, implementation-specific value otherwise. + conn_handle, status = data elif event == _IRQ_GATTC_DESCRIPTOR_RESULT: # Called for each descriptor found by gattc_discover_descriptors(). conn_handle, dsc_handle, uuid = data + elif event == _IRQ_GATTC_DESCRIPTOR_DONE: + # Called once service discovery is complete. + # Note: Status will be zero on success, implementation-specific value otherwise. + conn_handle, status = data elif event == _IRQ_GATTC_READ_RESULT: # A gattc_read() has completed. conn_handle, value_handle, char_data = data - elif event == _IRQ_GATTC_WRITE_STATUS: + elif event == _IRQ_GATTC_READ_DONE: + # A gattc_read() has completed. + # Note: The value_handle will be zero on btstack (but present on NimBLE). + # Note: Status will be zero on success, implementation-specific value otherwise. + conn_handle, value_handle, status = data + elif event == _IRQ_GATTC_WRITE_DONE: # A gattc_write() has completed. + # Note: The value_handle will be zero on btstack (but present on NimBLE). + # Note: Status will be zero on success, implementation-specific value otherwise. conn_handle, value_handle, status = data elif event == _IRQ_GATTC_NOTIFY: - # A peripheral has sent a notify request. + # A server has sent a notify request. conn_handle, value_handle, notify_data = data elif event == _IRQ_GATTC_INDICATE: - # A peripheral has sent an indicate request. + # A server has sent an indicate request. conn_handle, value_handle, notify_data = data + elif event == _IRQ_GATTS_INDICATE_DONE: + # A client has acknowledged the indication. + # Note: Status will be zero on successful acknowledgment, implementation-specific value otherwise. + conn_handle, value_handle, status = data + elif event == _IRQ_MTU_EXCHANGED: + # MTU exchange complete (either initiated by us or the remote device). + conn_handle, mtu = data The event codes are:: from micropython import const - _IRQ_CENTRAL_CONNECT = const(1 << 0) - _IRQ_CENTRAL_DISCONNECT = const(1 << 1) - _IRQ_GATTS_WRITE = const(1 << 2) - _IRQ_GATTS_READ_REQUEST = const(1 << 3) - _IRQ_SCAN_RESULT = const(1 << 4) - _IRQ_SCAN_COMPLETE = const(1 << 5) - _IRQ_PERIPHERAL_CONNECT = const(1 << 6) - _IRQ_PERIPHERAL_DISCONNECT = const(1 << 7) - _IRQ_GATTC_SERVICE_RESULT = const(1 << 8) - _IRQ_GATTC_CHARACTERISTIC_RESULT = const(1 << 9) - _IRQ_GATTC_DESCRIPTOR_RESULT = const(1 << 10) - _IRQ_GATTC_READ_RESULT = const(1 << 11) - _IRQ_GATTC_WRITE_STATUS = const(1 << 12) - _IRQ_GATTC_NOTIFY = const(1 << 13) - _IRQ_GATTC_INDICATE = const(1 << 14) + _IRQ_CENTRAL_CONNECT = const(1) + _IRQ_CENTRAL_DISCONNECT = const(2) + _IRQ_GATTS_WRITE = const(3) + _IRQ_GATTS_READ_REQUEST = const(4) + _IRQ_SCAN_RESULT = const(5) + _IRQ_SCAN_DONE = const(6) + _IRQ_PERIPHERAL_CONNECT = const(7) + _IRQ_PERIPHERAL_DISCONNECT = const(8) + _IRQ_GATTC_SERVICE_RESULT = const(9) + _IRQ_GATTC_SERVICE_DONE = const(10) + _IRQ_GATTC_CHARACTERISTIC_RESULT = const(11) + _IRQ_GATTC_CHARACTERISTIC_DONE = const(12) + _IRQ_GATTC_DESCRIPTOR_RESULT = const(13) + _IRQ_GATTC_DESCRIPTOR_DONE = const(14) + _IRQ_GATTC_READ_RESULT = const(15) + _IRQ_GATTC_READ_DONE = const(16) + _IRQ_GATTC_WRITE_DONE = const(17) + _IRQ_GATTC_NOTIFY = const(18) + _IRQ_GATTC_INDICATE = const(19) + _IRQ_GATTS_INDICATE_DONE = const(20) + _IRQ_MTU_EXCHANGED = const(21) In order to save space in the firmware, these constants are not included on the :mod:`ubluetooth` module. Add the ones that you need from the list above to your @@ -159,7 +215,7 @@ program. Broadcaster Role (Advertiser) ----------------------------- -.. method:: BLE.gap_advertise(interval_us, adv_data=None, resp_data=None, connectable=True) +.. method:: BLE.gap_advertise(interval_us, adv_data=None, *, resp_data=None, connectable=True) Starts advertising at the specified interval (in **micro**\ seconds). This interval will be rounded down to the nearest 625us. To stop advertising, set @@ -169,7 +225,7 @@ Broadcaster Role (Advertiser) protocol (e.g. ``bytes``, ``bytearray``, ``str``). *adv_data* is included in all broadcasts, and *resp_data* is send in reply to an active scan. - Note: if *adv_data* (or *resp_data*) is ``None``, then the data passed + **Note:** if *adv_data* (or *resp_data*) is ``None``, then the data passed to the previous call to ``gap_advertise`` will be re-used. This allows a broadcaster to resume advertising with just ``gap_advertise(interval_us)``. To clear the advertising payload pass an empty ``bytes``, i.e. ``b''``. @@ -178,7 +234,7 @@ Broadcaster Role (Advertiser) Observer Role (Scanner) ----------------------- -.. method:: BLE.gap_scan(duration_ms, [interval_us], [window_us]) +.. method:: BLE.gap_scan(duration_ms, interval_us=1280000, window_us=11250, active=False, /) Run a scan operation lasting for the specified duration (in **milli**\ seconds). @@ -193,8 +249,13 @@ Observer Role (Scanner) (background scanning). For each scan result the ``_IRQ_SCAN_RESULT`` event will be raised, with event - data ``(addr_type, addr, adv_type, rssi, adv_data)``. ``adv_type`` values correspond - to the Bluetooth Specification: + data ``(addr_type, addr, adv_type, rssi, adv_data)``. + + ``addr_type`` values indicate public or random addresses: + * 0x00 - PUBLIC + * 0x01 - RANDOM (either static, RPA, or NRPA, the type is encoded in the address itself) + + ``adv_type`` values correspond to the Bluetooth Specification: * 0x00 - ADV_IND - connectable and scannable undirected advertising * 0x01 - ADV_DIRECT_IND - connectable directed advertising @@ -202,32 +263,80 @@ Observer Role (Scanner) * 0x03 - ADV_NONCONN_IND - non-connectable undirected advertising * 0x04 - SCAN_RSP - scan response + ``active`` can be set ``True`` if you want to receive scan responses in the results. + When scanning is stopped (either due to the duration finishing or when - explicitly stopped), the ``_IRQ_SCAN_COMPLETE`` event will be raised. + explicitly stopped), the ``_IRQ_SCAN_DONE`` event will be raised. -Peripheral Role (GATT Server) ------------------------------ +Central Role +------------ + +A central device can connect to peripherals that it has discovered using the observer role (see :meth:`gap_scan`) or with a known address. + +.. method:: BLE.gap_connect(addr_type, addr, scan_duration_ms=2000, /) + + Connect to a peripheral. + + See :meth:`gap_scan ` for details about address types. + + On success, the ``_IRQ_PERIPHERAL_CONNECT`` event will be raised. + + +Peripheral Role +--------------- + +A peripheral device is expected to send connectable advertisements (see +:meth:`gap_advertise`). It will usually be acting as a GATT +server, having first registered services and characteristics using +:meth:`gatts_register_services`. + +When a central connects, the ``_IRQ_CENTRAL_CONNECT`` event will be raised. + + +Central & Peripheral Roles +-------------------------- + +.. method:: BLE.gap_disconnect(conn_handle, /) -A BLE peripheral has a set of registered services. Each service may contain + Disconnect the specified connection handle. This can either be a + central that has connected to this device (if acting as a peripheral) + or a peripheral that was previously connected to by this device (if acting + as a central). + + On success, the ``_IRQ_PERIPHERAL_DISCONNECT`` or ``_IRQ_CENTRAL_DISCONNECT`` + event will be raised. + + Returns ``False`` if the connection handle wasn't connected, and ``True`` + otherwise. + + +GATT Server +----------- + +A GATT server has a set of registered services. Each service may contain characteristics, which each have a value. Characteristics can also contain descriptors, which themselves have values. These values are stored locally, and are accessed by their "value handle" which is generated during service registration. They can also be read from or written -to by a remote central device. Additionally, a peripheral can "notify" a -characteristic to a connected central via a connection handle. +to by a remote client device. Additionally, a server can "notify" a +characteristic to a connected client via a connection handle. + +A device in either central or peripheral roles may function as a GATT server, +however in most cases it will be more common for a peripheral device to act +as the server. Characteristics and descriptors have a default maximum size of 20 bytes. -Anything written to them by a central will be truncated to this length. However, +Anything written to them by a client will be truncated to this length. However, any local write will increase the maximum size, so if you want to allow larger -writes from a central to a given characteristic, use +writes from a client to a given characteristic, use :meth:`gatts_write` after registration. e.g. ``gatts_write(char_handle, bytes(100))``. -.. method:: BLE.gatts_register_services(services_definition) +.. method:: BLE.gatts_register_services(services_definition, /) - Configures the peripheral with the specified services, replacing any + Configures the server with the specified services, replacing any existing services. *services_definition* is a list of **services**, where each **service** is a @@ -260,28 +369,40 @@ writes from a central to a given characteristic, use ( (hr,), (tx, rx,), ) = bt.gatts_register_services(SERVICES) The three value handles (``hr``, ``tx``, ``rx``) can be used with - :meth:`gatts_read `, :meth:`gatts_write `, - and :meth:`gatts_notify `. + :meth:`gatts_read `, :meth:`gatts_write `, :meth:`gatts_notify `, and + :meth:`gatts_indicate `. **Note:** Advertising must be stopped before registering services. -.. method:: BLE.gatts_read(value_handle) +.. method:: BLE.gatts_read(value_handle, /) Reads the local value for this handle (which has either been written by - :meth:`gatts_write ` or by a remote central). + :meth:`gatts_write ` or by a remote client). + +.. method:: BLE.gatts_write(value_handle, data, /) + + Writes the local value for this handle, which can be read by a client. + +.. method:: BLE.gatts_notify(conn_handle, value_handle, data=None, /) + + Sends a notification request to a connected client. + + If *data* is not ``None``, then that value is sent to the client as part of + the notification. The local value will not be modified. -.. method:: BLE.gatts_write(value_handle, data) + Otherwise, if *data* is ``None``, then the current local value (as + set with :meth:`gatts_write `) will be sent. - Writes the local value for this handle, which can be read by a central. +.. method:: BLE.gatts_indicate(conn_handle, value_handle, /) -.. method:: BLE.gatts_notify(conn_handle, value_handle, [data]) + Sends an indication request to a connected client. - Notifies a connected central that this value has changed and that it should - issue a read of the current value from this peripheral. + **Note:** This does not currently support sending a custom value, it will + always send the current local value (as set with :meth:`gatts_write + `). - If *data* is specified, then the that value is sent to the central as part - of the notification, avoiding the need for a separate read request. Note - that this will not update the local value stored. + On acknowledgment (or failure, e.g. timeout), the + ``_IRQ_GATTS_INDICATE_DONE`` event will be raised. .. method:: BLE.gatts_set_buffer(value_handle, len, append=False, /) @@ -294,70 +415,82 @@ writes from a central to a given characteristic, use be cleared after reading. This feature is useful when implementing something like the Nordic UART Service. +GATT Client +----------- -Central Role (GATT Client) --------------------------- - -.. method:: BLE.gap_connect(addr_type, addr, scan_duration_ms=2000, /) +A GATT client can discover and read/write characteristics on a remote GATT server. - Connect to a peripheral. +It is more common for a central role device to act as the GATT client, however +it's also possible for a peripheral to act as a client in order to discover +information about the central that has connected to it (e.g. to read the +device name from the device information service). - On success, the ``_IRQ_PERIPHERAL_CONNECT`` event will be raised. +.. method:: BLE.gattc_discover_services(conn_handle, uuid=None, /) -.. method:: BLE.gap_disconnect(conn_handle) + Query a connected server for its services. - Disconnect the specified connection handle. + Optionally specify a service *uuid* to query for that service only. - On success, the ``_IRQ_PERIPHERAL_DISCONNECT`` event will be raised. + For each service discovered, the ``_IRQ_GATTC_SERVICE_RESULT`` event will + be raised, followed by ``_IRQ_GATTC_SERVICE_DONE`` on completion. - Returns ``False`` if the connection handle wasn't connected, and ``True`` - otherwise. +.. method:: BLE.gattc_discover_characteristics(conn_handle, start_handle, end_handle, uuid=None, /) -.. method:: BLE.gattc_discover_services(conn_handle) + Query a connected server for characteristics in the specified range. - Query a connected peripheral for its services. + Optionally specify a characteristic *uuid* to query for that + characteristic only. - For each service discovered, the ``_IRQ_GATTC_SERVICE_RESULT`` event will be - raised. - -.. method:: BLE.gattc_discover_characteristics(conn_handle, start_handle, end_handle) - - Query a connected peripheral for characteristics in the specified range. + You can use ``start_handle=1``, ``end_handle=0xffff`` to search for a + characteristic in any service. For each characteristic discovered, the ``_IRQ_GATTC_CHARACTERISTIC_RESULT`` - event will be raised. + event will be raised, followed by ``_IRQ_GATTC_CHARACTERISTIC_DONE`` on completion. -.. method:: BLE.gattc_discover_descriptors(conn_handle, start_handle, end_handle) +.. method:: BLE.gattc_discover_descriptors(conn_handle, start_handle, end_handle, /) - Query a connected peripheral for descriptors in the specified range. + Query a connected server for descriptors in the specified range. For each descriptor discovered, the ``_IRQ_GATTC_DESCRIPTOR_RESULT`` event - will be raised. + will be raised, followed by ``_IRQ_GATTC_DESCRIPTOR_DONE`` on completion. -.. method:: BLE.gattc_read(conn_handle, value_handle) +.. method:: BLE.gattc_read(conn_handle, value_handle, /) - Issue a remote read to a connected peripheral for the specified + Issue a remote read to a connected server for the specified characteristic or descriptor handle. - On success, the ``_IRQ_GATTC_READ_RESULT`` event will be raised. + When a value is available, the ``_IRQ_GATTC_READ_RESULT`` event will be + raised. Additionally, the ``_IRQ_GATTC_READ_DONE`` will be raised. .. method:: BLE.gattc_write(conn_handle, value_handle, data, mode=0, /) - Issue a remote write to a connected peripheral for the specified + Issue a remote write to a connected server for the specified characteristic or descriptor handle. The argument *mode* specifies the write behaviour, with the currently supported values being: * ``mode=0`` (default) is a write-without-response: the write will - be sent to the remote peripheral but no confirmation will be + be sent to the remote server but no confirmation will be returned, and no event will be raised. - * ``mode=1`` is a write-with-response: the remote peripheral is + * ``mode=1`` is a write-with-response: the remote server is requested to send a response/acknowledgement that it received the data. - If a response is received from the remote peripheral the - ``_IRQ_GATTC_WRITE_STATUS`` event will be raised. + If a response is received from the remote server the + ``_IRQ_GATTC_WRITE_DONE`` event will be raised. + +.. method:: BLE.gattc_exchange_mtu(conn_handle, /) + + Initiate MTU exchange with a connected server, using the preferred MTU + set using ``BLE.config(mtu=value)``. + + The ``_IRQ_MTU_EXCHANGED`` event will be raised when MTU exchange + completes. + + **Note:** MTU exchange is typically initiated by the central. When using + the BlueKitchen stack in the central role, it does not support a remote + peripheral initiating the MTU exchange. NimBLE works for both roles. class UUID @@ -367,7 +500,7 @@ class UUID Constructor ----------- -.. class:: UUID(value) +.. class:: UUID(value, /) Creates a UUID instance with the specified **value**. diff --git a/docs/library/uerrno.rst b/docs/library/uerrno.rst index e336eb5c5c85c..def01362f1387 100644 --- a/docs/library/uerrno.rst +++ b/docs/library/uerrno.rst @@ -7,7 +7,7 @@ |see_cpython_module| :mod:`python:errno`. This module provides access to symbolic error codes for `OSError` exception. -A particular inventory of codes depends on `MicroPython port`. +A particular inventory of codes depends on :term:`MicroPython port`. Constants --------- @@ -16,7 +16,7 @@ Constants Error codes, based on ANSI C/POSIX standard. All error codes start with "E". As mentioned above, inventory of the codes depends on - `MicroPython port`. Errors are usually accessible as ``exc.args[0]`` + :term:`MicroPython port`. Errors are usually accessible as ``exc.args[0]`` where ``exc`` is an instance of `OSError`. Usage example:: try: diff --git a/docs/library/uio.rst b/docs/library/uio.rst index 1a64b36582e52..dddb83a170750 100644 --- a/docs/library/uio.rst +++ b/docs/library/uio.rst @@ -114,7 +114,9 @@ Classes Get the current contents of the underlying buffer which holds data. .. class:: StringIO(alloc_size) + :noindex: .. class:: BytesIO(alloc_size) + :noindex: Create an empty `StringIO`/`BytesIO` object, preallocated to hold up to *alloc_size* number of bytes. That means that writing that amount diff --git a/docs/library/uos.rst b/docs/library/uos.rst index c49b13a5f5bca..edc94556b1722 100644 --- a/docs/library/uos.rst +++ b/docs/library/uos.rst @@ -144,7 +144,7 @@ programs. Ports that have this functionality provide the :func:`mount` and :func:`umount` functions, and possibly various filesystem implementations represented by VFS classes. -.. function:: mount(fsobj, mount_point, \*, readonly) +.. function:: mount(fsobj, mount_point, *, readonly) Mount the filesystem object *fsobj* at the location in the VFS given by the *mount_point* string. *fsobj* can be a a VFS object that has a ``mount()`` @@ -178,7 +178,7 @@ represented by VFS classes. Build a FAT filesystem on *block_dev*. -.. class:: VfsLfs1(block_dev) +.. class:: VfsLfs1(block_dev, readsize=32, progsize=32, lookahead=32) Create a filesystem object that uses the `littlefs v1 filesystem format`_. Storage of the littlefs filesystem is provided by *block_dev*, which must @@ -187,23 +187,31 @@ represented by VFS classes. See :ref:`filesystem` for more information. - .. staticmethod:: mkfs(block_dev) + .. staticmethod:: mkfs(block_dev, readsize=32, progsize=32, lookahead=32) Build a Lfs1 filesystem on *block_dev*. .. note:: There are reports of littlefs v1 failing in certain situations, for details see `littlefs issue 347`_. -.. class:: VfsLfs2(block_dev) +.. class:: VfsLfs2(block_dev, readsize=32, progsize=32, lookahead=32, mtime=True) Create a filesystem object that uses the `littlefs v2 filesystem format`_. Storage of the littlefs filesystem is provided by *block_dev*, which must support the :ref:`extended interface `. Objects created by this constructor can be mounted using :func:`mount`. + The *mtime* argument enables modification timestamps for files, stored using + littlefs attributes. This option can be disabled or enabled differently each + mount time and timestamps will only be added or updated if *mtime* is enabled, + otherwise the timestamps will remain untouched. Littlefs v2 filesystems without + timestamps will work without reformatting and timestamps will be added + transparently to existing files once they are opened for writing. When *mtime* + is enabled `uos.stat` on files without timestamps will return 0 for the timestamp. + See :ref:`filesystem` for more information. - .. staticmethod:: mkfs(block_dev) + .. staticmethod:: mkfs(block_dev, readsize=32, progsize=32, lookahead=32) Build a Lfs2 filesystem on *block_dev*. @@ -252,7 +260,7 @@ that the block device supports the extended interface. dependent on the specific block device. .. method:: readblocks(block_num, buf) - .. method:: readblocks(block_num, buf, offset) + readblocks(block_num, buf, offset) The first form reads aligned, multiples of blocks. Starting at the block given by the index *block_num*, read blocks from @@ -267,7 +275,7 @@ that the block device supports the extended interface. The number of bytes to read is given by the length of *buf*. .. method:: writeblocks(block_num, buf) - .. method:: writeblocks(block_num, buf, offset) + writeblocks(block_num, buf, offset) The first form writes aligned, multiples of blocks, and requires that the blocks that are written to be first erased (if necessary) by this method. diff --git a/docs/library/ure.rst b/docs/library/ure.rst index ca5f35b70302c..e94d286175cef 100644 --- a/docs/library/ure.rst +++ b/docs/library/ure.rst @@ -138,12 +138,12 @@ Functions If *count* is specified and non-zero then substitution will stop after this many substitutions are made. The *flags* argument is ignored. - Note: availability of this function depends on `MicroPython port`. + Note: availability of this function depends on :term:`MicroPython port`. .. data:: DEBUG Flag value, display debug information about compiled expression. - (Availability depends on `MicroPython port`.) + (Availability depends on :term:`MicroPython port`.) .. _regex: @@ -184,7 +184,7 @@ to the replacement function in `sub()`. Return a tuple containing all the substrings of the groups of the match. - Note: availability of this method depends on `MicroPython port`. + Note: availability of this method depends on :term:`MicroPython port`. .. method:: match.start([index]) match.end([index]) @@ -193,10 +193,10 @@ to the replacement function in `sub()`. substring group that was matched. *index* defaults to the entire group, otherwise it will select a group. - Note: availability of these methods depends on `MicroPython port`. + Note: availability of these methods depends on :term:`MicroPython port`. .. method:: match.span([index]) Returns the 2-tuple ``(match.start(index), match.end(index))``. - Note: availability of this method depends on `MicroPython port`. + Note: availability of this method depends on :term:`MicroPython port`. diff --git a/docs/library/usocket.rst b/docs/library/usocket.rst index e3b9d279a051e..bc4b4b6d5a952 100644 --- a/docs/library/usocket.rst +++ b/docs/library/usocket.rst @@ -37,8 +37,8 @@ power) and portable way to work with addresses. However, ``socket`` module (note the difference with native MicroPython ``usocket`` module described here) provides CPython-compatible way to specify addresses using tuples, as described below. Note that depending on a -`MicroPython port`, ``socket`` module can be builtin or need to be -installed from `micropython-lib` (as in the case of `MicroPython Unix port`), +:term:`MicroPython port`, ``socket`` module can be builtin or need to be +installed from `micropython-lib` (as in the case of :term:`MicroPython Unix port`), and some ports still accept only numeric addresses in the tuple format, and require to use `getaddrinfo` function to resolve domain names. @@ -61,7 +61,7 @@ Tuple address format for ``socket`` module: must be 0. *scopeid* is the interface scope identifier for link-local addresses. Note the domain names are not accepted as *ipv6_address*, they should be resolved first using `usocket.getaddrinfo()`. Availability - of IPv6 support depends on a `MicroPython port`. + of IPv6 support depends on a :term:`MicroPython port`. Functions --------- @@ -81,7 +81,7 @@ Functions .. function:: getaddrinfo(host, port, af=0, type=0, proto=0, flags=0, /) - Translate the host/port argument into a sequence of 5-tuples that contain all the + Translate the host/port argument into a sequence of 5-tuples that contain all the necessary arguments for creating a socket connected to that service. Arguments *af*, *type*, and *proto* (which have the same meaning as for the `socket()` function) can be used to filter which kind of addresses are returned. If a parameter is not @@ -141,7 +141,7 @@ Constants .. data:: AF_INET AF_INET6 - Address family types. Availability depends on a particular `MicroPython port`. + Address family types. Availability depends on a particular :term:`MicroPython port`. .. data:: SOCK_STREAM SOCK_DGRAM @@ -151,7 +151,7 @@ Constants .. data:: IPPROTO_UDP IPPROTO_TCP - IP protocol numbers. Availability depends on a particular `MicroPython port`. + IP protocol numbers. Availability depends on a particular :term:`MicroPython port`. Note that you don't need to specify these in a call to `usocket.socket()`, because `SOCK_STREAM` socket type automatically selects `IPPROTO_TCP`, and `SOCK_DGRAM` - `IPPROTO_UDP`. Thus, the only real use of these constants @@ -160,12 +160,12 @@ Constants .. data:: usocket.SOL_* Socket option levels (an argument to `setsockopt()`). The exact - inventory depends on a `MicroPython port`. + inventory depends on a :term:`MicroPython port`. .. data:: usocket.SO_* Socket options (an argument to `setsockopt()`). The exact - inventory depends on a `MicroPython port`. + inventory depends on a :term:`MicroPython port`. Constants specific to WiPy: @@ -185,7 +185,7 @@ Methods on the socket object will fail. The remote end will receive EOF indication if supported by protocol. - Sockets are automatically closed when they are garbage-collected, but it is recommended + Sockets are automatically closed when they are garbage-collected, but it is recommended to `close()` them explicitly as soon you finished working with them. .. method:: socket.bind(address) @@ -259,7 +259,7 @@ Methods completed. If zero is given, the socket is put in non-blocking mode. If None is given, the socket is put in blocking mode. - Not every `MicroPython port` supports this method. A more portable and + Not every :term:`MicroPython port` supports this method. A more portable and generic solution is to use `uselect.poll` object. This allows to wait on multiple objects at the same time (and not just on sockets, but on generic `stream` objects which support polling). Example:: diff --git a/docs/library/ussl.rst b/docs/library/ussl.rst index be84dc0545003..ffe146331ca08 100644 --- a/docs/library/ussl.rst +++ b/docs/library/ussl.rst @@ -24,7 +24,7 @@ Functions :meth:`~usocket.socket.accept()` on a non-SSL listening server socket. Depending on the underlying module implementation in a particular - `MicroPython port`, some or all keyword arguments above may be not supported. + :term:`MicroPython port`, some or all keyword arguments above may be not supported. .. warning:: diff --git a/docs/library/sys.rst b/docs/library/usys.rst similarity index 93% rename from docs/library/sys.rst rename to docs/library/usys.rst index 24f9e353bb006..96016438507cf 100644 --- a/docs/library/sys.rst +++ b/docs/library/usys.rst @@ -1,7 +1,7 @@ -:mod:`sys` -- system specific functions -======================================= +:mod:`usys` -- system specific functions +======================================== -.. module:: sys +.. module:: usys :synopsis: system specific functions |see_cpython_module| :mod:`python:sys`. @@ -28,10 +28,10 @@ Functions This function is a MicroPython extension intended to provide similar functionality to the :mod:`atexit` module in CPython. -.. function:: print_exception(exc, file=sys.stdout, /) +.. function:: print_exception(exc, file=usys.stdout, /) Print exception with a traceback to a file-like object *file* (or - `sys.stdout` by default). + `usys.stdout` by default). .. admonition:: Difference to CPython :class: attention @@ -84,7 +84,7 @@ Constants value directly, but instead count number of bits in it:: bits = 0 - v = sys.maxsize + v = usys.maxsize while v: bits += 1 v >>= 1 @@ -113,7 +113,7 @@ Constants is an identifier of a board, e.g. ``"pyboard"`` for the original MicroPython reference board. It thus can be used to distinguish one board from another. If you need to check whether your program runs on MicroPython (vs other - Python implementation), use `sys.implementation` instead. + Python implementation), use `usys.implementation` instead. .. data:: stderr diff --git a/docs/library/utime.rst b/docs/library/utime.rst index 7fe83f5abe0cd..86fd27b3a5ac8 100644 --- a/docs/library/utime.rst +++ b/docs/library/utime.rst @@ -36,11 +36,17 @@ behave not as expected. Functions --------- -.. function:: localtime([secs]) +.. function:: gmtime([secs]) + localtime([secs]) - Convert a time expressed in seconds since the Epoch (see above) into an 8-tuple which - contains: (year, month, mday, hour, minute, second, weekday, yearday) - If secs is not provided or None, then the current time from the RTC is used. + Convert the time *secs* expressed in seconds since the Epoch (see above) into an + 8-tuple which contains: ``(year, month, mday, hour, minute, second, weekday, yearday)`` + If *secs* is not provided or None, then the current time from the RTC is used. + + The `gmtime()` function returns a date-time tuple in UTC, and `localtime()` returns a + date-time tuple in local time. + + The format of the entries in the 8-tuple are: * year includes the century (for example 2014). * month is 1-12 @@ -210,8 +216,9 @@ Functions function returns number of seconds since a port-specific reference point in time (for embedded boards without a battery-backed RTC, usually since power up or reset). If you want to develop portable MicroPython application, you should not rely on this function - to provide higher than second precision. If you need higher precision, use - `ticks_ms()` and `ticks_us()` functions, if you need calendar time, + to provide higher than second precision. If you need higher precision, absolute + timestamps, use `time_ns()`. If relative times are acceptable then use the + `ticks_ms()` and `ticks_us()` functions. If you need calendar time, `gmtime()` or `localtime()` without an argument is a better choice. .. admonition:: Difference to CPython @@ -227,3 +234,8 @@ Functions hardware also lacks battery-powered RTC, so returns number of seconds since last power-up or from other relative, hardware-specific point (e.g. reset). + +.. function:: time_ns() + + Similar to `time()` but returns nanoseconds since the Epoch, as an integer (usually + a big integer, so will allocate on the heap). diff --git a/docs/make.bat b/docs/make.bat index 44f9682790ef5..c09487fb741ef 100644 --- a/docs/make.bat +++ b/docs/make.bat @@ -6,6 +6,7 @@ if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build +set SPHINXOPTS=-W --keep-going set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( diff --git a/docs/reference/isr_rules.rst b/docs/reference/isr_rules.rst index 57690ac212c64..7f466ab42f41c 100644 --- a/docs/reference/isr_rules.rst +++ b/docs/reference/isr_rules.rst @@ -43,6 +43,11 @@ for the purpose. Debugging is simplified if the following code is included in an import micropython micropython.alloc_emergency_exception_buf(100) +The emergency exception buffer can only hold one exception stack trace. This means that if a second exception is +thrown during the handling of an exception while the heap is locked, that second exception's stack trace will +replace the original one - even if the second exception is cleanly handled. This can lead to confusing exception +messages if the buffer is later printed. + Simplicity ~~~~~~~~~~ diff --git a/docs/reference/packages.rst b/docs/reference/packages.rst index 43217493e52a4..e9adfc176ea73 100644 --- a/docs/reference/packages.rst +++ b/docs/reference/packages.rst @@ -14,8 +14,8 @@ packages: 1. Python modules and packages are turned into distribution package archives, and published at the Python Package Index (PyPI). -2. `upip` package manager can be used to install a distribution package - on a `MicroPython port` with networking capabilities (for example, +2. :term:`upip` package manager can be used to install a distribution package + on a :term:`MicroPython port` with networking capabilities (for example, on the Unix port). 3. For ports without networking capabilities, an "installation image" can be prepared on the Unix port, and transferred to a device by @@ -51,14 +51,14 @@ even by the smallest devices. Besides the small compression dictionary size, MicroPython distribution packages also have other optimizations, like removing any files from the archive which aren't used by the installation process. In particular, -`upip` package manager doesn't execute ``setup.py`` during installation +:term:`upip` package manager doesn't execute ``setup.py`` during installation (see below), and thus that file is not included in the archive. At the same time, these optimizations make MicroPython distribution -packages not compatible with `CPython`'s package manager, ``pip``. +packages not compatible with :term:`CPython`'s package manager, ``pip``. This isn't considered a big problem, because: -1. Packages can be installed with `upip`, and then can be used with +1. Packages can be installed with :term:`upip`, and then can be used with CPython (if they are compatible with it). 2. In the other direction, majority of CPython packages would be incompatible with MicroPython by various reasons, first of all, @@ -73,12 +73,12 @@ resource constrained devices. ------------------------ MicroPython distribution packages are intended to be installed using -the `upip` package manager. `upip` is a Python application which is +the :term:`upip` package manager. :term:`upip` is a Python application which is usually distributed (as frozen bytecode) with network-enabled -`MicroPython ports `. At the very least, -`upip` is available in the `MicroPython Unix port`. +:term:`MicroPython ports `. At the very least, +:term:`upip` is available in the :term:`MicroPython Unix port`. -On any `MicroPython port` providing `upip`, it can be accessed as +On any :term:`MicroPython port` providing :term:`upip`, it can be accessed as following:: import upip @@ -123,12 +123,12 @@ commands which corresponds to the example above are:: Cross-installing packages ------------------------- -For `MicroPython ports ` without native networking +For :term:`MicroPython ports ` without native networking capabilities, the recommend process is "cross-installing" them into a -"directory image" using the `MicroPython Unix port`, and then +"directory image" using the :term:`MicroPython Unix port`, and then transferring this image to a device by suitable means. -Installing to a directory image involves using ``-p`` switch to `upip`:: +Installing to a directory image involves using ``-p`` switch to :term:`upip`:: micropython -m upip install -p install_dir micropython-pystone_lowmem @@ -137,13 +137,13 @@ packages) will be available in the ``install_dir/`` subdirectory. You would need to transfer contents of this directory (without the ``install_dir/`` prefix) to the device, at the suitable location, where it can be found by the Python ``import`` statement (see discussion of -the `upip` installation path above). +the :term:`upip` installation path above). Cross-installing packages with freezing --------------------------------------- -For the low-memory `MicroPython ports `, the process +For the low-memory :term:`MicroPython ports `, the process described in the previous section does not provide the most efficient resource usage,because the packages are installed in the source form, so need to be compiled to the bytecome on each import. This compilation @@ -160,7 +160,7 @@ mentioned above: * Filesystem is not required for frozen packages. Using frozen bytecode requires building the executable (firmware) -for a given `MicroPython port` from the C source code. Consequently, +for a given :term:`MicroPython port` from the C source code. Consequently, the process is: 1. Follow the instructions for a particular port on setting up a @@ -168,7 +168,7 @@ the process is: study instructions in ``ports/esp8266/README.md`` and follow them. Make sure you can build the port and deploy the resulting executable/firmware successfully before proceeding to the next steps. -2. Build `MicroPython Unix port` and make sure it is in your PATH and +2. Build :term:`MicroPython Unix port` and make sure it is in your PATH and you can execute ``micropython``. 3. Change to port's directory (e.g. ``ports/esp8266/`` for ESP8266). 4. Run ``make clean-frozen``. This step cleans up any previous @@ -188,7 +188,7 @@ Few notes: 1. Step 5 in the sequence above assumes that the distribution package is available from PyPI. If that is not the case, you would need to copy Python source files manually to ``modules/`` subdirectory - of the port port directory. (Note that upip does not support + of the port directory. (Note that upip does not support installing from e.g. version control repositories). 2. The firmware for baremetal devices usually has size restrictions, so adding too many frozen modules may overflow it. Usually, you @@ -281,7 +281,7 @@ following calls:: pkg_resources.resource_stream(__name__, "data/page.html") pkg_resources.resource_stream(__name__, "data/image.png") -You can develop and debug using the `MicroPython Unix port` as usual. +You can develop and debug using the :term:`MicroPython Unix port` as usual. When time comes to make a distribution package out of it, just use overridden "sdist" command from sdist_upip.py module as described in the previous section. diff --git a/docs/reference/pyboard.py.rst b/docs/reference/pyboard.py.rst index d404c738f1d26..30230eebc3bf4 100644 --- a/docs/reference/pyboard.py.rst +++ b/docs/reference/pyboard.py.rst @@ -68,7 +68,7 @@ example:: $ pyboard.py -c 'print(1+1)' Similarly, the ``PYBOARD_BAUDRATE`` environment variable can be used -to set the default for the `--baudrate` option. +to set the default for the ``--baudrate`` option. Running a script on the device ------------------------------ diff --git a/docs/templates/replace.inc b/docs/templates/replace.inc index 319c53735fc28..14f1875eeec6a 100644 --- a/docs/templates/replace.inc +++ b/docs/templates/replace.inc @@ -4,6 +4,6 @@ .. |see_cpython_module| replace:: - *This module implements a subset of the corresponding* `CPython` *module, + *This module implements a subset of the corresponding* :term:`CPython` *module, as described below. For more information, refer to the original CPython documentation:* diff --git a/drivers/cyw43/cywbt.c b/drivers/cyw43/cywbt.c index 79318f4483ba4..defe670683ffd 100644 --- a/drivers/cyw43/cywbt.c +++ b/drivers/cyw43/cywbt.c @@ -31,13 +31,19 @@ #include "py/mphal.h" #include "pin_static_af.h" #include "uart.h" -#include "extmod/modbluetooth_hci.h" +#include "extmod/mpbthci.h" #if MICROPY_PY_NETWORK_CYW43 extern const char fw_4343WA1_7_45_98_50_start; #define CYWBT_FW_ADDR (&fw_4343WA1_7_45_98_50_start + 749 * 512 + 29 * 256) +// Provided by the port. +extern pyb_uart_obj_t mp_bluetooth_hci_uart_obj; + +// Provided by the port, and also possibly shared with the stack. +extern uint8_t mp_bluetooth_hci_cmd_buf[4 + 256]; + /******************************************************************************/ // CYW BT HCI low-level driver @@ -168,10 +174,6 @@ int mp_bluetooth_hci_controller_init(void) { mp_hal_pin_config(pyb_pin_WL_GPIO_4, MP_HAL_PIN_MODE_OUTPUT, MP_HAL_PIN_PULL_NONE, 0); // RF-switch power mp_hal_pin_high(pyb_pin_WL_GPIO_4); // Turn the RF-switch on - return 0; -} - -int mp_bluetooth_hci_controller_activate(void) { uint8_t buf[256]; mp_hal_pin_low(pyb_pin_BT_REG_ON); @@ -219,7 +221,7 @@ int mp_bluetooth_hci_controller_activate(void) { return 0; } -int mp_bluetooth_hci_controller_deactivate(void) { +int mp_bluetooth_hci_controller_deinit(void) { mp_hal_pin_low(pyb_pin_BT_REG_ON); return 0; diff --git a/drivers/nrf24l01/nrf24l01test.py b/drivers/nrf24l01/nrf24l01test.py index 14efbffd2aba1..56bdb6e26eb9c 100644 --- a/drivers/nrf24l01/nrf24l01test.py +++ b/drivers/nrf24l01/nrf24l01test.py @@ -1,6 +1,6 @@ """Test for nrf24l01 module. Portable between MicroPython targets.""" -import sys +import usys import ustruct as struct import utime from machine import Pin, SPI @@ -14,14 +14,14 @@ # master may be a slow device. Value tested with Pyboard, ESP32 and ESP8266. _SLAVE_SEND_DELAY = const(10) -if sys.platform == "pyboard": +if usys.platform == "pyboard": cfg = {"spi": 2, "miso": "Y7", "mosi": "Y8", "sck": "Y6", "csn": "Y5", "ce": "Y4"} -elif sys.platform == "esp8266": # Hardware SPI +elif usys.platform == "esp8266": # Hardware SPI cfg = {"spi": 1, "miso": 12, "mosi": 13, "sck": 14, "csn": 4, "ce": 5} -elif sys.platform == "esp32": # Software SPI +elif usys.platform == "esp32": # Software SPI cfg = {"spi": -1, "miso": 32, "mosi": 33, "sck": 25, "csn": 26, "ce": 27} else: - raise ValueError("Unsupported platform {}".format(sys.platform)) + raise ValueError("Unsupported platform {}".format(usys.platform)) # Addresses are in little-endian format. They correspond to big-endian # 0xf0f0f0f0e1, 0xf0f0f0f0d2 diff --git a/examples/bluetooth/ble_advertising.py b/examples/bluetooth/ble_advertising.py index 3fb1281f636ee..eed527f55d210 100644 --- a/examples/bluetooth/ble_advertising.py +++ b/examples/bluetooth/ble_advertising.py @@ -30,7 +30,7 @@ def _append(adv_type, value): _append( _ADV_TYPE_FLAGS, - struct.pack("B", (0x01 if limited_disc else 0x02) + (0x00 if br_edr else 0x04)), + struct.pack("B", (0x01 if limited_disc else 0x02) + (0x18 if br_edr else 0x04)), ) if name: @@ -47,7 +47,8 @@ def _append(adv_type, value): _append(_ADV_TYPE_UUID128_COMPLETE, b) # See org.bluetooth.characteristic.gap.appearance.xml - _append(_ADV_TYPE_APPEARANCE, struct.pack(" 0 + + def _advertise(self, interval_us=500000): + print("Starting advertising") + self._ble.gap_advertise(interval_us, adv_data=self._payload) + + def on_write(self, callback): + self._write_callback = callback + + +def demo(): + ble = bluetooth.BLE() + p = BLESimplePeripheral(ble) + + def on_rx(v): + print("RX", v) + + p.on_write(on_rx) + + i = 0 + while True: + if p.is_connected(): + # Short burst of queued notifications. + for _ in range(3): + data = str(i) + "_" + print("TX", data) + p.send(data) + i += 1 + time.sleep_ms(100) + + +if __name__ == "__main__": + demo() diff --git a/examples/bluetooth/ble_temperature.py b/examples/bluetooth/ble_temperature.py index 01d2f7441fa17..d375a62ffb8ac 100644 --- a/examples/bluetooth/ble_temperature.py +++ b/examples/bluetooth/ble_temperature.py @@ -11,15 +11,16 @@ from micropython import const -_IRQ_CENTRAL_CONNECT = const(1 << 0) -_IRQ_CENTRAL_DISCONNECT = const(1 << 1) +_IRQ_CENTRAL_CONNECT = const(1) +_IRQ_CENTRAL_DISCONNECT = const(2) +_IRQ_GATTS_INDICATE_DONE = const(20) # org.bluetooth.service.environmental_sensing _ENV_SENSE_UUID = bluetooth.UUID(0x181A) # org.bluetooth.characteristic.temperature _TEMP_CHAR = ( bluetooth.UUID(0x2A6E), - bluetooth.FLAG_READ | bluetooth.FLAG_NOTIFY, + bluetooth.FLAG_READ | bluetooth.FLAG_NOTIFY | bluetooth.FLAG_INDICATE, ) _ENV_SENSE_SERVICE = ( _ENV_SENSE_UUID, @@ -34,7 +35,7 @@ class BLETemperature: def __init__(self, ble, name="mpy-temp"): self._ble = ble self._ble.active(True) - self._ble.irq(handler=self._irq) + self._ble.irq(self._irq) ((self._handle,),) = self._ble.gatts_register_services((_ENV_SENSE_SERVICE,)) self._connections = set() self._payload = advertising_payload( @@ -45,22 +46,28 @@ def __init__(self, ble, name="mpy-temp"): def _irq(self, event, data): # Track connections so we can send notifications. if event == _IRQ_CENTRAL_CONNECT: - conn_handle, _, _, = data + conn_handle, _, _ = data self._connections.add(conn_handle) elif event == _IRQ_CENTRAL_DISCONNECT: - conn_handle, _, _, = data + conn_handle, _, _ = data self._connections.remove(conn_handle) # Start advertising again to allow a new connection. self._advertise() + elif event == _IRQ_GATTS_INDICATE_DONE: + conn_handle, value_handle, status = data - def set_temperature(self, temp_deg_c, notify=False): + def set_temperature(self, temp_deg_c, notify=False, indicate=False): # Data is sint16 in degrees Celsius with a resolution of 0.01 degrees Celsius. # Write the local value, ready for a central to read. self._ble.gatts_write(self._handle, struct.pack("init(); - } - - // hci_dump_open(NULL, HCI_DUMP_STDOUT); - const hci_transport_t *transport = hci_transport_h4_instance(&btstack_uart_block); - hci_init(transport, &hci_transport_config_uart); - - // TODO: Probably not necessary for BCM (we have our own firmware loader), - // but might be worth investigating for other controllers in the future. - // hci_set_chipset(btstack_chipset_bcm_instance()); -} - -void mp_bluetooth_btstack_port_deinit(void) { - hci_power_control(HCI_POWER_OFF); -} - -void mp_bluetooth_btstack_port_start(void) { - hci_power_control(HCI_POWER_ON); } #endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK diff --git a/extmod/nimble/nimble/nimble_hci_uart.h b/extmod/btstack/btstack_hci_uart.h similarity index 66% rename from extmod/nimble/nimble/nimble_hci_uart.h rename to extmod/btstack/btstack_hci_uart.h index 646a12dac1460..8011e587dee3f 100644 --- a/extmod/nimble/nimble/nimble_hci_uart.h +++ b/extmod/btstack/btstack_hci_uart.h @@ -3,7 +3,8 @@ * * The MIT License (MIT) * - * Copyright (c) 2019 Damien P. George + * Copyright (c) 2020 Damien P. George + * Copyright (c) 2020 Jim Mussared * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,18 +24,16 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -#ifndef MICROPY_INCLUDED_EXTMOD_NIMBLE_NIMBLE_NIMBLE_HCI_UART_H -#define MICROPY_INCLUDED_EXTMOD_NIMBLE_NIMBLE_NIMBLE_HCI_UART_H -// Extensions to extmod/modbluetooth_hci.h specific to NimBLE. +#ifndef MICROPY_INCLUDED_EXTMOD_BTSTACK_HCI_UART_H +#define MICROPY_INCLUDED_EXTMOD_BTSTACK_HCI_UART_H -#include "extmod/nimble/hal/hal_uart.h" +#include "lib/btstack/src/btstack.h" -// Helpers called from ports. -void mp_bluetooth_nimble_hci_uart_process(void); +// --- Used by the port to create the HCI transport --------------------------- +extern const btstack_uart_block_t mp_bluetooth_btstack_hci_uart_block; -// Must be provided by the port. -void mp_bluetooth_nimble_hci_uart_rx(hal_uart_rx_cb_t rx_cb, void *rx_arg); -void mp_bluetooth_nimble_hci_uart_tx_strn(const char *str, uint len); +// --- Called by the MicroPython port when UART data is available ------------- +void mp_bluetooth_btstack_hci_uart_process(void); -#endif // MICROPY_INCLUDED_EXTMOD_NIMBLE_NIMBLE_NIMBLE_HCI_UART_H +#endif // MICROPY_INCLUDED_EXTMOD_BTSTACK_MODBLUETOOTH_BTSTACK_H diff --git a/extmod/btstack/modbluetooth_btstack.c b/extmod/btstack/modbluetooth_btstack.c index 772fe4bed1054..ae96035868877 100644 --- a/extmod/btstack/modbluetooth_btstack.c +++ b/extmod/btstack/modbluetooth_btstack.c @@ -35,7 +35,7 @@ #include "lib/btstack/src/btstack.h" -#define DEBUG_EVENT_printf(...) //printf(__VA_ARGS__) +#define DEBUG_printf(...) // printf("btstack: " __VA_ARGS__) #ifndef MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME #define MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME "MPY BTSTACK" @@ -53,18 +53,26 @@ STATIC const uint16_t BTSTACK_GAP_DEVICE_NAME_HANDLE = 3; volatile int mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_OFF; -STATIC btstack_packet_callback_registration_t hci_event_callback_registration; +#define ERRNO_BLUETOOTH_NOT_ACTIVE MP_ENODEV STATIC int btstack_error_to_errno(int err) { + DEBUG_printf(" --> btstack error: %d\n", err); if (err == ERROR_CODE_SUCCESS) { return 0; - } else if (err == BTSTACK_ACL_BUFFERS_FULL) { + } else if (err == BTSTACK_ACL_BUFFERS_FULL || err == BTSTACK_MEMORY_ALLOC_FAILED) { return MP_ENOMEM; + } else if (err == GATT_CLIENT_IN_WRONG_STATE) { + return MP_EALREADY; + } else if (err == GATT_CLIENT_BUSY) { + return MP_EBUSY; + } else if (err == GATT_CLIENT_NOT_CONNECTED) { + return MP_ENOTCONN; } else { return MP_EINVAL; } } +#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE STATIC mp_obj_bluetooth_uuid_t create_mp_uuid(uint16_t uuid16, const uint8_t *uuid128) { mp_obj_bluetooth_uuid_t result; if (uuid16 != 0) { @@ -77,22 +85,215 @@ STATIC mp_obj_bluetooth_uuid_t create_mp_uuid(uint16_t uuid16, const uint8_t *uu } return result; } +#endif + +// Notes on supporting background ops (e.g. an attempt to gatts_notify while +// an existing notification is in progress): + +// GATTS Notify/Indicate (att_server_notify/indicate) +// * When available, copies buffer immediately. +// * Otherwise fails with BTSTACK_ACL_BUFFERS_FULL +// * Use att_server_request_to_send_notification/indication to get callback +// * Takes btstack_context_callback_registration_t (and takes ownership) and conn_handle. +// * Callback is invoked with just the context member of the btstack_context_callback_registration_t + +// GATTC Write without response (gatt_client_write_value_of_characteristic_without_response) +// * When available, copies buffer immediately. +// * Otherwise, fails with GATT_CLIENT_BUSY. +// * Use gatt_client_request_can_write_without_response_event to get callback +// * Takes btstack_packet_handler_t (function pointer) and conn_handle +// * Callback is invoked, use gatt_event_can_write_without_response_get_handle to get the conn_handle (no other context) +// * There can only be one pending gatt_client_request_can_write_without_response_event (otherwise we fail with EALREADY). + +// GATTC Write with response (gatt_client_write_value_of_characteristic) +// * When peripheral is available, takes ownership of buffer. +// * Otherwise, fails with GATT_CLIENT_IN_WRONG_STATE (we fail the operation). +// * Raises GATT_EVENT_QUERY_COMPLETE to the supplied packet handler. + +// For notify/indicate/write-without-response that proceed immediately, nothing extra required. +// For all other cases, buffer needs to be copied and protected from GC. +// For notify/indicate: +// * btstack_context_callback_registration_t: +// * needs to be malloc'ed +// * needs to be protected from GC +// * context arg needs to point back to the callback registration so it can be freed and un-protected +// For write-without-response +// * only the conn_handle is available in the callback +// * so we need a queue of conn_handle->(value_handle, copied buffer) + +// Pending operation types. +enum { + // Queued for sending when possible. + MP_BLUETOOTH_BTSTACK_PENDING_NOTIFY, // Waiting for context callback + MP_BLUETOOTH_BTSTACK_PENDING_INDICATE, // Waiting for context callback + MP_BLUETOOTH_BTSTACK_PENDING_WRITE_NO_RESPONSE, // Waiting for conn handle + // Hold buffer pointer until complete. + MP_BLUETOOTH_BTSTACK_PENDING_WRITE, // Waiting for write done event +}; + +// Pending operation: +// - Holds a GC reference to the copied outgoing buffer. +// - Provides enough information for the callback handler to execute the desired operation. +struct _mp_btstack_pending_op_t { + btstack_linked_item_t *next; // Must be first field to match btstack_linked_item. + + // See enum above. + uint16_t op_type; + + // For all op types. + uint16_t conn_handle; + uint16_t value_handle; + + // For notify/indicate only. + // context_registration.context will point back to this struct. + btstack_context_callback_registration_t context_registration; + + // For notify/indicate/write-without-response, this is the actual buffer to send. + // For write-with-response, just holding onto the buffer for GC ref. + size_t len; + uint8_t buf[]; +}; + +// Must hold MICROPY_PY_BLUETOOTH_ENTER. +STATIC void btstack_remove_pending_operation(mp_btstack_pending_op_t *pending_op, bool del) { + bool removed = btstack_linked_list_remove(&MP_STATE_PORT(bluetooth_btstack_root_pointers)->pending_ops, (btstack_linked_item_t *)pending_op); + assert(removed); + (void)removed; + if (del) { + m_del_var(mp_btstack_pending_op_t, uint8_t, pending_op->len, pending_op); + } +} + +// Called in response to a gatts_notify/indicate being unable to complete, which then calls +// att_server_request_to_send_notification. +// We now have an opportunity to re-try the operation with an empty ACL buffer. +STATIC void btstack_notify_indicate_ready_handler(void *context) { + MICROPY_PY_BLUETOOTH_ENTER + mp_btstack_pending_op_t *pending_op = (mp_btstack_pending_op_t *)context; + DEBUG_printf("btstack_notify_indicate_ready_handler op_type=%d conn_handle=%d value_handle=%d len=%zu\n", pending_op->op_type, pending_op->conn_handle, pending_op->value_handle, pending_op->len); + if (pending_op->op_type == MP_BLUETOOTH_BTSTACK_PENDING_NOTIFY) { + int err = att_server_notify(pending_op->conn_handle, pending_op->value_handle, pending_op->buf, pending_op->len); + DEBUG_printf("btstack_notify_indicate_ready_handler: sending notification err=%d\n", err); + assert(err == ERROR_CODE_SUCCESS); + (void)err; + } else { + assert(pending_op->op_type == MP_BLUETOOTH_BTSTACK_PENDING_INDICATE); + int err = att_server_indicate(pending_op->conn_handle, pending_op->value_handle, NULL, 0); + DEBUG_printf("btstack_notify_indicate_ready_handler: sending indication err=%d\n", err); + assert(err == ERROR_CODE_SUCCESS); + (void)err; + } + // Can't free the pending op as we're in IRQ context. Leave it for the GC. + btstack_remove_pending_operation(pending_op, false /* del */); + MICROPY_PY_BLUETOOTH_EXIT +} + +// Register a pending background operation -- copies the buffer, and makes it known to the GC. +STATIC mp_btstack_pending_op_t *btstack_enqueue_pending_operation(uint16_t op_type, uint16_t conn_handle, uint16_t value_handle, const uint8_t *buf, size_t len) { + DEBUG_printf("btstack_enqueue_pending_operation op_type=%d conn_handle=%d value_handle=%d len=%zu\n", op_type, conn_handle, value_handle, len); + mp_btstack_pending_op_t *pending_op = m_new_obj_var(mp_btstack_pending_op_t, uint8_t, len); + pending_op->op_type = op_type; + pending_op->conn_handle = conn_handle; + pending_op->value_handle = value_handle; + pending_op->len = len; + memcpy(pending_op->buf, buf, len); + + if (op_type == MP_BLUETOOTH_BTSTACK_PENDING_NOTIFY || op_type == MP_BLUETOOTH_BTSTACK_PENDING_INDICATE) { + pending_op->context_registration.callback = &btstack_notify_indicate_ready_handler; + pending_op->context_registration.context = pending_op; + } + + MICROPY_PY_BLUETOOTH_ENTER + bool added = btstack_linked_list_add(&MP_STATE_PORT(bluetooth_btstack_root_pointers)->pending_ops, (btstack_linked_item_t *)pending_op); + assert(added); + (void)added; + MICROPY_PY_BLUETOOTH_EXIT + + return pending_op; +} + +#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE -STATIC void btstack_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { +// Cleans up a pending op of the specified type for this conn_handle (and if specified, value_handle). +// Used by MP_BLUETOOTH_BTSTACK_PENDING_WRITE and MP_BLUETOOTH_BTSTACK_PENDING_WRITE_NO_RESPONSE. +// At the moment, both will set value_handle=0xffff as the events do not know their value_handle. +// TODO: Can we make btstack give us the value_handle for regular write (with response) so that we +// know for sure that we're using the correct entry. +STATIC mp_btstack_pending_op_t *btstack_finish_pending_operation(uint16_t op_type, uint16_t conn_handle, uint16_t value_handle, bool del) { + MICROPY_PY_BLUETOOTH_ENTER + DEBUG_printf("btstack_finish_pending_operation op_type=%d conn_handle=%d value_handle=%d\n", op_type, conn_handle, value_handle); + btstack_linked_list_iterator_t it; + btstack_linked_list_iterator_init(&it, &MP_STATE_PORT(bluetooth_btstack_root_pointers)->pending_ops); + while (btstack_linked_list_iterator_has_next(&it)) { + mp_btstack_pending_op_t *pending_op = (mp_btstack_pending_op_t *)btstack_linked_list_iterator_next(&it); + + if (pending_op->op_type == op_type && pending_op->conn_handle == conn_handle && (value_handle == 0xffff || pending_op->value_handle == value_handle)) { + DEBUG_printf("btstack_finish_pending_operation: found value_handle=%d len=%zu\n", pending_op->value_handle, pending_op->len); + btstack_remove_pending_operation(pending_op, del); + MICROPY_PY_BLUETOOTH_EXIT + return del ? NULL : pending_op; + } + } + DEBUG_printf("btstack_finish_pending_operation: not found\n"); + MICROPY_PY_BLUETOOTH_EXIT + return NULL; +} +#endif + +// This needs to be separate to btstack_packet_handler otherwise we get +// dual-delivery of the HCI_EVENT_LE_META event. +STATIC void btstack_packet_handler_att_server(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { (void)channel; (void)size; - DEBUG_EVENT_printf("btstack_packet_handler(packet_type=%u, channel=%u, packet=%p, size=%u)\n", packet_type, channel, packet, size); + DEBUG_printf("btstack_packet_handler_att_server(packet_type=%u, packet=%p)\n", packet_type, packet); if (packet_type != HCI_EVENT_PACKET) { return; } uint8_t event_type = hci_event_packet_get_type(packet); + if (event_type == ATT_EVENT_CONNECTED) { - DEBUG_EVENT_printf(" --> att connected\n"); + DEBUG_printf(" --> att connected\n"); + // The ATT_EVENT_*CONNECTED events are fired for both peripheral and central role, with no way to tell which. + // So we use the HCI_EVENT_LE_META event directly in the main packet handler. } else if (event_type == ATT_EVENT_DISCONNECTED) { - DEBUG_EVENT_printf(" --> att disconnected\n"); - } else if (event_type == HCI_EVENT_LE_META) { - DEBUG_EVENT_printf(" --> hci le meta\n"); + DEBUG_printf(" --> att disconnected\n"); + } else if (event_type == ATT_EVENT_HANDLE_VALUE_INDICATION_COMPLETE) { + DEBUG_printf(" --> att indication complete\n"); + uint16_t conn_handle = att_event_handle_value_indication_complete_get_conn_handle(packet); + uint16_t value_handle = att_event_handle_value_indication_complete_get_attribute_handle(packet); + uint8_t status = att_event_handle_value_indication_complete_get_status(packet); + mp_bluetooth_gatts_on_indicate_complete(conn_handle, value_handle, status); + } else if (event_type == ATT_EVENT_MTU_EXCHANGE_COMPLETE) { + // This is triggered in peripheral mode, when exchange initiated by us or remote. + uint16_t conn_handle = att_event_mtu_exchange_complete_get_handle(packet); + uint16_t mtu = att_event_mtu_exchange_complete_get_MTU(packet); + mp_bluetooth_gatts_on_mtu_exchanged(conn_handle, mtu); + } else if (event_type == HCI_EVENT_LE_META || event_type == HCI_EVENT_DISCONNECTION_COMPLETE) { + // Ignore, duplicated by att_server.c. + } else { + DEBUG_printf(" --> hci att server event type: unknown (0x%02x)\n", event_type); + } +} + +#if MICROPY_BLUETOOTH_BTSTACK_ZEPHYR_STATIC_ADDRESS +// During startup, the controller (e.g. Zephyr) might give us a static address that we can use. +STATIC uint8_t controller_static_addr[6] = {0}; +STATIC bool controller_static_addr_available = false; + +STATIC const uint8_t read_static_address_command_complete_prefix[] = { 0x0e, 0x1b, 0x01, 0x09, 0xfc }; +#endif + +STATIC void btstack_packet_handler(uint8_t packet_type, uint8_t *packet, uint8_t irq) { + DEBUG_printf("btstack_packet_handler(packet_type=%u, packet=%p)\n", packet_type, packet); + if (packet_type != HCI_EVENT_PACKET) { + return; + } + + uint8_t event_type = hci_event_packet_get_type(packet); + + if (event_type == HCI_EVENT_LE_META) { + DEBUG_printf(" --> hci le meta\n"); if (hci_event_le_meta_get_subevent_code(packet) == HCI_SUBEVENT_LE_CONNECTION_COMPLETE) { uint16_t conn_handle = hci_subevent_le_connection_complete_get_connection_handle(packet); uint8_t addr_type = hci_subevent_le_connection_complete_get_peer_address_type(packet); @@ -110,38 +311,41 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint16_t channel, uint8_ } } else if (event_type == BTSTACK_EVENT_STATE) { uint8_t state = btstack_event_state_get_state(packet); - DEBUG_EVENT_printf(" --> btstack event state 0x%02x\n", state); + DEBUG_printf(" --> btstack event state 0x%02x\n", state); if (state == HCI_STATE_WORKING) { // Signal that initialisation has completed. mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_ACTIVE; + } else if (state == HCI_STATE_HALTING) { + // Signal that de-initialisation has begun. + mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_HALTING; } else if (state == HCI_STATE_OFF) { // Signal that de-initialisation has completed. mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_OFF; } + } else if (event_type == BTSTACK_EVENT_POWERON_FAILED) { + // Signal that initialisation has failed. + mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_OFF; } else if (event_type == HCI_EVENT_TRANSPORT_PACKET_SENT) { - DEBUG_EVENT_printf(" --> hci transport packet set\n"); + DEBUG_printf(" --> hci transport packet sent\n"); } else if (event_type == HCI_EVENT_COMMAND_COMPLETE) { - DEBUG_EVENT_printf(" --> hci command complete\n"); + DEBUG_printf(" --> hci command complete\n"); + #if MICROPY_BLUETOOTH_BTSTACK_ZEPHYR_STATIC_ADDRESS + if (memcmp(packet, read_static_address_command_complete_prefix, sizeof(read_static_address_command_complete_prefix)) == 0) { + DEBUG_printf(" --> static address available\n"); + reverse_48(&packet[7], controller_static_addr); + controller_static_addr_available = true; + } + #endif // MICROPY_BLUETOOTH_BTSTACK_ZEPHYR_STATIC_ADDRESS } else if (event_type == HCI_EVENT_COMMAND_STATUS) { - DEBUG_EVENT_printf(" --> hci command status\n"); + DEBUG_printf(" --> hci command status\n"); } else if (event_type == HCI_EVENT_NUMBER_OF_COMPLETED_PACKETS) { - DEBUG_EVENT_printf(" --> hci number of completed packets\n"); + DEBUG_printf(" --> hci number of completed packets\n"); } else if (event_type == BTSTACK_EVENT_NR_CONNECTIONS_CHANGED) { - DEBUG_EVENT_printf(" --> btstack # conns changed\n"); + DEBUG_printf(" --> btstack # conns changed\n"); } else if (event_type == HCI_EVENT_VENDOR_SPECIFIC) { - DEBUG_EVENT_printf(" --> hci vendor specific\n"); - } else if (event_type == GAP_EVENT_ADVERTISING_REPORT) { - DEBUG_EVENT_printf(" --> gap advertising report\n"); - bd_addr_t address; - gap_event_advertising_report_get_address(packet, address); - uint8_t adv_event_type = gap_event_advertising_report_get_advertising_event_type(packet); - uint8_t address_type = gap_event_advertising_report_get_address_type(packet); - int8_t rssi = gap_event_advertising_report_get_rssi(packet); - uint8_t length = gap_event_advertising_report_get_data_length(packet); - const uint8_t *data = gap_event_advertising_report_get_data(packet); - mp_bluetooth_gap_on_scan_result(address_type, address, adv_event_type, rssi, data, length); + DEBUG_printf(" --> hci vendor specific\n"); } else if (event_type == HCI_EVENT_DISCONNECTION_COMPLETE) { - DEBUG_EVENT_printf(" --> hci disconnect complete\n"); + DEBUG_printf(" --> hci disconnect complete\n"); uint16_t conn_handle = hci_event_disconnection_complete_get_connection_handle(packet); const hci_connection_t *conn = hci_connection_for_handle(conn_handle); uint16_t irq_event; @@ -155,31 +359,56 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint16_t channel, uint8_ uint8_t addr[6] = {0}; mp_bluetooth_gap_on_connected_disconnected(irq_event, conn_handle, 0xff, addr); #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE + } else if (event_type == GAP_EVENT_ADVERTISING_REPORT) { + DEBUG_printf(" --> gap advertising report\n"); + bd_addr_t address; + gap_event_advertising_report_get_address(packet, address); + uint8_t adv_event_type = gap_event_advertising_report_get_advertising_event_type(packet); + uint8_t address_type = gap_event_advertising_report_get_address_type(packet); + int8_t rssi = gap_event_advertising_report_get_rssi(packet); + uint8_t length = gap_event_advertising_report_get_data_length(packet); + const uint8_t *data = gap_event_advertising_report_get_data(packet); + mp_bluetooth_gap_on_scan_result(address_type, address, adv_event_type, rssi, data, length); } else if (event_type == GATT_EVENT_QUERY_COMPLETE) { - DEBUG_EVENT_printf(" --> gatt query complete\n"); + uint16_t conn_handle = gatt_event_query_complete_get_handle(packet); + uint16_t status = gatt_event_query_complete_get_att_status(packet); + DEBUG_printf(" --> gatt query complete irq=%d conn_handle=%d status=%d\n", irq, conn_handle, status); + if (irq == MP_BLUETOOTH_IRQ_GATTC_READ_DONE || irq == MP_BLUETOOTH_IRQ_GATTC_WRITE_DONE) { + // TODO there is no value_handle available to pass here. + // TODO try and get this implemented in btstack. + mp_bluetooth_gattc_on_read_write_status(irq, conn_handle, 0xffff, status); + // Unref the saved buffer for the write operation on this conn_handle. + if (irq == MP_BLUETOOTH_IRQ_GATTC_WRITE_DONE) { + btstack_finish_pending_operation(MP_BLUETOOTH_BTSTACK_PENDING_WRITE, conn_handle, 0xffff, false /* del */); + } + } else if (irq == MP_BLUETOOTH_IRQ_GATTC_SERVICE_DONE || + irq == MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_DONE || + irq == MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_DONE) { + mp_bluetooth_gattc_on_discover_complete(irq, conn_handle, status); + } } else if (event_type == GATT_EVENT_SERVICE_QUERY_RESULT) { - DEBUG_EVENT_printf(" --> gatt service query result\n"); + DEBUG_printf(" --> gatt service query result\n"); uint16_t conn_handle = gatt_event_service_query_result_get_handle(packet); gatt_client_service_t service; gatt_event_service_query_result_get_service(packet, &service); mp_obj_bluetooth_uuid_t service_uuid = create_mp_uuid(service.uuid16, service.uuid128); mp_bluetooth_gattc_on_primary_service_result(conn_handle, service.start_group_handle, service.end_group_handle, &service_uuid); } else if (event_type == GATT_EVENT_CHARACTERISTIC_QUERY_RESULT) { - DEBUG_EVENT_printf(" --> gatt characteristic query result\n"); + DEBUG_printf(" --> gatt characteristic query result\n"); uint16_t conn_handle = gatt_event_characteristic_query_result_get_handle(packet); gatt_client_characteristic_t characteristic; gatt_event_characteristic_query_result_get_characteristic(packet, &characteristic); mp_obj_bluetooth_uuid_t characteristic_uuid = create_mp_uuid(characteristic.uuid16, characteristic.uuid128); mp_bluetooth_gattc_on_characteristic_result(conn_handle, characteristic.start_handle, characteristic.value_handle, characteristic.properties, &characteristic_uuid); } else if (event_type == GATT_EVENT_CHARACTERISTIC_DESCRIPTOR_QUERY_RESULT) { - DEBUG_EVENT_printf(" --> gatt descriptor query result\n"); + DEBUG_printf(" --> gatt descriptor query result\n"); uint16_t conn_handle = gatt_event_all_characteristic_descriptors_query_result_get_handle(packet); gatt_client_characteristic_descriptor_t descriptor; gatt_event_all_characteristic_descriptors_query_result_get_characteristic_descriptor(packet, &descriptor); mp_obj_bluetooth_uuid_t descriptor_uuid = create_mp_uuid(descriptor.uuid16, descriptor.uuid128); mp_bluetooth_gattc_on_descriptor_result(conn_handle, descriptor.handle, &descriptor_uuid); } else if (event_type == GATT_EVENT_CHARACTERISTIC_VALUE_QUERY_RESULT) { - DEBUG_EVENT_printf(" --> gatt characteristic value query result\n"); + DEBUG_printf(" --> gatt characteristic value query result\n"); uint16_t conn_handle = gatt_event_characteristic_value_query_result_get_handle(packet); uint16_t value_handle = gatt_event_characteristic_value_query_result_get_value_handle(packet); uint16_t len = gatt_event_characteristic_value_query_result_get_value_length(packet); @@ -189,7 +418,7 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint16_t channel, uint8_ mp_bluetooth_gattc_on_data_available_chunk(data, len); mp_bluetooth_gattc_on_data_available_end(atomic_state); } else if (event_type == GATT_EVENT_NOTIFICATION) { - DEBUG_EVENT_printf(" --> gatt notification\n"); + DEBUG_printf(" --> gatt notification\n"); uint16_t conn_handle = gatt_event_notification_get_handle(packet); uint16_t value_handle = gatt_event_notification_get_value_handle(packet); uint16_t len = gatt_event_notification_get_value_length(packet); @@ -199,7 +428,7 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint16_t channel, uint8_ mp_bluetooth_gattc_on_data_available_chunk(data, len); mp_bluetooth_gattc_on_data_available_end(atomic_state); } else if (event_type == GATT_EVENT_INDICATION) { - DEBUG_EVENT_printf(" --> gatt indication\n"); + DEBUG_printf(" --> gatt indication\n"); uint16_t conn_handle = gatt_event_indication_get_handle(packet); uint16_t value_handle = gatt_event_indication_get_value_handle(packet); uint16_t len = gatt_event_indication_get_value_length(packet); @@ -208,31 +437,71 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint16_t channel, uint8_ len = mp_bluetooth_gattc_on_data_available_start(MP_BLUETOOTH_IRQ_GATTC_INDICATE, conn_handle, value_handle, len, &atomic_state); mp_bluetooth_gattc_on_data_available_chunk(data, len); mp_bluetooth_gattc_on_data_available_end(atomic_state); - #endif + } else if (event_type == GATT_EVENT_CAN_WRITE_WITHOUT_RESPONSE) { + uint16_t conn_handle = gatt_event_can_write_without_response_get_handle(packet); + DEBUG_printf(" --> gatt can write without response %d\n", conn_handle); + mp_btstack_pending_op_t *pending_op = btstack_finish_pending_operation(MP_BLUETOOTH_BTSTACK_PENDING_WRITE_NO_RESPONSE, conn_handle, 0xffff, false /* !del */); + if (pending_op) { + DEBUG_printf(" --> ready for value_handle=%d len=%zu\n", pending_op->value_handle, pending_op->len); + gatt_client_write_value_of_characteristic_without_response(pending_op->conn_handle, pending_op->value_handle, pending_op->len, (uint8_t *)pending_op->buf); + // Note: Can't "del" the pending_op from IRQ context. Leave it for the GC. + } + + #endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE } else { - DEBUG_EVENT_printf(" --> hci event type: unknown (0x%02x)\n", event_type); + DEBUG_printf(" --> hci event type: unknown (0x%02x)\n", event_type); } } +// Because the packet handler callbacks don't support an argument, we use a specific +// handler when we need to provide additional state to the handler (in the "irq" parameter). +// This is the generic handler for when you don't need extra state. +STATIC void btstack_packet_handler_generic(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { + (void)channel; + (void)size; + btstack_packet_handler(packet_type, packet, 0); +} + +STATIC btstack_packet_callback_registration_t hci_event_callback_registration = { + .callback = &btstack_packet_handler_generic +}; + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE -STATIC void btstack_packet_handler_write_with_response(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { +// For when the handler is being used for service discovery. +STATIC void btstack_packet_handler_discover_services(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { (void)channel; (void)size; - DEBUG_EVENT_printf("btstack_packet_handler_write_with_response(packet_type=%u, channel=%u, packet=%p, size=%u)\n", packet_type, channel, packet, size); - if (packet_type != HCI_EVENT_PACKET) { - return; - } + btstack_packet_handler(packet_type, packet, MP_BLUETOOTH_IRQ_GATTC_SERVICE_DONE); +} - uint8_t event_type = hci_event_packet_get_type(packet); - if (event_type == GATT_EVENT_QUERY_COMPLETE) { - DEBUG_EVENT_printf(" --> gatt query complete\n"); - uint16_t conn_handle = gatt_event_query_complete_get_handle(packet); - uint8_t status = gatt_event_query_complete_get_att_status(packet); - // TODO there is no value_handle to pass here - mp_bluetooth_gattc_on_write_status(conn_handle, 0, status); - } +// For when the handler is being used for characteristic discovery. +STATIC void btstack_packet_handler_discover_characteristics(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { + (void)channel; + (void)size; + btstack_packet_handler(packet_type, packet, MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_DONE); } -#endif + +// For when the handler is being used for descriptor discovery. +STATIC void btstack_packet_handler_discover_descriptors(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { + (void)channel; + (void)size; + btstack_packet_handler(packet_type, packet, MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_DONE); +} + +// For when the handler is being used for a read query. +STATIC void btstack_packet_handler_read(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { + (void)channel; + (void)size; + btstack_packet_handler(packet_type, packet, MP_BLUETOOTH_IRQ_GATTC_READ_DONE); +} + +// For when the handler is being used for write-with-response. +STATIC void btstack_packet_handler_write_with_response(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { + (void)channel; + (void)size; + btstack_packet_handler(packet_type, packet, MP_BLUETOOTH_IRQ_GATTC_WRITE_DONE); +} +#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE STATIC btstack_timer_source_t btstack_init_deinit_timeout; @@ -245,8 +514,77 @@ STATIC void btstack_init_deinit_timeout_handler(btstack_timer_source_t *ds) { mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_TIMEOUT; } +#if !MICROPY_BLUETOOTH_USE_MP_HAL_GET_MAC_STATIC_ADDRESS +STATIC void btstack_static_address_ready(void *arg) { + DEBUG_printf("btstack_static_address_ready.\n"); + *(volatile bool *)arg = true; +} +#endif + +STATIC bool set_public_address(void) { + bd_addr_t local_addr; + gap_local_bd_addr(local_addr); + bd_addr_t null_addr = {0}; + if (memcmp(local_addr, null_addr, 6) == 0) { + DEBUG_printf("set_public_address: No public address available.\n"); + return false; + } + DEBUG_printf("set_public_address: Using controller's public address.\n"); + gap_random_address_set_mode(GAP_RANDOM_ADDRESS_TYPE_OFF); + return true; +} + +STATIC void set_random_address(void) { + #if MICROPY_BLUETOOTH_BTSTACK_ZEPHYR_STATIC_ADDRESS + if (controller_static_addr_available) { + DEBUG_printf("set_random_address: Using static address supplied by controller.\n"); + gap_random_address_set(controller_static_addr); + } else + #endif // MICROPY_BLUETOOTH_BTSTACK_ZEPHYR_STATIC_ADDRESS + { + bd_addr_t static_addr; + + #if MICROPY_BLUETOOTH_USE_MP_HAL_GET_MAC_STATIC_ADDRESS + + DEBUG_printf("set_random_address: Generating static address using mp_hal_get_mac\n"); + mp_hal_get_mac(MP_HAL_MAC_BDADDR, static_addr); + // Mark it as STATIC (not RPA or NRPA). + static_addr[0] |= 0xc0; + + #else + + DEBUG_printf("set_random_address: Generating random static address.\n"); + btstack_crypto_random_t sm_crypto_random_request; + volatile bool ready = false; + btstack_crypto_random_generate(&sm_crypto_random_request, static_addr, 6, &btstack_static_address_ready, (void *)&ready); + while (!ready) { + MICROPY_EVENT_POLL_HOOK + } + + #endif // MICROPY_BLUETOOTH_USE_MP_HAL_GET_MAC_STATIC_ADDRESS + + DEBUG_printf("set_random_address: Address generated.\n"); + gap_random_address_set(static_addr); + } + + // Wait for the controller to accept this address. + while (true) { + uint8_t addr_type; + bd_addr_t addr; + gap_le_get_own_address(&addr_type, addr); + + bd_addr_t null_addr = {0}; + if (memcmp(addr, null_addr, 6) != 0) { + break; + } + + MICROPY_EVENT_POLL_HOOK + } + DEBUG_printf("set_random_address: Address loaded by controller\n"); +} + int mp_bluetooth_init(void) { - DEBUG_EVENT_printf("mp_bluetooth_init\n"); + DEBUG_printf("mp_bluetooth_init\n"); if (mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_ACTIVE) { return 0; @@ -257,6 +595,10 @@ int mp_bluetooth_init(void) { btstack_memory_init(); + #if MICROPY_BLUETOOTH_BTSTACK_ZEPHYR_STATIC_ADDRESS + controller_static_addr_available = false; + #endif + MP_STATE_PORT(bluetooth_btstack_root_pointers) = m_new0(mp_bluetooth_btstack_root_pointers_t, 1); mp_bluetooth_gatts_db_create(&MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db); @@ -282,17 +624,24 @@ int mp_bluetooth_init(void) { #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE gatt_client_init(); + + // We always require explicitly exchanging MTU with ble.gattc_exchange_mtu(). + gatt_client_mtu_enable_auto_negotiation(false); #endif // Register for HCI events. - hci_event_callback_registration.callback = &btstack_packet_handler; hci_add_event_handler(&hci_event_callback_registration); + // Register for ATT server events. + att_server_register_packet_handler(&btstack_packet_handler_att_server); + // Set a timeout for HCI initialisation. btstack_run_loop_set_timer(&btstack_init_deinit_timeout, BTSTACK_INIT_DEINIT_TIMEOUT_MS); btstack_run_loop_set_timer_handler(&btstack_init_deinit_timeout, btstack_init_deinit_timeout_handler); btstack_run_loop_add_timer(&btstack_init_deinit_timeout); + DEBUG_printf("mp_bluetooth_init: waiting for stack startup\n"); + // Either the HCI event will set state to ACTIVE, or the timeout will set it to TIMEOUT. mp_bluetooth_btstack_port_start(); while (mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_STARTING) { @@ -302,6 +651,10 @@ int mp_bluetooth_init(void) { // Check for timeout. if (mp_bluetooth_btstack_state != MP_BLUETOOTH_BTSTACK_STATE_ACTIVE) { + DEBUG_printf("mp_bluetooth_init: stack startup timed out\n"); + + bool timeout = mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_TIMEOUT; + // Required to stop the polling loop. mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_OFF; // Attempt a shutdown (may not do anything). @@ -309,19 +662,33 @@ int mp_bluetooth_init(void) { // Clean up. MP_STATE_PORT(bluetooth_btstack_root_pointers) = NULL; - return MP_ETIMEDOUT; + + return timeout ? MP_ETIMEDOUT : MP_EINVAL; + } + + DEBUG_printf("mp_bluetooth_init: stack startup complete\n"); + + // At this point if the controller has its own public address, btstack will know this. + // However, if this is not available, then attempt to get a static address: + // - For a Zephyr controller on nRF, a static address will be available during startup. + // - Otherwise we ask the controller to generate a static address for us. + // In either case, calling gap_random_address_set will set the mode to STATIC, and then + // immediately set the address on the controller. We then wait until this address becomes available. + + if (!set_public_address()) { + set_random_address(); } #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE // Enable GATT_EVENT_NOTIFICATION/GATT_EVENT_INDICATION for all connections and handles. - gatt_client_listen_for_characteristic_value_updates(&MP_STATE_PORT(bluetooth_btstack_root_pointers)->notification, &btstack_packet_handler, GATT_CLIENT_ANY_CONNECTION, NULL); + gatt_client_listen_for_characteristic_value_updates(&MP_STATE_PORT(bluetooth_btstack_root_pointers)->notification, &btstack_packet_handler_generic, GATT_CLIENT_ANY_CONNECTION, NULL); #endif return 0; } void mp_bluetooth_deinit(void) { - DEBUG_EVENT_printf("mp_bluetooth_deinit\n"); + DEBUG_printf("mp_bluetooth_deinit\n"); // Nothing to do if not initialised. if (!MP_STATE_PORT(bluetooth_btstack_root_pointers)) { @@ -350,14 +717,47 @@ void mp_bluetooth_deinit(void) { mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_OFF; MP_STATE_PORT(bluetooth_btstack_root_pointers) = NULL; + + DEBUG_printf("mp_bluetooth_deinit: complete\n"); } bool mp_bluetooth_is_active(void) { return mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_ACTIVE; } -void mp_bluetooth_get_device_addr(uint8_t *addr) { - mp_hal_get_mac(MP_HAL_MAC_BDADDR, addr); +void mp_bluetooth_get_current_address(uint8_t *addr_type, uint8_t *addr) { + if (!mp_bluetooth_is_active()) { + mp_raise_OSError(ERRNO_BLUETOOTH_NOT_ACTIVE); + } + + DEBUG_printf("mp_bluetooth_get_current_address\n"); + gap_le_get_own_address(addr_type, addr); +} + +void mp_bluetooth_set_address_mode(uint8_t addr_mode) { + if (!mp_bluetooth_is_active()) { + mp_raise_OSError(ERRNO_BLUETOOTH_NOT_ACTIVE); + } + + switch (addr_mode) { + case MP_BLUETOOTH_ADDRESS_MODE_PUBLIC: { + DEBUG_printf("mp_bluetooth_set_address_mode: public\n"); + if (!set_public_address()) { + // No public address available. + mp_raise_OSError(MP_EINVAL); + } + break; + } + case MP_BLUETOOTH_ADDRESS_MODE_RANDOM: { + DEBUG_printf("mp_bluetooth_set_address_mode: random\n"); + set_random_address(); + break; + } + case MP_BLUETOOTH_ADDRESS_MODE_RPA: + case MP_BLUETOOTH_ADDRESS_MODE_NRPA: + // Not yet supported. + mp_raise_OSError(MP_EINVAL); + } } size_t mp_bluetooth_gap_get_device_name(const uint8_t **buf) { @@ -369,12 +769,11 @@ size_t mp_bluetooth_gap_get_device_name(const uint8_t **buf) { } int mp_bluetooth_gap_set_device_name(const uint8_t *buf, size_t len) { - mp_bluetooth_gatts_db_write(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, BTSTACK_GAP_DEVICE_NAME_HANDLE, buf, len); - return 0; + return mp_bluetooth_gatts_db_write(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, BTSTACK_GAP_DEVICE_NAME_HANDLE, buf, len); } int mp_bluetooth_gap_advertise_start(bool connectable, int32_t interval_us, const uint8_t *adv_data, size_t adv_data_len, const uint8_t *sr_data, size_t sr_data_len) { - DEBUG_EVENT_printf("mp_bluetooth_gap_advertise_start\n"); + DEBUG_printf("mp_bluetooth_gap_advertise_start\n"); uint16_t adv_int_min = interval_us / 625; uint16_t adv_int_max = interval_us / 625; uint8_t adv_type = connectable ? 0 : 2; @@ -410,14 +809,14 @@ int mp_bluetooth_gap_advertise_start(bool connectable, int32_t interval_us, cons } void mp_bluetooth_gap_advertise_stop(void) { - DEBUG_EVENT_printf("mp_bluetooth_gap_advertise_stop\n"); + DEBUG_printf("mp_bluetooth_gap_advertise_stop\n"); gap_advertisements_enable(false); MP_STATE_PORT(bluetooth_btstack_root_pointers)->adv_data_alloc = 0; MP_STATE_PORT(bluetooth_btstack_root_pointers)->adv_data = NULL; } int mp_bluetooth_gatts_register_service_begin(bool append) { - DEBUG_EVENT_printf("mp_bluetooth_gatts_register_service_begin\n"); + DEBUG_printf("mp_bluetooth_gatts_register_service_begin\n"); if (!append) { // This will reset the DB. // Becase the DB is statically allocated, there's no problem with just re-initing it. @@ -437,24 +836,37 @@ int mp_bluetooth_gatts_register_service_begin(bool append) { } STATIC uint16_t att_read_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t offset, uint8_t *buffer, uint16_t buffer_size) { + // Should return data length, 0 for error, or -1 for delayed response. + // For more details search "*att_read_callback*" in micropython/lib/btstack/doc/manual/docs/profiles.md (void)connection_handle; - DEBUG_EVENT_printf("btstack: att_read_callback (handle: %u, offset: %u, buffer: %p, size: %u)\n", att_handle, offset, buffer, buffer_size); + DEBUG_printf("att_read_callback (handle: %u, offset: %u, buffer: %p, size: %u)\n", att_handle, offset, buffer, buffer_size); mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, att_handle); if (!entry) { - DEBUG_EVENT_printf("btstack: att_read_callback handle not found\n"); - return 0; // TODO: Find status code for not-found. + DEBUG_printf("att_read_callback handle not found\n"); + return 0; + } + + #if MICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK + // Allow Python code to override value (by using gatts_write), or deny (by returning false) the read. + if ((buffer == NULL) && (buffer_size == 0)) { + if (!mp_bluetooth_gatts_on_read_request(connection_handle, att_handle)) { + DEBUG_printf("att_read_callback: read request denied\n"); + return 0; + } } + #endif - return att_read_callback_handle_blob(entry->data, entry->data_len, offset, buffer, buffer_size); + uint16_t ret = att_read_callback_handle_blob(entry->data, entry->data_len, offset, buffer, buffer_size); + return ret; } STATIC int att_write_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size) { (void)offset; (void)transaction_mode; - DEBUG_EVENT_printf("btstack: att_write_callback (handle: %u, mode: %u, offset: %u, buffer: %p, size: %u)\n", att_handle, transaction_mode, offset, buffer, buffer_size); + DEBUG_printf("att_write_callback (handle: %u, mode: %u, offset: %u, buffer: %p, size: %u)\n", att_handle, transaction_mode, offset, buffer, buffer_size); mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, att_handle); if (!entry) { - DEBUG_EVENT_printf("btstack: att_write_callback handle not found\n"); + DEBUG_printf("att_write_callback handle not found\n"); return 0; // TODO: Find status code for not-found. } @@ -476,7 +888,7 @@ STATIC inline uint16_t get_uuid16(const mp_obj_bluetooth_uuid_t *uuid) { } int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, mp_obj_bluetooth_uuid_t **characteristic_uuids, uint8_t *characteristic_flags, mp_obj_bluetooth_uuid_t **descriptor_uuids, uint8_t *descriptor_flags, uint8_t *num_descriptors, uint16_t *handles, size_t num_characteristics) { - DEBUG_EVENT_printf("mp_bluetooth_gatts_register_service\n"); + DEBUG_printf("mp_bluetooth_gatts_register_service\n"); // Note: btstack expects BE UUIDs (which it immediately convertes to LE). // So we have to convert all our modbluetooth LE UUIDs to BE just for the att_db_util_add_* methods (using get_uuid16 above, and reverse_128 from btstackutil.h). @@ -515,9 +927,12 @@ int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, m if (props & (ATT_PROPERTY_NOTIFY | ATT_PROPERTY_INDICATE)) { // btstack creates the CCCB as the next handle. mp_bluetooth_gatts_db_create_entry(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, handles[handle_index] + 1, MP_BLUETOOTH_CCCB_LEN); - mp_bluetooth_gatts_db_write(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, handles[handle_index] + 1, cccb_buf, sizeof(cccb_buf)); + int ret = mp_bluetooth_gatts_db_write(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, handles[handle_index] + 1, cccb_buf, sizeof(cccb_buf)); + if (ret) { + return ret; + } } - DEBUG_EVENT_printf("Registered char with handle %u\n", handles[handle_index]); + DEBUG_printf("mp_bluetooth_gatts_register_service: Registered char with handle %u\n", handles[handle_index]); ++handle_index; for (size_t j = 0; j < num_descriptors[i]; ++j) { @@ -535,7 +950,7 @@ int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, m return MP_EINVAL; } mp_bluetooth_gatts_db_create_entry(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, handles[handle_index], MP_BLUETOOTH_DEFAULT_ATTR_LEN); - DEBUG_EVENT_printf("Registered desc with handle %u\n", handles[handle_index]); + DEBUG_printf("mp_bluetooth_gatts_register_service: Registered desc with handle %u\n", handles[handle_index]); ++descriptor_index; ++handle_index; } @@ -545,49 +960,114 @@ int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, m } int mp_bluetooth_gatts_register_service_end(void) { - DEBUG_EVENT_printf("mp_bluetooth_gatts_register_service_end\n"); + DEBUG_printf("mp_bluetooth_gatts_register_service_end\n"); att_server_init(att_db_util_get_address(), &att_read_callback, &att_write_callback); return 0; } int mp_bluetooth_gatts_read(uint16_t value_handle, uint8_t **value, size_t *value_len) { - DEBUG_EVENT_printf("mp_bluetooth_gatts_read\n"); + DEBUG_printf("mp_bluetooth_gatts_read\n"); return mp_bluetooth_gatts_db_read(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, value_handle, value, value_len); } int mp_bluetooth_gatts_write(uint16_t value_handle, const uint8_t *value, size_t value_len) { - DEBUG_EVENT_printf("mp_bluetooth_gatts_write\n"); + DEBUG_printf("mp_bluetooth_gatts_write\n"); return mp_bluetooth_gatts_db_write(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, value_handle, value, value_len); } int mp_bluetooth_gatts_notify(uint16_t conn_handle, uint16_t value_handle) { - DEBUG_EVENT_printf("mp_bluetooth_gatts_notify\n"); + DEBUG_printf("mp_bluetooth_gatts_notify\n"); // Note: btstack doesn't appear to support sending a notification without a value, so include the stored value. uint8_t *data = NULL; size_t len = 0; mp_bluetooth_gatts_db_read(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, value_handle, &data, &len); - return mp_bluetooth_gatts_notify_send(conn_handle, value_handle, data, &len); + return mp_bluetooth_gatts_notify_send(conn_handle, value_handle, data, len); } -int mp_bluetooth_gatts_notify_send(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t *value_len) { - DEBUG_EVENT_printf("mp_bluetooth_gatts_notify_send\n"); - // TODO: We need to use att_server_request_to_send_notification here as the stack may not be ready to send a notification. - int err = att_server_notify(conn_handle, value_handle, value, *value_len); - return btstack_error_to_errno(err); +int mp_bluetooth_gatts_notify_send(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t value_len) { + DEBUG_printf("mp_bluetooth_gatts_notify_send\n"); + + // Attempt to send immediately. If it succeeds, btstack will copy the buffer. + MICROPY_PY_BLUETOOTH_ENTER + int err = att_server_notify(conn_handle, value_handle, value, value_len); + MICROPY_PY_BLUETOOTH_EXIT + + if (err == BTSTACK_ACL_BUFFERS_FULL) { + DEBUG_printf("mp_bluetooth_gatts_notify_send: ACL buffer full, scheduling callback\n"); + // Schedule callback, making a copy of the buffer. + mp_btstack_pending_op_t *pending_op = btstack_enqueue_pending_operation(MP_BLUETOOTH_BTSTACK_PENDING_NOTIFY, conn_handle, value_handle, value, value_len); + + err = att_server_request_to_send_notification(&pending_op->context_registration, conn_handle); + + if (err != ERROR_CODE_SUCCESS) { + // Failure. Unref and free the pending operation. + btstack_remove_pending_operation(pending_op, true /* del */); + } + + return 0; + } else { + return btstack_error_to_errno(err); + } } int mp_bluetooth_gatts_indicate(uint16_t conn_handle, uint16_t value_handle) { - DEBUG_EVENT_printf("mp_bluetooth_gatts_indicate\n"); - return btstack_error_to_errno(att_server_indicate(conn_handle, value_handle, NULL, 0)); + DEBUG_printf("mp_bluetooth_gatts_indicate\n"); + + uint8_t *data = NULL; + size_t len = 0; + mp_bluetooth_gatts_db_read(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, value_handle, &data, &len); + + // Indicate will raise ATT_EVENT_HANDLE_VALUE_INDICATION_COMPLETE when + // acknowledged (or timeout/error). + + // Attempt to send immediately, will copy buffer. + MICROPY_PY_BLUETOOTH_ENTER + int err = att_server_indicate(conn_handle, value_handle, data, len); + MICROPY_PY_BLUETOOTH_EXIT + + if (err == BTSTACK_ACL_BUFFERS_FULL) { + DEBUG_printf("mp_bluetooth_gatts_indicate: ACL buffer full, scheduling callback\n"); + // Schedule callback, making a copy of the buffer. + mp_btstack_pending_op_t *pending_op = btstack_enqueue_pending_operation(MP_BLUETOOTH_BTSTACK_PENDING_INDICATE, conn_handle, value_handle, data, len); + + err = att_server_request_to_send_indication(&pending_op->context_registration, conn_handle); + + if (err != ERROR_CODE_SUCCESS) { + // Failure. Unref and free the pending operation. + btstack_remove_pending_operation(pending_op, true /* del */); + } + + return 0; + } else { + return btstack_error_to_errno(err); + } } int mp_bluetooth_gatts_set_buffer(uint16_t value_handle, size_t len, bool append) { - DEBUG_EVENT_printf("mp_bluetooth_gatts_set_buffer\n"); + DEBUG_printf("mp_bluetooth_gatts_set_buffer\n"); return mp_bluetooth_gatts_db_resize(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, value_handle, len, append); } +int mp_bluetooth_get_preferred_mtu(void) { + if (!mp_bluetooth_is_active()) { + mp_raise_OSError(ERRNO_BLUETOOTH_NOT_ACTIVE); + } + return l2cap_max_le_mtu(); +} + +int mp_bluetooth_set_preferred_mtu(uint16_t mtu) { + if (!mp_bluetooth_is_active()) { + return ERRNO_BLUETOOTH_NOT_ACTIVE; + } + l2cap_set_max_le_mtu(mtu); + if (l2cap_max_le_mtu() != mtu) { + return MP_EINVAL; + } + return 0; +} + int mp_bluetooth_gap_disconnect(uint16_t conn_handle) { - DEBUG_EVENT_printf("mp_bluetooth_gap_disconnect\n"); + DEBUG_printf("mp_bluetooth_gap_disconnect\n"); gap_disconnect(conn_handle); return 0; } @@ -595,27 +1075,28 @@ int mp_bluetooth_gap_disconnect(uint16_t conn_handle) { #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE STATIC btstack_timer_source_t scan_duration_timeout; -STATIC void hci_initialization_timeout_handler(btstack_timer_source_t *ds) { +STATIC void scan_duration_timeout_handler(btstack_timer_source_t *ds) { (void)ds; mp_bluetooth_gap_scan_stop(); } -int mp_bluetooth_gap_scan_start(int32_t duration_ms, int32_t interval_us, int32_t window_us) { - DEBUG_EVENT_printf("mp_bluetooth_gap_scan_start\n"); +int mp_bluetooth_gap_scan_start(int32_t duration_ms, int32_t interval_us, int32_t window_us, bool active_scan) { + DEBUG_printf("mp_bluetooth_gap_scan_start\n"); - btstack_run_loop_set_timer(&scan_duration_timeout, duration_ms); - btstack_run_loop_set_timer_handler(&scan_duration_timeout, hci_initialization_timeout_handler); - btstack_run_loop_add_timer(&scan_duration_timeout); + if (duration_ms > 0) { + btstack_run_loop_set_timer(&scan_duration_timeout, duration_ms); + btstack_run_loop_set_timer_handler(&scan_duration_timeout, scan_duration_timeout_handler); + btstack_run_loop_add_timer(&scan_duration_timeout); + } - // 0 = passive scan (we don't handle scan response). - gap_set_scan_parameters(0, interval_us / 625, window_us / 625); + gap_set_scan_parameters(active_scan ? 1 : 0, interval_us / 625, window_us / 625); gap_start_scan(); return 0; } int mp_bluetooth_gap_scan_stop(void) { - DEBUG_EVENT_printf("mp_bluetooth_gap_scan_stop\n"); + DEBUG_printf("mp_bluetooth_gap_scan_stop\n"); btstack_run_loop_remove_timer(&scan_duration_timeout); gap_stop_scan(); mp_bluetooth_gap_on_scan_complete(); @@ -623,7 +1104,7 @@ int mp_bluetooth_gap_scan_stop(void) { } int mp_bluetooth_gap_peripheral_connect(uint8_t addr_type, const uint8_t *addr, int32_t duration_ms) { - DEBUG_EVENT_printf("mp_bluetooth_gap_peripheral_connect\n"); + DEBUG_printf("mp_bluetooth_gap_peripheral_connect\n"); uint16_t conn_scan_interval = 60000 / 625; uint16_t conn_scan_window = 30000 / 625; @@ -641,14 +1122,28 @@ int mp_bluetooth_gap_peripheral_connect(uint8_t addr_type, const uint8_t *addr, return btstack_error_to_errno(gap_connect(btstack_addr, addr_type)); } -int mp_bluetooth_gattc_discover_primary_services(uint16_t conn_handle) { - DEBUG_EVENT_printf("mp_bluetooth_gattc_discover_primary_services\n"); - return btstack_error_to_errno(gatt_client_discover_primary_services(&btstack_packet_handler, conn_handle)); +int mp_bluetooth_gattc_discover_primary_services(uint16_t conn_handle, const mp_obj_bluetooth_uuid_t *uuid) { + DEBUG_printf("mp_bluetooth_gattc_discover_primary_services\n"); + uint8_t err; + if (uuid) { + if (uuid->type == MP_BLUETOOTH_UUID_TYPE_16) { + err = gatt_client_discover_primary_services_by_uuid16(&btstack_packet_handler_discover_services, conn_handle, get_uuid16(uuid)); + } else if (uuid->type == MP_BLUETOOTH_UUID_TYPE_128) { + uint8_t buffer[16]; + reverse_128(uuid->data, buffer); + err = gatt_client_discover_primary_services_by_uuid128(&btstack_packet_handler_discover_services, conn_handle, buffer); + } else { + DEBUG_printf(" --> unknown UUID size\n"); + return MP_EINVAL; + } + } else { + err = gatt_client_discover_primary_services(&btstack_packet_handler_discover_services, conn_handle); + } + return btstack_error_to_errno(err); } -int mp_bluetooth_gattc_discover_characteristics(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle) { - DEBUG_EVENT_printf("mp_bluetooth_gattc_discover_characteristics\n"); - +int mp_bluetooth_gattc_discover_characteristics(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle, const mp_obj_bluetooth_uuid_t *uuid) { + DEBUG_printf("mp_bluetooth_gattc_discover_characteristics\n"); gatt_client_service_t service = { // Only start/end handles needed for gatt_client_discover_characteristics_for_service. .start_group_handle = start_handle, @@ -656,11 +1151,26 @@ int mp_bluetooth_gattc_discover_characteristics(uint16_t conn_handle, uint16_t s .uuid16 = 0, .uuid128 = {0}, }; - return btstack_error_to_errno(gatt_client_discover_characteristics_for_service(&btstack_packet_handler, conn_handle, &service)); + uint8_t err; + if (uuid) { + if (uuid->type == MP_BLUETOOTH_UUID_TYPE_16) { + err = gatt_client_discover_characteristics_for_service_by_uuid16(&btstack_packet_handler_discover_characteristics, conn_handle, &service, get_uuid16(uuid)); + } else if (uuid->type == MP_BLUETOOTH_UUID_TYPE_128) { + uint8_t buffer[16]; + reverse_128(uuid->data, buffer); + err = gatt_client_discover_characteristics_for_service_by_uuid128(&btstack_packet_handler_discover_characteristics, conn_handle, &service, buffer); + } else { + DEBUG_printf(" --> unknown UUID size\n"); + return MP_EINVAL; + } + } else { + err = gatt_client_discover_characteristics_for_service(&btstack_packet_handler_discover_characteristics, conn_handle, &service); + } + return btstack_error_to_errno(err); } int mp_bluetooth_gattc_discover_descriptors(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle) { - DEBUG_EVENT_printf("mp_bluetooth_gattc_discover_descriptors\n"); + DEBUG_printf("mp_bluetooth_gattc_discover_descriptors\n"); gatt_client_characteristic_t characteristic = { // Only start/end handles needed for gatt_client_discover_characteristic_descriptors. .start_handle = start_handle, @@ -670,30 +1180,61 @@ int mp_bluetooth_gattc_discover_descriptors(uint16_t conn_handle, uint16_t start .uuid16 = 0, .uuid128 = {0}, }; - return btstack_error_to_errno(gatt_client_discover_characteristic_descriptors(&btstack_packet_handler, conn_handle, &characteristic)); + return btstack_error_to_errno(gatt_client_discover_characteristic_descriptors(&btstack_packet_handler_discover_descriptors, conn_handle, &characteristic)); } int mp_bluetooth_gattc_read(uint16_t conn_handle, uint16_t value_handle) { - DEBUG_EVENT_printf("mp_bluetooth_gattc_read\n"); - return btstack_error_to_errno(gatt_client_read_value_of_characteristic_using_value_handle(&btstack_packet_handler, conn_handle, value_handle)); + DEBUG_printf("mp_bluetooth_gattc_read\n"); + return btstack_error_to_errno(gatt_client_read_value_of_characteristic_using_value_handle(&btstack_packet_handler_read, conn_handle, value_handle)); } int mp_bluetooth_gattc_write(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t *value_len, unsigned int mode) { - DEBUG_EVENT_printf("mp_bluetooth_gattc_write\n"); + DEBUG_printf("mp_bluetooth_gattc_write\n"); - // TODO the below gatt_client functions do not copy the data and require it to be valid - // until the write is done, so there should be some kind of buffering done here. + // We should be distinguishing between gatt_client_write_value_of_characteristic vs + // gatt_client_write_characteristic_descriptor_using_descriptor_handle. + // However both are implemented using send_gatt_write_attribute_value_request under the hood, + // and we get the exact same event to the packet handler. + // Same story for the "without response" version. + + int err; + mp_btstack_pending_op_t *pending_op = NULL; if (mode == MP_BLUETOOTH_WRITE_MODE_NO_RESPONSE) { - // TODO need to call gatt_client_request_can_write_without_response_event then do - // the actual write on the callback from that. - return btstack_error_to_errno(gatt_client_write_value_of_characteristic_without_response(conn_handle, value_handle, *value_len, (uint8_t *)value)); + // If possible, this will send immediately, copying the buffer directly to the ACL buffer. + err = gatt_client_write_value_of_characteristic_without_response(conn_handle, value_handle, *value_len, (uint8_t *)value); + if (err == GATT_CLIENT_BUSY) { + DEBUG_printf("mp_bluetooth_gattc_write: client busy\n"); + // Can't send right now, need to take a copy of the buffer and add it to the queue. + pending_op = btstack_enqueue_pending_operation(MP_BLUETOOTH_BTSTACK_PENDING_WRITE_NO_RESPONSE, conn_handle, value_handle, value, *value_len); + // Notify when this conn_handle can write. + err = gatt_client_request_can_write_without_response_event(&btstack_packet_handler_generic, conn_handle); + } else { + DEBUG_printf("mp_bluetooth_gattc_write: other failure: %d\n", err); + } + } else if (mode == MP_BLUETOOTH_WRITE_MODE_WITH_RESPONSE) { + // Pending operation copies the value buffer and keeps a GC reference + // until the response comes back (there is always a response). + pending_op = btstack_enqueue_pending_operation(MP_BLUETOOTH_BTSTACK_PENDING_WRITE, conn_handle, value_handle, value, *value_len); + err = gatt_client_write_value_of_characteristic(&btstack_packet_handler_write_with_response, conn_handle, value_handle, pending_op->len, pending_op->buf); + } else { + return MP_EINVAL; } - if (mode == MP_BLUETOOTH_WRITE_MODE_WITH_RESPONSE) { - return btstack_error_to_errno(gatt_client_write_value_of_characteristic(&btstack_packet_handler_write_with_response, conn_handle, value_handle, *value_len, (uint8_t *)value)); + + if (pending_op && err != ERROR_CODE_SUCCESS) { + // Failure. Unref and free the pending operation. + btstack_remove_pending_operation(pending_op, true /* del */); } - return MP_EINVAL; + return btstack_error_to_errno(err); +} + +int mp_bluetooth_gattc_exchange_mtu(uint16_t conn_handle) { + DEBUG_printf("mp_bluetooth_exchange_mtu: conn_handle=%d mtu=%d\n", conn_handle, l2cap_max_le_mtu()); + + gatt_client_send_mtu_negotiation(&btstack_packet_handler_att_server, conn_handle); + + return 0; } #endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE diff --git a/extmod/btstack/modbluetooth_btstack.h b/extmod/btstack/modbluetooth_btstack.h index 3bdb5f271e5dd..7890bbfae2c4a 100644 --- a/extmod/btstack/modbluetooth_btstack.h +++ b/extmod/btstack/modbluetooth_btstack.h @@ -33,6 +33,8 @@ #include "lib/btstack/src/btstack.h" +typedef struct _mp_btstack_pending_op_t mp_btstack_pending_op_t; + typedef struct _mp_bluetooth_btstack_root_pointers_t { // This stores both the advertising data and the scan response data, concatenated together. uint8_t *adv_data; @@ -42,6 +44,8 @@ typedef struct _mp_bluetooth_btstack_root_pointers_t { // Characteristic (and descriptor) value storage. mp_gatts_db_t gatts_db; + btstack_linked_list_t pending_ops; + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE // Registration for notify/indicate events. gatt_client_notification_t notification; @@ -52,6 +56,7 @@ enum { MP_BLUETOOTH_BTSTACK_STATE_OFF, MP_BLUETOOTH_BTSTACK_STATE_STARTING, MP_BLUETOOTH_BTSTACK_STATE_ACTIVE, + MP_BLUETOOTH_BTSTACK_STATE_HALTING, MP_BLUETOOTH_BTSTACK_STATE_TIMEOUT, }; diff --git a/extmod/machine_i2c.c b/extmod/machine_i2c.c index 14cba962365e1..9203f16f6d785 100644 --- a/extmod/machine_i2c.c +++ b/extmod/machine_i2c.c @@ -302,6 +302,12 @@ STATIC int mp_machine_i2c_writeto(mp_obj_base_t *self, uint16_t addr, const uint /******************************************************************************/ // MicroPython bindings for I2C +STATIC void mp_machine_soft_i2c_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + mp_machine_soft_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "SoftI2C(scl=" MP_HAL_PIN_FMT ", sda=" MP_HAL_PIN_FMT ", freq=%u)", + mp_hal_pin_name(self->scl), mp_hal_pin_name(self->sda), 500000 / self->us_delay); +} + STATIC void machine_i2c_obj_init_helper(machine_i2c_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_scl, ARG_sda, ARG_freq, ARG_timeout }; static const mp_arg_t allowed_args[] = { @@ -318,25 +324,10 @@ STATIC void machine_i2c_obj_init_helper(machine_i2c_obj_t *self, size_t n_args, mp_hal_i2c_init(self, args[ARG_freq].u_int); } -STATIC mp_obj_t machine_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { - // check the id argument, if given - if (n_args > 0) { - if (args[0] != MP_OBJ_NEW_SMALL_INT(-1)) { - #if defined(MICROPY_PY_MACHINE_I2C_MAKE_NEW) - // dispatch to port-specific constructor - extern mp_obj_t MICROPY_PY_MACHINE_I2C_MAKE_NEW(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args); - return MICROPY_PY_MACHINE_I2C_MAKE_NEW(type, n_args, n_kw, args); - #else - mp_raise_ValueError(MP_ERROR_TEXT("invalid I2C peripheral")); - #endif - } - --n_args; - ++args; - } - +STATIC mp_obj_t mp_machine_soft_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { // create new soft I2C object machine_i2c_obj_t *self = m_new_obj(machine_i2c_obj_t); - self->base.type = &machine_i2c_type; + self->base.type = &mp_machine_soft_i2c_type; mp_map_t kw_args; mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); machine_i2c_obj_init_helper(self, n_args, args, &kw_args); @@ -526,13 +517,24 @@ STATIC mp_obj_t machine_i2c_writevto(size_t n_args, const mp_obj_t *args) { } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_i2c_writevto_obj, 3, 4, machine_i2c_writevto); -STATIC int read_mem(mp_obj_t self_in, uint16_t addr, uint32_t memaddr, uint8_t addrsize, uint8_t *buf, size_t len) { - mp_obj_base_t *self = (mp_obj_base_t *)MP_OBJ_TO_PTR(self_in); - uint8_t memaddr_buf[4]; +STATIC size_t fill_memaddr_buf(uint8_t *memaddr_buf, uint32_t memaddr, uint8_t addrsize) { size_t memaddr_len = 0; + if ((addrsize & 7) != 0 || addrsize > 32) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid addrsize")); + } for (int16_t i = addrsize - 8; i >= 0; i -= 8) { memaddr_buf[memaddr_len++] = memaddr >> i; } + return memaddr_len; +} + +STATIC int read_mem(mp_obj_t self_in, uint16_t addr, uint32_t memaddr, uint8_t addrsize, uint8_t *buf, size_t len) { + mp_obj_base_t *self = (mp_obj_base_t *)MP_OBJ_TO_PTR(self_in); + + // Create buffer with memory address + uint8_t memaddr_buf[4]; + size_t memaddr_len = fill_memaddr_buf(&memaddr_buf[0], memaddr, addrsize); + int ret = mp_machine_i2c_writeto(self, addr, memaddr_buf, memaddr_len, false); if (ret != memaddr_len) { // must generate STOP @@ -546,11 +548,8 @@ STATIC int write_mem(mp_obj_t self_in, uint16_t addr, uint32_t memaddr, uint8_t mp_obj_base_t *self = (mp_obj_base_t *)MP_OBJ_TO_PTR(self_in); // Create buffer with memory address - size_t memaddr_len = 0; uint8_t memaddr_buf[4]; - for (int16_t i = addrsize - 8; i >= 0; i -= 8) { - memaddr_buf[memaddr_len++] = memaddr >> i; - } + size_t memaddr_len = fill_memaddr_buf(&memaddr_buf[0], memaddr, addrsize); // Create partial write buffers mp_machine_i2c_buf_t bufs[2] = { @@ -692,10 +691,11 @@ STATIC const mp_machine_i2c_p_t mp_machine_soft_i2c_p = { .transfer = mp_machine_soft_i2c_transfer, }; -const mp_obj_type_t machine_i2c_type = { +const mp_obj_type_t mp_machine_soft_i2c_type = { { &mp_type_type }, - .name = MP_QSTR_I2C, - .make_new = machine_i2c_make_new, + .name = MP_QSTR_SoftI2C, + .print = mp_machine_soft_i2c_print, + .make_new = mp_machine_soft_i2c_make_new, .protocol = &mp_machine_soft_i2c_p, .locals_dict = (mp_obj_dict_t *)&mp_machine_soft_i2c_locals_dict, }; diff --git a/extmod/machine_i2c.h b/extmod/machine_i2c.h index f951c1f214973..e3a87e282a0fe 100644 --- a/extmod/machine_i2c.h +++ b/extmod/machine_i2c.h @@ -27,6 +27,20 @@ #define MICROPY_INCLUDED_EXTMOD_MACHINE_I2C_H #include "py/obj.h" +#include "py/mphal.h" + +// Temporary support for legacy construction of SoftI2C via I2C type. +#define MP_MACHINE_I2C_CHECK_FOR_LEGACY_SOFTI2C_CONSTRUCTION(n_args, n_kw, all_args) \ + do { \ + if (n_args == 0 || all_args[0] == MP_OBJ_NEW_SMALL_INT(-1)) { \ + mp_print_str(MICROPY_ERROR_PRINTER, "Warning: I2C(-1, ...) is deprecated, use SoftI2C(...) instead\n"); \ + if (n_args != 0) { \ + --n_args; \ + ++all_args; \ + } \ + return mp_machine_soft_i2c_type.make_new(&mp_machine_soft_i2c_type, n_args, n_kw, all_args); \ + } \ + } while (0) #define MP_MACHINE_I2C_FLAG_READ (0x01) // if not set then it's a write #define MP_MACHINE_I2C_FLAG_STOP (0x02) @@ -56,7 +70,7 @@ typedef struct _mp_machine_soft_i2c_obj_t { mp_hal_pin_obj_t sda; } mp_machine_soft_i2c_obj_t; -extern const mp_obj_type_t machine_i2c_type; +extern const mp_obj_type_t mp_machine_soft_i2c_type; extern const mp_obj_dict_t mp_machine_soft_i2c_locals_dict; int mp_machine_i2c_transfer_adaptor(mp_obj_base_t *self, uint16_t addr, size_t n, mp_machine_i2c_buf_t *bufs, unsigned int flags); diff --git a/extmod/machine_spi.c b/extmod/machine_spi.c index a7f96573a720e..c951a5137c5ce 100644 --- a/extmod/machine_spi.c +++ b/extmod/machine_spi.c @@ -41,28 +41,6 @@ /******************************************************************************/ // MicroPython bindings for generic machine.SPI -STATIC mp_obj_t mp_machine_soft_spi_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args); - -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) { - // check the id argument, if given - if (n_args > 0) { - if (args[0] != MP_OBJ_NEW_SMALL_INT(-1)) { - #if defined(MICROPY_PY_MACHINE_SPI_MAKE_NEW) - // dispatch to port-specific constructor - extern mp_obj_t MICROPY_PY_MACHINE_SPI_MAKE_NEW(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args); - return MICROPY_PY_MACHINE_SPI_MAKE_NEW(type, n_args, n_kw, args); - #else - mp_raise_ValueError(MP_ERROR_TEXT("invalid SPI peripheral")); - #endif - } - --n_args; - ++args; - } - - // software SPI - return mp_machine_soft_spi_make_new(type, n_args, n_kw, args); -} - STATIC mp_obj_t machine_spi_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { mp_obj_base_t *s = (mp_obj_base_t *)MP_OBJ_TO_PTR(args[0]); mp_machine_spi_p_t *spi_p = (mp_machine_spi_p_t *)s->type->protocol; @@ -275,7 +253,7 @@ const mp_obj_type_t mp_machine_soft_spi_type = { { &mp_type_type }, .name = MP_QSTR_SoftSPI, .print = mp_machine_soft_spi_print, - .make_new = mp_machine_spi_make_new, // delegate to master constructor + .make_new = mp_machine_soft_spi_make_new, .protocol = &mp_machine_soft_spi_p, .locals_dict = (mp_obj_dict_t *)&mp_machine_spi_locals_dict, }; diff --git a/extmod/machine_spi.h b/extmod/machine_spi.h index db21e1cd31919..ca92c719a8b5a 100644 --- a/extmod/machine_spi.h +++ b/extmod/machine_spi.h @@ -30,6 +30,19 @@ #include "py/mphal.h" #include "drivers/bus/spi.h" +// Temporary support for legacy construction of SoftSPI via SPI type. +#define MP_MACHINE_SPI_CHECK_FOR_LEGACY_SOFTSPI_CONSTRUCTION(n_args, n_kw, all_args) \ + do { \ + if (n_args == 0 || all_args[0] == MP_OBJ_NEW_SMALL_INT(-1)) { \ + mp_print_str(MICROPY_ERROR_PRINTER, "Warning: SPI(-1, ...) is deprecated, use SoftSPI(...) instead\n"); \ + if (n_args != 0) { \ + --n_args; \ + ++all_args; \ + } \ + return mp_machine_soft_spi_type.make_new(&mp_machine_soft_spi_type, n_args, n_kw, all_args); \ + } \ + } while (0) + // SPI protocol typedef struct _mp_machine_spi_p_t { void (*init)(mp_obj_base_t *obj, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index 7e6abb54909ca..57f69433a1a97 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -4,7 +4,7 @@ * The MIT License (MIT) * * Copyright (c) 2018 Ayke van Laethem - * Copyright (c) 2019 Jim Mussared + * Copyright (c) 2019-2020 Jim Mussared * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -26,14 +26,14 @@ */ #include "py/binary.h" +#include "py/gc.h" #include "py/misc.h" #include "py/mperrno.h" +#include "py/mphal.h" #include "py/obj.h" -#include "py/objstr.h" #include "py/objarray.h" #include "py/qstr.h" #include "py/runtime.h" -#include "py/mphal.h" #include "extmod/modbluetooth.h" #include @@ -57,16 +57,17 @@ STATIC const mp_obj_type_t bluetooth_uuid_type; typedef struct { mp_obj_base_t base; mp_obj_t irq_handler; - uint16_t irq_trigger; bool irq_scheduled; mp_obj_t irq_data_tuple; uint8_t irq_data_addr_bytes[6]; uint16_t irq_data_data_alloc; - uint8_t *irq_data_data_bytes; - mp_obj_str_t irq_data_addr; - mp_obj_str_t irq_data_data; + mp_obj_array_t irq_data_addr; + mp_obj_array_t irq_data_data; mp_obj_bluetooth_uuid_t irq_data_uuid; ringbuf_t ringbuf; + #if MICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK + mp_obj_t irq_read_request_data_tuple; + #endif } mp_obj_bluetooth_ble_t; // TODO: this seems like it could be generic? @@ -178,7 +179,7 @@ STATIC void bluetooth_uuid_print(const mp_print_t *print, mp_obj_t self_in, mp_p (void)kind; mp_obj_bluetooth_uuid_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "UUID%u(%s", self->type * 8, self->type <= 4 ? "0x" : "'"); + mp_printf(print, "UUID(%s", self->type <= 4 ? "0x" : "'"); for (int i = 0; i < self->type; ++i) { if (i == 4 || i == 6 || i == 8 || i == 10) { mp_printf(print, "-"); @@ -249,17 +250,19 @@ STATIC mp_obj_t bluetooth_ble_make_new(const mp_obj_type_t *type, size_t n_args, o->base.type = &bluetooth_ble_type; o->irq_handler = mp_const_none; - o->irq_trigger = 0; // Pre-allocate the event data tuple to prevent needing to allocate in the IRQ handler. o->irq_data_tuple = mp_obj_new_tuple(MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_TUPLE_LEN, NULL); + #if MICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK + // Pre-allocate a separate data tuple for the read request "hard" irq. + o->irq_read_request_data_tuple = mp_obj_new_tuple(2, NULL); + #endif + // Pre-allocated buffers for address, payload and uuid. - o->irq_data_addr.base.type = &mp_type_bytes; - o->irq_data_addr.data = o->irq_data_addr_bytes; + mp_obj_memoryview_init(&o->irq_data_addr, 'B', 0, 0, o->irq_data_addr_bytes); o->irq_data_data_alloc = MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_BYTES_LEN(MICROPY_PY_BLUETOOTH_RINGBUF_SIZE); - o->irq_data_data.base.type = &mp_type_bytes; - o->irq_data_data.data = m_new(uint8_t, o->irq_data_data_alloc); + mp_obj_memoryview_init(&o->irq_data_data, 'B', 0, 0, m_new(uint8_t, o->irq_data_data_alloc)); o->irq_data_uuid.base.type = &bluetooth_uuid_type; // Allocate the default ringbuf. @@ -301,12 +304,16 @@ STATIC mp_obj_t bluetooth_ble_config(size_t n_args, const mp_obj_t *args, mp_map return mp_obj_new_bytes(buf, len); } case MP_QSTR_mac: { + uint8_t addr_type; uint8_t addr[6]; - mp_bluetooth_get_device_addr(addr); - return mp_obj_new_bytes(addr, MP_ARRAY_SIZE(addr)); + mp_bluetooth_get_current_address(&addr_type, addr); + mp_obj_t items[] = { MP_OBJ_NEW_SMALL_INT(addr_type), mp_obj_new_bytes(addr, MP_ARRAY_SIZE(addr)) }; + return mp_obj_new_tuple(2, items); } case MP_QSTR_rxbuf: return mp_obj_new_int(self->ringbuf.size); + case MP_QSTR_mtu: + return mp_obj_new_int(mp_bluetooth_get_preferred_mtu()); default: mp_raise_ValueError(MP_ERROR_TEXT("unknown config param")); } @@ -323,8 +330,7 @@ STATIC mp_obj_t bluetooth_ble_config(size_t n_args, const mp_obj_t *args, mp_map case MP_QSTR_gap_name: { mp_buffer_info_t bufinfo; mp_get_buffer_raise(e->value, &bufinfo, MP_BUFFER_READ); - int ret = mp_bluetooth_gap_set_device_name(bufinfo.buf, bufinfo.len); - bluetooth_handle_errno(ret); + bluetooth_handle_errno(mp_bluetooth_gap_set_device_name(bufinfo.buf, bufinfo.len)); break; } case MP_QSTR_rxbuf: { @@ -342,7 +348,7 @@ STATIC mp_obj_t bluetooth_ble_config(size_t n_args, const mp_obj_t *args, mp_map // Get old buffer sizes and pointers uint8_t *old_ringbuf_buf = self->ringbuf.buf; size_t old_ringbuf_alloc = self->ringbuf.size; - uint8_t *old_irq_data_buf = (uint8_t *)self->irq_data_data.data; + uint8_t *old_irq_data_buf = (uint8_t *)self->irq_data_data.items; size_t old_irq_data_alloc = self->irq_data_data_alloc; // Atomically update the ringbuf and irq data @@ -352,7 +358,7 @@ STATIC mp_obj_t bluetooth_ble_config(size_t n_args, const mp_obj_t *args, mp_map self->ringbuf.iget = 0; self->ringbuf.iput = 0; self->irq_data_data_alloc = irq_data_alloc; - self->irq_data_data.data = irq_data; + self->irq_data_data.items = irq_data; MICROPY_PY_BLUETOOTH_EXIT // Free old buffers @@ -360,6 +366,16 @@ STATIC mp_obj_t bluetooth_ble_config(size_t n_args, const mp_obj_t *args, mp_map m_del(uint8_t, old_irq_data_buf, old_irq_data_alloc); break; } + case MP_QSTR_mtu: { + mp_int_t mtu = mp_obj_get_int(e->value); + bluetooth_handle_errno(mp_bluetooth_set_preferred_mtu(mtu)); + break; + } + case MP_QSTR_addr_mode: { + mp_int_t addr_mode = mp_obj_get_int(e->value); + mp_bluetooth_set_address_mode(addr_mode); + break; + } default: mp_raise_ValueError(MP_ERROR_TEXT("unknown config param")); } @@ -371,29 +387,21 @@ STATIC mp_obj_t bluetooth_ble_config(size_t n_args, const mp_obj_t *args, mp_map } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(bluetooth_ble_config_obj, 1, bluetooth_ble_config); -STATIC mp_obj_t bluetooth_ble_irq(size_t n_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 | MP_ARG_REQUIRED, {.u_rom_obj = MP_ROM_NONE} }, - { MP_QSTR_trigger, MP_ARG_INT, {.u_int = MP_BLUETOOTH_IRQ_ALL} }, - }; - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - mp_obj_t callback = args[ARG_handler].u_obj; - if (callback != mp_const_none && !mp_obj_is_callable(callback)) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid callback")); +STATIC mp_obj_t bluetooth_ble_irq(mp_obj_t self_in, mp_obj_t handler_in) { + (void)self_in; + if (handler_in != mp_const_none && !mp_obj_is_callable(handler_in)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid handler")); } // Update the callback. MICROPY_PY_BLUETOOTH_ENTER mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - o->irq_handler = callback; - o->irq_trigger = args[ARG_trigger].u_int; + o->irq_handler = handler_in; MICROPY_PY_BLUETOOTH_EXIT return mp_const_none; } -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(bluetooth_ble_irq_obj, 1, bluetooth_ble_irq); +STATIC MP_DEFINE_CONST_FUN_OBJ_2(bluetooth_ble_irq_obj, bluetooth_ble_irq); // ---------------------------------------------------------------------------- // Bluetooth object: GAP @@ -605,6 +613,7 @@ STATIC mp_obj_t bluetooth_ble_gap_scan(size_t n_args, const mp_obj_t *args) { mp_int_t duration_ms = 0; mp_int_t interval_us = 1280000; mp_int_t window_us = 11250; + bool active_scan = false; if (n_args > 1) { if (args[1] == mp_const_none) { // scan(None) --> stop scan. @@ -615,12 +624,15 @@ STATIC mp_obj_t bluetooth_ble_gap_scan(size_t n_args, const mp_obj_t *args) { interval_us = mp_obj_get_int(args[2]); if (n_args > 3) { window_us = mp_obj_get_int(args[3]); + if (n_args > 4) { + active_scan = mp_obj_is_true(args[4]); + } } } } - return bluetooth_handle_errno(mp_bluetooth_gap_scan_start(duration_ms, interval_us, window_us)); + return bluetooth_handle_errno(mp_bluetooth_gap_scan_start(duration_ms, interval_us, window_us, active_scan)); } -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gap_scan_obj, 1, 4, bluetooth_ble_gap_scan); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gap_scan_obj, 1, 5, bluetooth_ble_gap_scan); #endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE STATIC mp_obj_t bluetooth_ble_gap_disconnect(mp_obj_t self_in, mp_obj_t conn_handle_in) { @@ -664,13 +676,12 @@ STATIC mp_obj_t bluetooth_ble_gatts_notify(size_t n_args, const mp_obj_t *args) mp_int_t conn_handle = mp_obj_get_int(args[1]); mp_int_t value_handle = mp_obj_get_int(args[2]); - if (n_args == 4) { + if (n_args == 4 && args[3] != mp_const_none) { mp_buffer_info_t bufinfo = {0}; mp_get_buffer_raise(args[3], &bufinfo, MP_BUFFER_READ); - size_t len = bufinfo.len; - int err = mp_bluetooth_gatts_notify_send(conn_handle, value_handle, bufinfo.buf, &len); + int err = mp_bluetooth_gatts_notify_send(conn_handle, value_handle, bufinfo.buf, bufinfo.len); bluetooth_handle_errno(err); - return MP_OBJ_NEW_SMALL_INT(len); + return mp_const_none; } else { int err = mp_bluetooth_gatts_notify(conn_handle, value_handle); return bluetooth_handle_errno(err); @@ -678,6 +689,16 @@ STATIC mp_obj_t bluetooth_ble_gatts_notify(size_t n_args, const mp_obj_t *args) } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gatts_notify_obj, 3, 4, bluetooth_ble_gatts_notify); +STATIC mp_obj_t bluetooth_ble_gatts_indicate(mp_obj_t self_in, mp_obj_t conn_handle_in, mp_obj_t value_handle_in) { + (void)self_in; + mp_int_t conn_handle = mp_obj_get_int(conn_handle_in); + mp_int_t value_handle = mp_obj_get_int(value_handle_in); + + int err = mp_bluetooth_gatts_indicate(conn_handle, value_handle); + return bluetooth_handle_errno(err); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(bluetooth_ble_gatts_indicate_obj, bluetooth_ble_gatts_indicate); + STATIC mp_obj_t bluetooth_ble_gatts_set_buffer(size_t n_args, const mp_obj_t *args) { mp_int_t value_handle = mp_obj_get_int(args[1]); mp_int_t len = mp_obj_get_int(args[2]); @@ -692,21 +713,33 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gatts_set_buffer_obj, 3 #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE -STATIC mp_obj_t bluetooth_ble_gattc_discover_services(mp_obj_t self_in, mp_obj_t conn_handle_in) { - (void)self_in; - mp_int_t conn_handle = mp_obj_get_int(conn_handle_in); - return bluetooth_handle_errno(mp_bluetooth_gattc_discover_primary_services(conn_handle)); +STATIC mp_obj_t bluetooth_ble_gattc_discover_services(size_t n_args, const mp_obj_t *args) { + mp_int_t conn_handle = mp_obj_get_int(args[1]); + mp_obj_bluetooth_uuid_t *uuid = NULL; + if (n_args == 3 && args[2] != mp_const_none) { + if (!mp_obj_is_type(args[2], &bluetooth_uuid_type)) { + mp_raise_TypeError(MP_ERROR_TEXT("UUID")); + } + uuid = MP_OBJ_TO_PTR(args[2]); + } + return bluetooth_handle_errno(mp_bluetooth_gattc_discover_primary_services(conn_handle, uuid)); } -STATIC MP_DEFINE_CONST_FUN_OBJ_2(bluetooth_ble_gattc_discover_services_obj, bluetooth_ble_gattc_discover_services); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gattc_discover_services_obj, 2, 3, bluetooth_ble_gattc_discover_services); STATIC mp_obj_t bluetooth_ble_gattc_discover_characteristics(size_t n_args, const mp_obj_t *args) { - (void)n_args; mp_int_t conn_handle = mp_obj_get_int(args[1]); mp_int_t start_handle = mp_obj_get_int(args[2]); mp_int_t end_handle = mp_obj_get_int(args[3]); - return bluetooth_handle_errno(mp_bluetooth_gattc_discover_characteristics(conn_handle, start_handle, end_handle)); + mp_obj_bluetooth_uuid_t *uuid = NULL; + if (n_args == 5 && args[4] != mp_const_none) { + if (!mp_obj_is_type(args[4], &bluetooth_uuid_type)) { + mp_raise_TypeError(MP_ERROR_TEXT("UUID")); + } + uuid = MP_OBJ_TO_PTR(args[4]); + } + return bluetooth_handle_errno(mp_bluetooth_gattc_discover_characteristics(conn_handle, start_handle, end_handle, uuid)); } -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gattc_discover_characteristics_obj, 4, 4, bluetooth_ble_gattc_discover_characteristics); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gattc_discover_characteristics_obj, 4, 5, bluetooth_ble_gattc_discover_characteristics); STATIC mp_obj_t bluetooth_ble_gattc_discover_descriptors(size_t n_args, const mp_obj_t *args) { (void)n_args; @@ -740,6 +773,13 @@ STATIC mp_obj_t bluetooth_ble_gattc_write(size_t n_args, const mp_obj_t *args) { } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gattc_write_obj, 4, 5, bluetooth_ble_gattc_write); +STATIC mp_obj_t bluetooth_ble_gattc_exchange_mtu(mp_obj_t self_in, mp_obj_t conn_handle_in) { + (void)self_in; + uint16_t conn_handle = mp_obj_get_int(conn_handle_in); + return bluetooth_handle_errno(mp_bluetooth_gattc_exchange_mtu(conn_handle)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(bluetooth_ble_gattc_exchange_mtu_obj, bluetooth_ble_gattc_exchange_mtu); + #endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE // ---------------------------------------------------------------------------- @@ -763,6 +803,7 @@ STATIC const mp_rom_map_elem_t bluetooth_ble_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_gatts_read), MP_ROM_PTR(&bluetooth_ble_gatts_read_obj) }, { MP_ROM_QSTR(MP_QSTR_gatts_write), MP_ROM_PTR(&bluetooth_ble_gatts_write_obj) }, { MP_ROM_QSTR(MP_QSTR_gatts_notify), MP_ROM_PTR(&bluetooth_ble_gatts_notify_obj) }, + { MP_ROM_QSTR(MP_QSTR_gatts_indicate), MP_ROM_PTR(&bluetooth_ble_gatts_indicate_obj) }, { MP_ROM_QSTR(MP_QSTR_gatts_set_buffer), MP_ROM_PTR(&bluetooth_ble_gatts_set_buffer_obj) }, #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE // GATT Client (i.e. central/scanner role) @@ -771,6 +812,7 @@ STATIC const mp_rom_map_elem_t bluetooth_ble_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_gattc_discover_descriptors), MP_ROM_PTR(&bluetooth_ble_gattc_discover_descriptors_obj) }, { MP_ROM_QSTR(MP_QSTR_gattc_read), MP_ROM_PTR(&bluetooth_ble_gattc_read_obj) }, { MP_ROM_QSTR(MP_QSTR_gattc_write), MP_ROM_PTR(&bluetooth_ble_gattc_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_gattc_exchange_mtu), MP_ROM_PTR(&bluetooth_ble_gattc_exchange_mtu_obj) }, #endif }; STATIC MP_DEFINE_CONST_DICT(bluetooth_ble_locals_dict, bluetooth_ble_locals_dict_table); @@ -789,6 +831,7 @@ STATIC const mp_rom_map_elem_t mp_module_bluetooth_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_FLAG_READ), MP_ROM_INT(MP_BLUETOOTH_CHARACTERISTIC_FLAG_READ) }, { MP_ROM_QSTR(MP_QSTR_FLAG_WRITE), MP_ROM_INT(MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE) }, { MP_ROM_QSTR(MP_QSTR_FLAG_NOTIFY), MP_ROM_INT(MP_BLUETOOTH_CHARACTERISTIC_FLAG_NOTIFY) }, + { MP_ROM_QSTR(MP_QSTR_FLAG_INDICATE), MP_ROM_INT(MP_BLUETOOTH_CHARACTERISTIC_FLAG_INDICATE) }, { MP_ROM_QSTR(MP_QSTR_FLAG_WRITE_NO_RESPONSE), MP_ROM_INT(MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE_NO_RESPONSE) }, }; @@ -803,7 +846,7 @@ const mp_obj_module_t mp_module_ubluetooth = { #include -STATIC void ringbuf_extract(ringbuf_t *ringbuf, mp_obj_tuple_t *data_tuple, size_t n_u16, size_t n_u8, mp_obj_str_t *bytes_addr, size_t n_i8, mp_obj_bluetooth_uuid_t *uuid, mp_obj_str_t *bytes_data) { +STATIC void ringbuf_extract(ringbuf_t *ringbuf, mp_obj_tuple_t *data_tuple, size_t n_u16, size_t n_u8, mp_obj_array_t *bytes_addr, size_t n_i8, mp_obj_bluetooth_uuid_t *uuid, mp_obj_array_t *bytes_data) { assert(ringbuf_avail(ringbuf) >= n_u16 * 2 + n_u8 + (bytes_addr ? 6 : 0) + n_i8 + (uuid ? 1 : 0) + (bytes_data ? 1 : 0)); size_t j = 0; @@ -816,8 +859,7 @@ STATIC void ringbuf_extract(ringbuf_t *ringbuf, mp_obj_tuple_t *data_tuple, size if (bytes_addr) { bytes_addr->len = 6; for (size_t i = 0; i < bytes_addr->len; ++i) { - // cast away const, this is actually bt->irq_addr_bytes. - ((uint8_t *)bytes_addr->data)[i] = ringbuf_get(ringbuf); + ((uint8_t *)bytes_addr->items)[i] = ringbuf_get(ringbuf); } data_tuple->items[j++] = MP_OBJ_FROM_PTR(bytes_addr); } @@ -825,18 +867,19 @@ STATIC void ringbuf_extract(ringbuf_t *ringbuf, mp_obj_tuple_t *data_tuple, size // Note the int8_t got packed into the ringbuf as a uint8_t. data_tuple->items[j++] = MP_OBJ_NEW_SMALL_INT((int8_t)ringbuf_get(ringbuf)); } + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE if (uuid) { ringbuf_get_uuid(ringbuf, uuid); data_tuple->items[j++] = MP_OBJ_FROM_PTR(uuid); } + #endif // The code that enqueues into the ringbuf should ensure that it doesn't // put more than bt->irq_data_data_alloc bytes into the ringbuf, because - // that's what's available here in bt->irq_data_bytes. + // that's what's available here. if (bytes_data) { - bytes_data->len = ringbuf_get(ringbuf); + bytes_data->len = ringbuf_get16(ringbuf); for (size_t i = 0; i < bytes_data->len; ++i) { - // cast away const, this is actually bt->irq_data_bytes. - ((uint8_t *)bytes_data->data)[i] = ringbuf_get(ringbuf); + ((uint8_t *)bytes_data->items)[i] = ringbuf_get(ringbuf); } data_tuple->items[j++] = MP_OBJ_FROM_PTR(bytes_data); } @@ -854,7 +897,7 @@ STATIC mp_obj_t bluetooth_ble_invoke_irq(mp_obj_t none_in) { for (;;) { MICROPY_PY_BLUETOOTH_ENTER - mp_int_t event = event = ringbuf_get16(&o->ringbuf); + mp_int_t event = ringbuf_get(&o->ringbuf); if (event < 0) { // Nothing available in ringbuf. MICROPY_PY_BLUETOOTH_EXIT @@ -874,11 +917,17 @@ STATIC mp_obj_t bluetooth_ble_invoke_irq(mp_obj_t none_in) { } else if (event == MP_BLUETOOTH_IRQ_GATTS_WRITE) { // conn_handle, value_handle ringbuf_extract(&o->ringbuf, data_tuple, 2, 0, NULL, 0, NULL, NULL); + } else if (event == MP_BLUETOOTH_IRQ_GATTS_INDICATE_DONE) { + // conn_handle, value_handle, status + ringbuf_extract(&o->ringbuf, data_tuple, 2, 1, NULL, 0, NULL, NULL); + } else if (event == MP_BLUETOOTH_IRQ_MTU_EXCHANGED) { + // conn_handle, mtu + ringbuf_extract(&o->ringbuf, data_tuple, 2, 0, NULL, 0, NULL, NULL); #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE } else if (event == MP_BLUETOOTH_IRQ_SCAN_RESULT) { // addr_type, addr, adv_type, rssi, adv_data ringbuf_extract(&o->ringbuf, data_tuple, 0, 1, &o->irq_data_addr, 2, NULL, &o->irq_data_data); - } else if (event == MP_BLUETOOTH_IRQ_SCAN_COMPLETE) { + } else if (event == MP_BLUETOOTH_IRQ_SCAN_DONE) { // No params required. data_tuple->len = 0; } else if (event == MP_BLUETOOTH_IRQ_GATTC_SERVICE_RESULT) { @@ -890,10 +939,13 @@ STATIC mp_obj_t bluetooth_ble_invoke_irq(mp_obj_t none_in) { } else if (event == MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_RESULT) { // conn_handle, handle, uuid ringbuf_extract(&o->ringbuf, data_tuple, 2, 0, NULL, 0, &o->irq_data_uuid, NULL); + } else if (event == MP_BLUETOOTH_IRQ_GATTC_SERVICE_DONE || event == MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_DONE || event == MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_DONE) { + // conn_handle, status + ringbuf_extract(&o->ringbuf, data_tuple, 2, 0, NULL, 0, NULL, NULL); } else if (event == MP_BLUETOOTH_IRQ_GATTC_READ_RESULT || event == MP_BLUETOOTH_IRQ_GATTC_NOTIFY || event == MP_BLUETOOTH_IRQ_GATTC_INDICATE) { // conn_handle, value_handle, data ringbuf_extract(&o->ringbuf, data_tuple, 2, 0, NULL, 0, NULL, &o->irq_data_data); - } else if (event == MP_BLUETOOTH_IRQ_GATTC_WRITE_STATUS) { + } else if (event == MP_BLUETOOTH_IRQ_GATTC_READ_DONE || event == MP_BLUETOOTH_IRQ_GATTC_WRITE_DONE) { // conn_handle, value_handle, status ringbuf_extract(&o->ringbuf, data_tuple, 3, 0, NULL, 0, NULL, NULL); #endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE @@ -915,23 +967,24 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(bluetooth_ble_invoke_irq_obj, bluetooth_ble_inv // Callbacks are called in interrupt context (i.e. can't allocate), so we need to push the data // into the ringbuf and schedule the callback via mp_sched_schedule. -STATIC bool enqueue_irq(mp_obj_bluetooth_ble_t *o, size_t len, uint16_t event) { - if (!o || !(o->irq_trigger & event) || o->irq_handler == mp_const_none) { +STATIC bool enqueue_irq(mp_obj_bluetooth_ble_t *o, size_t len, uint8_t event) { + if (!o || o->irq_handler == mp_const_none) { return false; } - if (ringbuf_free(&o->ringbuf) < len + 2) { + // Check if there is enough room for . + if (ringbuf_free(&o->ringbuf) < len + 1) { // Ringbuffer doesn't have room (and is therefore non-empty). // If this is another scan result, or the front of the ringbuffer isn't a scan result, then nothing to do. - if (event == MP_BLUETOOTH_IRQ_SCAN_RESULT || ringbuf_peek16(&o->ringbuf) != MP_BLUETOOTH_IRQ_SCAN_RESULT) { + if (event == MP_BLUETOOTH_IRQ_SCAN_RESULT || ringbuf_peek(&o->ringbuf) != MP_BLUETOOTH_IRQ_SCAN_RESULT) { return false; } // Front of the queue is a scan result, remove it. // event, addr_type, addr, adv_type, rssi - int n = 2 + 1 + 6 + 1 + 1; + int n = 1 + 1 + 6 + 1 + 1; for (int i = 0; i < n; ++i) { ringbuf_get(&o->ringbuf); } @@ -943,7 +996,7 @@ STATIC bool enqueue_irq(mp_obj_bluetooth_ble_t *o, size_t len, uint16_t event) { } // Append this event, the caller will then append the arguments. - ringbuf_put16(&o->ringbuf, event); + ringbuf_put(&o->ringbuf, event); return true; } @@ -959,7 +1012,7 @@ STATIC void schedule_ringbuf(mp_uint_t atomic_state) { } } -void mp_bluetooth_gap_on_connected_disconnected(uint16_t event, uint16_t conn_handle, uint8_t addr_type, const uint8_t *addr) { +void mp_bluetooth_gap_on_connected_disconnected(uint8_t event, uint16_t conn_handle, uint8_t addr_type, const uint8_t *addr) { MICROPY_PY_BLUETOOTH_ENTER mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); if (enqueue_irq(o, 2 + 1 + 6, event)) { @@ -982,11 +1035,22 @@ void mp_bluetooth_gatts_on_write(uint16_t conn_handle, uint16_t value_handle) { schedule_ringbuf(atomic_state); } +void mp_bluetooth_gatts_on_indicate_complete(uint16_t conn_handle, uint16_t value_handle, uint8_t status) { + MICROPY_PY_BLUETOOTH_ENTER + mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); + if (enqueue_irq(o, 2 + 2 + 1, MP_BLUETOOTH_IRQ_GATTS_INDICATE_DONE)) { + ringbuf_put16(&o->ringbuf, conn_handle); + ringbuf_put16(&o->ringbuf, value_handle); + ringbuf_put(&o->ringbuf, status); + } + schedule_ringbuf(atomic_state); +} + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE void mp_bluetooth_gap_on_scan_complete(void) { MICROPY_PY_BLUETOOTH_ENTER mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - if (enqueue_irq(o, 0, MP_BLUETOOTH_IRQ_SCAN_COMPLETE)) { + if (enqueue_irq(o, 0, MP_BLUETOOTH_IRQ_SCAN_DONE)) { } schedule_ringbuf(atomic_state); } @@ -995,7 +1059,7 @@ void mp_bluetooth_gap_on_scan_result(uint8_t addr_type, const uint8_t *addr, uin MICROPY_PY_BLUETOOTH_ENTER mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); data_len = MIN(o->irq_data_data_alloc, data_len); - if (enqueue_irq(o, 1 + 6 + 1 + 1 + 1 + data_len, MP_BLUETOOTH_IRQ_SCAN_RESULT)) { + if (enqueue_irq(o, 1 + 6 + 1 + 1 + 2 + data_len, MP_BLUETOOTH_IRQ_SCAN_RESULT)) { ringbuf_put(&o->ringbuf, addr_type); for (int i = 0; i < 6; ++i) { ringbuf_put(&o->ringbuf, addr[i]); @@ -1004,7 +1068,9 @@ void mp_bluetooth_gap_on_scan_result(uint8_t addr_type, const uint8_t *addr, uin ringbuf_put(&o->ringbuf, adv_type); // Note conversion of int8_t rssi to uint8_t. Must un-convert on the way out. ringbuf_put(&o->ringbuf, (uint8_t)rssi); - ringbuf_put(&o->ringbuf, data_len); + // Length field is 16-bit. + data_len = MIN(UINT16_MAX, data_len); + ringbuf_put16(&o->ringbuf, data_len); for (size_t i = 0; i < data_len; ++i) { ringbuf_put(&o->ringbuf, data[i]); } @@ -1048,15 +1114,27 @@ void mp_bluetooth_gattc_on_descriptor_result(uint16_t conn_handle, uint16_t hand schedule_ringbuf(atomic_state); } -size_t mp_bluetooth_gattc_on_data_available_start(uint16_t event, uint16_t conn_handle, uint16_t value_handle, size_t data_len, mp_uint_t *atomic_state_out) { +void mp_bluetooth_gattc_on_discover_complete(uint8_t event, uint16_t conn_handle, uint16_t status) { + MICROPY_PY_BLUETOOTH_ENTER + mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); + if (enqueue_irq(o, 2 + 2, event)) { + ringbuf_put16(&o->ringbuf, conn_handle); + ringbuf_put16(&o->ringbuf, status); + } + schedule_ringbuf(atomic_state); +} + +size_t mp_bluetooth_gattc_on_data_available_start(uint8_t event, uint16_t conn_handle, uint16_t value_handle, size_t data_len, mp_uint_t *atomic_state_out) { MICROPY_PY_BLUETOOTH_ENTER *atomic_state_out = atomic_state; mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); data_len = MIN(o->irq_data_data_alloc, data_len); - if (enqueue_irq(o, 2 + 2 + 1 + data_len, event)) { + if (enqueue_irq(o, 2 + 2 + 2 + data_len, event)) { ringbuf_put16(&o->ringbuf, conn_handle); ringbuf_put16(&o->ringbuf, value_handle); - ringbuf_put(&o->ringbuf, data_len); + // Length field is 16-bit. + data_len = MIN(UINT16_MAX, data_len); + ringbuf_put16(&o->ringbuf, data_len); return data_len; } else { return 0; @@ -1074,10 +1152,10 @@ void mp_bluetooth_gattc_on_data_available_end(mp_uint_t atomic_state) { schedule_ringbuf(atomic_state); } -void mp_bluetooth_gattc_on_write_status(uint16_t conn_handle, uint16_t value_handle, uint16_t status) { +void mp_bluetooth_gattc_on_read_write_status(uint8_t event, uint16_t conn_handle, uint16_t value_handle, uint16_t status) { MICROPY_PY_BLUETOOTH_ENTER mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - if (enqueue_irq(o, 2 + 2 + 2, MP_BLUETOOTH_IRQ_GATTC_WRITE_STATUS)) { + if (enqueue_irq(o, 2 + 2 + 2, event)) { ringbuf_put16(&o->ringbuf, conn_handle); ringbuf_put16(&o->ringbuf, value_handle); ringbuf_put16(&o->ringbuf, status); @@ -1092,13 +1170,23 @@ void mp_bluetooth_gattc_on_write_status(uint16_t conn_handle, uint16_t value_han // On ESP32, for example, this is not the case. bool mp_bluetooth_gatts_on_read_request(uint16_t conn_handle, uint16_t value_handle) { mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - if ((o->irq_trigger & MP_BLUETOOTH_IRQ_GATTS_READ_REQUEST) && o->irq_handler != mp_const_none) { - // Use pre-allocated tuple because this is a hard IRQ. - mp_obj_tuple_t *data = MP_OBJ_TO_PTR(o->irq_data_tuple); + if (o->irq_handler != mp_const_none) { + // When executing code within a handler we must lock the scheduler to + // prevent any scheduled callbacks from running, and lock the GC to + // prevent any memory allocations. + mp_sched_lock(); + gc_lock(); + + // Use pre-allocated tuple distinct to the one used by the "soft" IRQs. + mp_obj_tuple_t *data = MP_OBJ_TO_PTR(o->irq_read_request_data_tuple); data->items[0] = MP_OBJ_NEW_SMALL_INT(conn_handle); data->items[1] = MP_OBJ_NEW_SMALL_INT(value_handle); data->len = 2; - mp_obj_t irq_ret = mp_call_function_2_protected(o->irq_handler, MP_OBJ_NEW_SMALL_INT(MP_BLUETOOTH_IRQ_GATTS_READ_REQUEST), o->irq_data_tuple); + mp_obj_t irq_ret = mp_call_function_2_protected(o->irq_handler, MP_OBJ_NEW_SMALL_INT(MP_BLUETOOTH_IRQ_GATTS_READ_REQUEST), o->irq_read_request_data_tuple); + + gc_unlock(); + mp_sched_unlock(); + // If the IRQ handler explicitly returned false, then deny the read. Otherwise if it returns None/True, allow it. return irq_ret != MP_OBJ_NULL && (irq_ret == mp_const_none || mp_obj_is_true(irq_ret)); } else { @@ -1108,6 +1196,16 @@ bool mp_bluetooth_gatts_on_read_request(uint16_t conn_handle, uint16_t value_han } #endif +void mp_bluetooth_gatts_on_mtu_exchanged(uint16_t conn_handle, uint16_t value) { + MICROPY_PY_BLUETOOTH_ENTER + mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); + if (enqueue_irq(o, 2 + 2, MP_BLUETOOTH_IRQ_MTU_EXCHANGED)) { + ringbuf_put16(&o->ringbuf, conn_handle); + ringbuf_put16(&o->ringbuf, value); + } + schedule_ringbuf(atomic_state); +} + void mp_bluetooth_gatts_db_create_entry(mp_gatts_db_t db, uint16_t handle, size_t len) { mp_map_elem_t *elem = mp_map_lookup(db, MP_OBJ_NEW_SMALL_INT(handle), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); mp_bluetooth_gatts_db_entry_t *entry = m_new(mp_bluetooth_gatts_db_entry_t, 1); @@ -1127,47 +1225,58 @@ mp_bluetooth_gatts_db_entry_t *mp_bluetooth_gatts_db_lookup(mp_gatts_db_t db, ui } int mp_bluetooth_gatts_db_read(mp_gatts_db_t db, uint16_t handle, uint8_t **value, size_t *value_len) { + MICROPY_PY_BLUETOOTH_ENTER mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(db, handle); - if (!entry) { - return MP_EINVAL; - } - - *value = entry->data; - *value_len = entry->data_len; - if (entry->append) { - entry->data_len = 0; + if (entry) { + *value = entry->data; + *value_len = entry->data_len; + if (entry->append) { + entry->data_len = 0; + } } - - return 0; + MICROPY_PY_BLUETOOTH_EXIT + return entry ? 0 : MP_EINVAL; } int mp_bluetooth_gatts_db_write(mp_gatts_db_t db, uint16_t handle, const uint8_t *value, size_t value_len) { + MICROPY_PY_BLUETOOTH_ENTER mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(db, handle); - if (!entry) { - return MP_EINVAL; - } + if (entry) { + if (value_len > entry->data_alloc) { + uint8_t *data = m_new_maybe(uint8_t, value_len); + if (data) { + entry->data = data; + entry->data_alloc = value_len; + } else { + MICROPY_PY_BLUETOOTH_EXIT + return MP_ENOMEM; + } + } - if (value_len > entry->data_alloc) { - entry->data = m_new(uint8_t, value_len); - entry->data_alloc = value_len; + memcpy(entry->data, value, value_len); + entry->data_len = value_len; } - - memcpy(entry->data, value, value_len); - entry->data_len = value_len; - - return 0; + MICROPY_PY_BLUETOOTH_EXIT + return entry ? 0 : MP_EINVAL; } int mp_bluetooth_gatts_db_resize(mp_gatts_db_t db, uint16_t handle, size_t len, bool append) { + MICROPY_PY_BLUETOOTH_ENTER mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(db, handle); - if (!entry) { - return MP_EINVAL; + if (entry) { + uint8_t *data = m_renew_maybe(uint8_t, entry->data, entry->data_alloc, len, true); + if (data) { + entry->data = data; + entry->data_alloc = len; + entry->data_len = 0; + entry->append = append; + } else { + MICROPY_PY_BLUETOOTH_EXIT + return MP_ENOMEM; + } } - entry->data = m_renew(uint8_t, entry->data, entry->data_alloc, len); - entry->data_alloc = len; - entry->data_len = 0; - entry->append = append; - return 0; + MICROPY_PY_BLUETOOTH_EXIT + return entry ? 0 : MP_EINVAL; } #endif // MICROPY_PY_BLUETOOTH diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index 4e658a7a0a426..2df4c3c25f17b 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -4,7 +4,7 @@ * The MIT License (MIT) * * Copyright (c) 2018 Ayke van Laethem - * Copyright (c) 2019 Jim Mussared + * Copyright (c) 2019-2020 Jim Mussared * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -68,6 +68,7 @@ #define MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE_NO_RESPONSE (1 << 2) #define MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE (1 << 3) #define MP_BLUETOOTH_CHARACTERISTIC_FLAG_NOTIFY (1 << 4) +#define MP_BLUETOOTH_CHARACTERISTIC_FLAG_INDICATE (1 << 5) // For mp_bluetooth_gattc_write, the mode parameter #define MP_BLUETOOTH_WRITE_MODE_NO_RESPONSE (0) @@ -78,58 +79,60 @@ #define MP_BLUETOOTH_UUID_TYPE_32 (4) #define MP_BLUETOOTH_UUID_TYPE_128 (16) -// Address types (for the addr_type params). -// Ports will need to map these to their own values. -#define MP_BLUETOOTH_ADDR_PUBLIC (0x00) // Public (identity) address. (Same as NimBLE and NRF SD) -#define MP_BLUETOOTH_ADDR_RANDOM_STATIC (0x01) // Random static (identity) address. (Same as NimBLE and NRF SD) -#define MP_BLUETOOTH_ADDR_PUBLIC_ID (0x02) // (Same as NimBLE) -#define MP_BLUETOOTH_ADDR_RANDOM_ID (0x03) // (Same as NimBLE) -#define MP_BLUETOOTH_ADDR_RANDOM_PRIVATE_RESOLVABLE (0x12) // Random private resolvable address. (NRF SD 0x02) -#define MP_BLUETOOTH_ADDR_RANDOM_PRIVATE_NON_RESOLVABLE (0x13) // Random private non-resolvable address. (NRF SD 0x03) - // Event codes for the IRQ handler. -// Can also be combined to pass to the trigger param to select which events you -// are interested in. -// Note this is currently stored in a uint16_t (in irq_trigger, and the event -// arg to the irq handler), so one spare value remaining. -#define MP_BLUETOOTH_IRQ_CENTRAL_CONNECT (1 << 0) -#define MP_BLUETOOTH_IRQ_CENTRAL_DISCONNECT (1 << 1) -#define MP_BLUETOOTH_IRQ_GATTS_WRITE (1 << 2) -#define MP_BLUETOOTH_IRQ_GATTS_READ_REQUEST (1 << 3) -#define MP_BLUETOOTH_IRQ_SCAN_RESULT (1 << 4) -#define MP_BLUETOOTH_IRQ_SCAN_COMPLETE (1 << 5) -#define MP_BLUETOOTH_IRQ_PERIPHERAL_CONNECT (1 << 6) -#define MP_BLUETOOTH_IRQ_PERIPHERAL_DISCONNECT (1 << 7) -#define MP_BLUETOOTH_IRQ_GATTC_SERVICE_RESULT (1 << 8) -#define MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_RESULT (1 << 9) -#define MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_RESULT (1 << 10) -#define MP_BLUETOOTH_IRQ_GATTC_READ_RESULT (1 << 11) -#define MP_BLUETOOTH_IRQ_GATTC_WRITE_STATUS (1 << 12) -#define MP_BLUETOOTH_IRQ_GATTC_NOTIFY (1 << 13) -#define MP_BLUETOOTH_IRQ_GATTC_INDICATE (1 << 14) -#define MP_BLUETOOTH_IRQ_ALL (0xffff) +#define MP_BLUETOOTH_IRQ_CENTRAL_CONNECT (1) +#define MP_BLUETOOTH_IRQ_CENTRAL_DISCONNECT (2) +#define MP_BLUETOOTH_IRQ_GATTS_WRITE (3) +#define MP_BLUETOOTH_IRQ_GATTS_READ_REQUEST (4) +#define MP_BLUETOOTH_IRQ_SCAN_RESULT (5) +#define MP_BLUETOOTH_IRQ_SCAN_DONE (6) +#define MP_BLUETOOTH_IRQ_PERIPHERAL_CONNECT (7) +#define MP_BLUETOOTH_IRQ_PERIPHERAL_DISCONNECT (8) +#define MP_BLUETOOTH_IRQ_GATTC_SERVICE_RESULT (9) +#define MP_BLUETOOTH_IRQ_GATTC_SERVICE_DONE (10) +#define MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_RESULT (11) +#define MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_DONE (12) +#define MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_RESULT (13) +#define MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_DONE (14) +#define MP_BLUETOOTH_IRQ_GATTC_READ_RESULT (15) +#define MP_BLUETOOTH_IRQ_GATTC_READ_DONE (16) +#define MP_BLUETOOTH_IRQ_GATTC_WRITE_DONE (17) +#define MP_BLUETOOTH_IRQ_GATTC_NOTIFY (18) +#define MP_BLUETOOTH_IRQ_GATTC_INDICATE (19) +#define MP_BLUETOOTH_IRQ_GATTS_INDICATE_DONE (20) +#define MP_BLUETOOTH_IRQ_MTU_EXCHANGED (21) + +#define MP_BLUETOOTH_ADDRESS_MODE_PUBLIC (0) +#define MP_BLUETOOTH_ADDRESS_MODE_RANDOM (1) +#define MP_BLUETOOTH_ADDRESS_MODE_RPA (2) +#define MP_BLUETOOTH_ADDRESS_MODE_NRPA (3) /* These aren't included in the module for space reasons, but can be used in your Python code if necessary. from micropython import const -_IRQ_CENTRAL_CONNECT = const(1 << 0) -_IRQ_CENTRAL_DISCONNECT = const(1 << 1) -_IRQ_GATTS_WRITE = const(1 << 2) -_IRQ_GATTS_READ_REQUEST = const(1 << 3) -_IRQ_SCAN_RESULT = const(1 << 4) -_IRQ_SCAN_COMPLETE = const(1 << 5) -_IRQ_PERIPHERAL_CONNECT = const(1 << 6) -_IRQ_PERIPHERAL_DISCONNECT = const(1 << 7) -_IRQ_GATTC_SERVICE_RESULT = const(1 << 8) -_IRQ_GATTC_CHARACTERISTIC_RESULT = const(1 << 9) -_IRQ_GATTC_DESCRIPTOR_RESULT = const(1 << 10) -_IRQ_GATTC_READ_RESULT = const(1 << 11) -_IRQ_GATTC_WRITE_STATUS = const(1 << 12) -_IRQ_GATTC_NOTIFY = const(1 << 13) -_IRQ_GATTC_INDICATE = const(1 << 14) -_IRQ_ALL = const(0xffff) +_IRQ_CENTRAL_CONNECT = const(1) +_IRQ_CENTRAL_DISCONNECT = const(2) +_IRQ_GATTS_WRITE = const(3) +_IRQ_GATTS_READ_REQUEST = const(4) +_IRQ_SCAN_RESULT = const(5) +_IRQ_SCAN_DONE = const(6) +_IRQ_PERIPHERAL_CONNECT = const(7) +_IRQ_PERIPHERAL_DISCONNECT = const(8) +_IRQ_GATTC_SERVICE_RESULT = const(9) +_IRQ_GATTC_SERVICE_DONE = const(10) +_IRQ_GATTC_CHARACTERISTIC_RESULT = const(11) +_IRQ_GATTC_CHARACTERISTIC_DONE = const(12) +_IRQ_GATTC_DESCRIPTOR_RESULT = const(13) +_IRQ_GATTC_DESCRIPTOR_DONE = const(14) +_IRQ_GATTC_READ_RESULT = const(15) +_IRQ_GATTC_READ_DONE = const(16) +_IRQ_GATTC_WRITE_DONE = const(17) +_IRQ_GATTC_NOTIFY = const(18) +_IRQ_GATTC_INDICATE = const(19) +_IRQ_GATTS_INDICATE_DONE = const(20) +_IRQ_MTU_EXCHANGED = const(21) */ // Common UUID type. @@ -168,8 +171,11 @@ void mp_bluetooth_deinit(void); // Returns true when the Bluetooth stack is active. bool mp_bluetooth_is_active(void); -// Gets the MAC addr of this device in big-endian format. -void mp_bluetooth_get_device_addr(uint8_t *addr); +// Gets the current address of this device in big-endian format. +void mp_bluetooth_get_current_address(uint8_t *addr_type, uint8_t *addr); + +// Sets the addressing mode to use. +void mp_bluetooth_set_address_mode(uint8_t addr_mode); // Get or set the GAP device name that will be used by service 0x1800, characteristic 0x2a00. size_t mp_bluetooth_gap_get_device_name(const uint8_t **buf); @@ -197,7 +203,7 @@ int mp_bluetooth_gatts_write(uint16_t value_handle, const uint8_t *value, size_t // Notify the central that it should do a read. int mp_bluetooth_gatts_notify(uint16_t conn_handle, uint16_t value_handle); // Notify the central, including a data payload. (Note: does not set the gatts db value). -int mp_bluetooth_gatts_notify_send(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t *value_len); +int mp_bluetooth_gatts_notify_send(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t value_len); // Indicate the central. int mp_bluetooth_gatts_indicate(uint16_t conn_handle, uint16_t value_handle); @@ -208,9 +214,13 @@ int mp_bluetooth_gatts_set_buffer(uint16_t value_handle, size_t len, bool append // Disconnect from a central or peripheral. int mp_bluetooth_gap_disconnect(uint16_t conn_handle); +// Set/get the MTU that we will respond to a MTU exchange with. +int mp_bluetooth_get_preferred_mtu(void); +int mp_bluetooth_set_preferred_mtu(uint16_t mtu); + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE // Start a discovery (scan). Set duration to zero to run continuously. -int mp_bluetooth_gap_scan_start(int32_t duration_ms, int32_t interval_us, int32_t window_us); +int mp_bluetooth_gap_scan_start(int32_t duration_ms, int32_t interval_us, int32_t window_us, bool active_scan); // Stop discovery (if currently active). int mp_bluetooth_gap_scan_stop(void); @@ -219,10 +229,10 @@ int mp_bluetooth_gap_scan_stop(void); int mp_bluetooth_gap_peripheral_connect(uint8_t addr_type, const uint8_t *addr, int32_t duration_ms); // Find all primary services on the connected peripheral. -int mp_bluetooth_gattc_discover_primary_services(uint16_t conn_handle); +int mp_bluetooth_gattc_discover_primary_services(uint16_t conn_handle, const mp_obj_bluetooth_uuid_t *uuid); // Find all characteristics on the specified service on a connected peripheral. -int mp_bluetooth_gattc_discover_characteristics(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle); +int mp_bluetooth_gattc_discover_characteristics(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle, const mp_obj_bluetooth_uuid_t *uuid); // Find all descriptors on the specified characteristic on a connected peripheral. int mp_bluetooth_gattc_discover_descriptors(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle); @@ -232,22 +242,31 @@ int mp_bluetooth_gattc_read(uint16_t conn_handle, uint16_t value_handle); // Write the value to the remote peripheral. int mp_bluetooth_gattc_write(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t *value_len, unsigned int mode); + +// Initiate MTU exchange for a specific connection using the preferred MTU. +int mp_bluetooth_gattc_exchange_mtu(uint16_t conn_handle); #endif ///////////////////////////////////////////////////////////////////////////// // API implemented by modbluetooth (called by port-specific implementations): // Notify modbluetooth that a connection/disconnection event has occurred. -void mp_bluetooth_gap_on_connected_disconnected(uint16_t event, uint16_t conn_handle, uint8_t addr_type, const uint8_t *addr); +void mp_bluetooth_gap_on_connected_disconnected(uint8_t event, uint16_t conn_handle, uint8_t addr_type, const uint8_t *addr); // Call this when a characteristic is written to. void mp_bluetooth_gatts_on_write(uint16_t conn_handle, uint16_t value_handle); +// Call this when an acknowledgment is received for an indication. +void mp_bluetooth_gatts_on_indicate_complete(uint16_t conn_handle, uint16_t value_handle, uint8_t status); + #if MICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK // Call this when a characteristic is read from. Return false to deny the read. bool mp_bluetooth_gatts_on_read_request(uint16_t conn_handle, uint16_t value_handle); #endif +// Call this when an MTU exchange completes. +void mp_bluetooth_gatts_on_mtu_exchanged(uint16_t conn_handle, uint16_t value); + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE // Notify modbluetooth that scan has finished, either timeout, manually, or by some other action (e.g. connecting). void mp_bluetooth_gap_on_scan_complete(void); @@ -264,15 +283,18 @@ void mp_bluetooth_gattc_on_characteristic_result(uint16_t conn_handle, uint16_t // Notify modbluetooth that a descriptor was found. void mp_bluetooth_gattc_on_descriptor_result(uint16_t conn_handle, uint16_t handle, mp_obj_bluetooth_uuid_t *descriptor_uuid); +// Notify modbluetooth that service, characteristic or descriptor discovery has finished. +void mp_bluetooth_gattc_on_discover_complete(uint8_t event, uint16_t conn_handle, uint16_t status); + // Notify modbluetooth that a read has completed with data (or notify/indicate data available, use `event` to disambiguate). // Note: these functions are to be called in a group protected by MICROPY_PY_BLUETOOTH_ENTER/EXIT. // _start returns the number of bytes to submit to the calls to _chunk, followed by a call to _end. -size_t mp_bluetooth_gattc_on_data_available_start(uint16_t event, uint16_t conn_handle, uint16_t value_handle, size_t data_len, mp_uint_t *atomic_state_out); +size_t mp_bluetooth_gattc_on_data_available_start(uint8_t event, uint16_t conn_handle, uint16_t value_handle, size_t data_len, mp_uint_t *atomic_state_out); void mp_bluetooth_gattc_on_data_available_chunk(const uint8_t *data, size_t data_len); void mp_bluetooth_gattc_on_data_available_end(mp_uint_t atomic_state); -// Notify modbluetooth that a write has completed. -void mp_bluetooth_gattc_on_write_status(uint16_t conn_handle, uint16_t value_handle, uint16_t status); +// Notify modbluetooth that a read or write operation has completed. +void mp_bluetooth_gattc_on_read_write_status(uint8_t event, uint16_t conn_handle, uint16_t value_handle, uint16_t status); #endif // For stacks that don't manage attribute value data (currently all of them), helpers diff --git a/extmod/modlwip.c b/extmod/modlwip.c index 216e81749d1c3..1d557a6a84b5a 100644 --- a/extmod/modlwip.c +++ b/extmod/modlwip.c @@ -757,8 +757,11 @@ STATIC mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_ return 0; } } else if (socket->state != STATE_CONNECTED) { - assert(socket->state < 0); - *_errno = error_lookup_table[-socket->state]; + if (socket->state >= STATE_NEW) { + *_errno = MP_ENOTCONN; + } else { + *_errno = error_lookup_table[-socket->state]; + } return -1; } } diff --git a/extmod/moductypes.c b/extmod/moductypes.c index f7d2be1bc1487..811258424a3bf 100644 --- a/extmod/moductypes.c +++ b/extmod/moductypes.c @@ -137,11 +137,7 @@ STATIC void uctypes_struct_print(const mp_print_t *print, mp_obj_t self_in, mp_p (void)kind; mp_obj_uctypes_struct_t *self = MP_OBJ_TO_PTR(self_in); const char *typen = "unk"; - if (mp_obj_is_type(self->desc, &mp_type_dict) - #if MICROPY_PY_COLLECTIONS_ORDEREDDICT - || mp_obj_is_type(self->desc, &mp_type_ordereddict) - #endif - ) { + if (mp_obj_is_dict_or_ordereddict(self->desc)) { typen = "STRUCT"; } else if (mp_obj_is_type(self->desc, &mp_type_tuple)) { mp_obj_tuple_t *t = MP_OBJ_TO_PTR(self->desc); @@ -214,11 +210,7 @@ STATIC mp_uint_t uctypes_struct_agg_size(mp_obj_tuple_t *t, int layout_type, mp_ } STATIC mp_uint_t uctypes_struct_size(mp_obj_t desc_in, int layout_type, mp_uint_t *max_field_size) { - if (!mp_obj_is_type(desc_in, &mp_type_dict) - #if MICROPY_PY_COLLECTIONS_ORDEREDDICT - && !mp_obj_is_type(desc_in, &mp_type_ordereddict) - #endif - ) { + if (!mp_obj_is_dict_or_ordereddict(desc_in)) { if (mp_obj_is_type(desc_in, &mp_type_tuple)) { return uctypes_struct_agg_size((mp_obj_tuple_t *)MP_OBJ_TO_PTR(desc_in), layout_type, max_field_size); } else if (mp_obj_is_small_int(desc_in)) { @@ -418,11 +410,7 @@ STATIC void set_aligned(uint val_type, void *p, mp_int_t index, mp_obj_t val) { STATIC mp_obj_t uctypes_struct_attr_op(mp_obj_t self_in, qstr attr, mp_obj_t set_val) { mp_obj_uctypes_struct_t *self = MP_OBJ_TO_PTR(self_in); - if (!mp_obj_is_type(self->desc, &mp_type_dict) - #if MICROPY_PY_COLLECTIONS_ORDEREDDICT - && !mp_obj_is_type(self->desc, &mp_type_ordereddict) - #endif - ) { + if (!mp_obj_is_dict_or_ordereddict(self->desc)) { mp_raise_TypeError(MP_ERROR_TEXT("struct: no fields")); } diff --git a/extmod/modure.c b/extmod/modure.c index 1847ec288075d..220587b42f9a3 100644 --- a/extmod/modure.c +++ b/extmod/modure.c @@ -53,6 +53,10 @@ typedef struct _mp_obj_match_t { const char *caps[0]; } mp_obj_match_t; +STATIC mp_obj_t mod_re_compile(size_t n_args, const mp_obj_t *args); +#if !MICROPY_ENABLE_DYNRUNTIME +STATIC const mp_obj_type_t re_type; +#endif STATIC void match_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { (void)kind; @@ -175,7 +179,12 @@ STATIC void re_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t STATIC mp_obj_t ure_exec(bool is_anchored, uint n_args, const mp_obj_t *args) { (void)n_args; - mp_obj_re_t *self = MP_OBJ_TO_PTR(args[0]); + mp_obj_re_t *self; + if (mp_obj_is_type(args[0], &re_type)) { + self = MP_OBJ_TO_PTR(args[0]); + } else { + self = MP_OBJ_TO_PTR(mod_re_compile(1, args)); + } Subject subj; size_t len; subj.begin = mp_obj_str_get_data(args[1], &len); @@ -253,8 +262,13 @@ MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(re_split_obj, 2, 3, re_split); #if MICROPY_PY_URE_SUB -STATIC mp_obj_t re_sub_helper(mp_obj_t self_in, size_t n_args, const mp_obj_t *args) { - mp_obj_re_t *self = MP_OBJ_TO_PTR(self_in); +STATIC mp_obj_t re_sub_helper(size_t n_args, const mp_obj_t *args) { + mp_obj_re_t *self; + if (mp_obj_is_type(args[0], &re_type)) { + self = MP_OBJ_TO_PTR(args[0]); + } else { + self = MP_OBJ_TO_PTR(mod_re_compile(1, args)); + } mp_obj_t replace = args[1]; mp_obj_t where = args[2]; mp_int_t count = 0; @@ -329,6 +343,9 @@ STATIC mp_obj_t re_sub_helper(mp_obj_t self_in, size_t n_args, const mp_obj_t *a const char *end_match = match->caps[match_no * 2 + 1]; vstr_add_strn(&vstr_return, start_match, end_match - start_match); } + } else if (*repl == '\\') { + // Add the \ character + vstr_add_byte(&vstr_return, *repl++); } } else { // Just add the current byte from the replacement string @@ -358,10 +375,7 @@ STATIC mp_obj_t re_sub_helper(mp_obj_t self_in, size_t n_args, const mp_obj_t *a return mp_obj_new_str_from_vstr(mp_obj_get_type(where), &vstr_return); } -STATIC mp_obj_t re_sub(size_t n_args, const mp_obj_t *args) { - return re_sub_helper(args[0], n_args, args); -} -MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(re_sub_obj, 3, 5, re_sub); +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(re_sub_obj, 3, 5, re_sub_helper); #endif @@ -414,41 +428,14 @@ STATIC mp_obj_t mod_re_compile(size_t n_args, const mp_obj_t *args) { } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_re_compile_obj, 1, 2, mod_re_compile); -STATIC mp_obj_t mod_re_exec(bool is_anchored, uint n_args, const mp_obj_t *args) { - (void)n_args; - mp_obj_t self = mod_re_compile(1, args); - - const mp_obj_t args2[] = {self, args[1]}; - mp_obj_t match = ure_exec(is_anchored, 2, args2); - return match; -} - -STATIC mp_obj_t mod_re_match(size_t n_args, const mp_obj_t *args) { - return mod_re_exec(true, n_args, args); -} -MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_re_match_obj, 2, 4, mod_re_match); - -STATIC mp_obj_t mod_re_search(size_t n_args, const mp_obj_t *args) { - return mod_re_exec(false, n_args, args); -} -MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_re_search_obj, 2, 4, mod_re_search); - -#if MICROPY_PY_URE_SUB -STATIC mp_obj_t mod_re_sub(size_t n_args, const mp_obj_t *args) { - mp_obj_t self = mod_re_compile(1, args); - return re_sub_helper(self, n_args, args); -} -MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_re_sub_obj, 3, 5, mod_re_sub); -#endif - #if !MICROPY_ENABLE_DYNRUNTIME STATIC const mp_rom_map_elem_t mp_module_re_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ure) }, { MP_ROM_QSTR(MP_QSTR_compile), MP_ROM_PTR(&mod_re_compile_obj) }, - { MP_ROM_QSTR(MP_QSTR_match), MP_ROM_PTR(&mod_re_match_obj) }, - { MP_ROM_QSTR(MP_QSTR_search), MP_ROM_PTR(&mod_re_search_obj) }, + { MP_ROM_QSTR(MP_QSTR_match), MP_ROM_PTR(&re_match_obj) }, + { MP_ROM_QSTR(MP_QSTR_search), MP_ROM_PTR(&re_search_obj) }, #if MICROPY_PY_URE_SUB - { MP_ROM_QSTR(MP_QSTR_sub), MP_ROM_PTR(&mod_re_sub_obj) }, + { MP_ROM_QSTR(MP_QSTR_sub), MP_ROM_PTR(&re_sub_obj) }, #endif #if MICROPY_PY_URE_DEBUG { MP_ROM_QSTR(MP_QSTR_DEBUG), MP_ROM_INT(FLAG_DEBUG) }, diff --git a/extmod/modussl_axtls.c b/extmod/modussl_axtls.c index 7b0e3cbcbc327..da5941a55b33e 100644 --- a/extmod/modussl_axtls.c +++ b/extmod/modussl_axtls.c @@ -29,6 +29,7 @@ #include "py/runtime.h" #include "py/stream.h" +#include "py/objstr.h" #if MICROPY_PY_USSL && MICROPY_SSL_AXTLS @@ -54,6 +55,68 @@ struct ssl_args { STATIC const mp_obj_type_t ussl_socket_type; +// Table of error strings corresponding to SSL_xxx error codes. +STATIC const char *const ssl_error_tab1[] = { + "NOT_OK", + "DEAD", + "CLOSE_NOTIFY", + "EAGAIN", +}; +STATIC const char *const ssl_error_tab2[] = { + "CONN_LOST", + "RECORD_OVERFLOW", + "SOCK_SETUP_FAILURE", + NULL, + "INVALID_HANDSHAKE", + "INVALID_PROT_MSG", + "INVALID_HMAC", + "INVALID_VERSION", + "UNSUPPORTED_EXTENSION", + "INVALID_SESSION", + "NO_CIPHER", + "INVALID_CERT_HASH_ALG", + "BAD_CERTIFICATE", + "INVALID_KEY", + NULL, + "FINISHED_INVALID", + "NO_CERT_DEFINED", + "NO_CLIENT_RENOG", + "NOT_SUPPORTED", +}; + +STATIC NORETURN void ussl_raise_error(int err) { + MP_STATIC_ASSERT(SSL_NOT_OK - 3 == SSL_EAGAIN); + MP_STATIC_ASSERT(SSL_ERROR_CONN_LOST - 18 == SSL_ERROR_NOT_SUPPORTED); + + // Check if err corresponds to something in one of the error string tables. + const char *errstr = NULL; + if (SSL_NOT_OK >= err && err >= SSL_EAGAIN) { + errstr = ssl_error_tab1[SSL_NOT_OK - err]; + } else if (SSL_ERROR_CONN_LOST >= err && err >= SSL_ERROR_NOT_SUPPORTED) { + errstr = ssl_error_tab2[SSL_ERROR_CONN_LOST - err]; + } + + // Unknown error, just raise the error code. + if (errstr == NULL) { + mp_raise_OSError(err); + } + + // Construct string object. + mp_obj_str_t *o_str = m_new_obj_maybe(mp_obj_str_t); + if (o_str == NULL) { + mp_raise_OSError(err); + } + o_str->base.type = &mp_type_str; + o_str->data = (const byte *)errstr; + o_str->len = strlen((char *)o_str->data); + o_str->hash = qstr_compute_hash(o_str->data, o_str->len); + + // Raise OSError(err, str). + mp_obj_t args[2] = { MP_OBJ_NEW_SMALL_INT(err), MP_OBJ_FROM_PTR(o_str)}; + nlr_raise(mp_obj_exception_make_new(&mp_type_OSError, 2, 0, args)); +} + + STATIC mp_obj_ssl_socket_t *ussl_socket_new(mp_obj_t sock, struct ssl_args *args) { #if MICROPY_PY_USSL_FINALISER mp_obj_ssl_socket_t *o = m_new_obj_with_finaliser(mp_obj_ssl_socket_t); @@ -107,9 +170,7 @@ STATIC mp_obj_ssl_socket_t *ussl_socket_new(mp_obj_t sock, struct ssl_args *args int res = ssl_handshake_status(o->ssl_sock); if (res != SSL_OK) { - printf("ssl_handshake_status: %d\n", res); - ssl_display_error(res); - mp_raise_OSError(MP_EIO); + ussl_raise_error(res); } } diff --git a/extmod/modussl_mbedtls.c b/extmod/modussl_mbedtls.c index 9e117c82ccf34..1677dc6e1ca70 100644 --- a/extmod/modussl_mbedtls.c +++ b/extmod/modussl_mbedtls.c @@ -34,6 +34,7 @@ #include "py/runtime.h" #include "py/stream.h" +#include "py/objstr.h" // mbedtls_time_t #include "mbedtls/platform.h" @@ -43,6 +44,7 @@ #include "mbedtls/entropy.h" #include "mbedtls/ctr_drbg.h" #include "mbedtls/debug.h" +#include "mbedtls/error.h" typedef struct _mp_obj_ssl_socket_t { mp_obj_base_t base; @@ -74,6 +76,46 @@ STATIC void mbedtls_debug(void *ctx, int level, const char *file, int line, cons } #endif +STATIC NORETURN void mbedtls_raise_error(int err) { + // _mbedtls_ssl_send and _mbedtls_ssl_recv (below) turn positive error codes from the + // underlying socket into negative codes to pass them through mbedtls. Here we turn them + // positive again so they get interpreted as the OSError they really are. The + // cut-off of -256 is a bit hacky, sigh. + if (err < 0 && err > -256) { + mp_raise_OSError(-err); + } + + #if defined(MBEDTLS_ERROR_C) + // Including mbedtls_strerror takes about 1.5KB due to the error strings. + // MBEDTLS_ERROR_C is the define used by mbedtls to conditionally include mbedtls_strerror. + // It is set/unset in the MBEDTLS_CONFIG_FILE which is defined in the Makefile. + + // Try to allocate memory for the message + #define ERR_STR_MAX 80 // mbedtls_strerror truncates if it doesn't fit + mp_obj_str_t *o_str = m_new_obj_maybe(mp_obj_str_t); + byte *o_str_buf = m_new_maybe(byte, ERR_STR_MAX); + if (o_str == NULL || o_str_buf == NULL) { + mp_raise_OSError(err); + } + + // print the error message into the allocated buffer + mbedtls_strerror(err, (char *)o_str_buf, ERR_STR_MAX); + size_t len = strlen((char *)o_str_buf); + + // Put the exception object together + o_str->base.type = &mp_type_str; + o_str->data = o_str_buf; + o_str->len = len; + o_str->hash = qstr_compute_hash(o_str->data, o_str->len); + // raise + mp_obj_t args[2] = { MP_OBJ_NEW_SMALL_INT(err), MP_OBJ_FROM_PTR(o_str)}; + nlr_raise(mp_obj_exception_make_new(&mp_type_OSError, 2, 0, args)); + #else + // mbedtls is compiled without error strings so we simply return the err number + mp_raise_OSError(err); // err is typically a large negative number + #endif +} + STATIC int _mbedtls_ssl_send(void *ctx, const byte *buf, size_t len) { mp_obj_t sock = *(mp_obj_t *)ctx; @@ -85,7 +127,7 @@ STATIC int _mbedtls_ssl_send(void *ctx, const byte *buf, size_t len) { if (mp_is_nonblocking_error(err)) { return MBEDTLS_ERR_SSL_WANT_WRITE; } - return -err; + return -err; // convert an MP_ERRNO to something mbedtls passes through as error } else { return out_sz; } @@ -197,7 +239,6 @@ STATIC mp_obj_ssl_socket_t *socket_new(mp_obj_t sock, struct ssl_args *args) { if (args->do_handshake.u_bool) { while ((ret = mbedtls_ssl_handshake(&o->ssl)) != 0) { if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { - printf("mbedtls_ssl_handshake error: -%x\n", -ret); goto cleanup; } } @@ -221,7 +262,7 @@ STATIC mp_obj_ssl_socket_t *socket_new(mp_obj_t sock, struct ssl_args *args) { } else if (ret == MBEDTLS_ERR_X509_BAD_INPUT_DATA) { mp_raise_ValueError(MP_ERROR_TEXT("invalid cert")); } else { - mp_raise_OSError(MP_EIO); + mbedtls_raise_error(ret); } } diff --git a/extmod/mpbthci.c b/extmod/mpbthci.c new file mode 100644 index 0000000000000..79a865424233d --- /dev/null +++ b/extmod/mpbthci.c @@ -0,0 +1,38 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jim Mussared + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// Default definitions in case a controller doesn't implement this. + +#include "py/mphal.h" + +#if MICROPY_PY_BLUETOOTH + +#define DEBUG_printf(...) // printf(__VA_ARGS__) + +#include "extmod/mpbthci.h" + + +#endif // MICROPY_PY_BLUETOOTH diff --git a/extmod/modbluetooth_hci.h b/extmod/mpbthci.h similarity index 71% rename from extmod/modbluetooth_hci.h rename to extmod/mpbthci.h index d6b432d224710..acb5b832bab11 100644 --- a/extmod/modbluetooth_hci.h +++ b/extmod/mpbthci.h @@ -24,15 +24,15 @@ * THE SOFTWARE. */ -#ifndef MICROPY_INCLUDED_EXTMOD_MODBLUETOOTH_HCI_H -#define MICROPY_INCLUDED_EXTMOD_MODBLUETOOTH_HCI_H +#ifndef MICROPY_INCLUDED_EXTMOD_MPBTHCI_H +#define MICROPY_INCLUDED_EXTMOD_MPBTHCI_H -#include "uart.h" +// --- Optionally can be implemented by the driver. --------------------------- -// Optionally can be implemented by the driver. +// Start/stop the HCI controller. +// Requires the UART to this HCI controller is available. int mp_bluetooth_hci_controller_init(void); -int mp_bluetooth_hci_controller_activate(void); -int mp_bluetooth_hci_controller_deactivate(void); +int mp_bluetooth_hci_controller_deinit(void); // Tell the controller to go to sleep (e.g. on RX if we don't think we're expecting anything more). int mp_bluetooth_hci_controller_sleep_maybe(void); @@ -41,16 +41,11 @@ bool mp_bluetooth_hci_controller_woken(void); // Wake up the controller (e.g. we're about to TX). int mp_bluetooth_hci_controller_wakeup(void); -// Storage and bindings that need to be implemented by the port. -// These are used by the stack bindings (e.g. nimble/hal_uart.c) -// as well as potentially the driver (e.g. cywbt.c). -extern uint8_t mp_bluetooth_hci_cmd_buf[4 + 256]; -extern pyb_uart_obj_t mp_bluetooth_hci_uart_obj; - -int mp_bluetooth_hci_uart_init(uint32_t port); -int mp_bluetooth_hci_uart_activate(void); +// --- Bindings that need to be implemented by the port. ---------------------- +int mp_bluetooth_hci_uart_init(uint32_t port, uint32_t baudrate); +int mp_bluetooth_hci_uart_deinit(void); int mp_bluetooth_hci_uart_set_baudrate(uint32_t baudrate); int mp_bluetooth_hci_uart_readchar(void); int mp_bluetooth_hci_uart_write(const uint8_t *buf, size_t len); -#endif // MICROPY_INCLUDED_EXTMOD_MODBLUETOOTH_HCI_H +#endif // MICROPY_INCLUDED_EXTMOD_MPBTHCI_H diff --git a/extmod/nimble/hal/hal_uart.c b/extmod/nimble/hal/hal_uart.c index fba82b830438a..c6d0850fea993 100644 --- a/extmod/nimble/hal/hal_uart.c +++ b/extmod/nimble/hal/hal_uart.c @@ -26,11 +26,9 @@ #include "py/runtime.h" #include "py/mphal.h" -#include "pin_static_af.h" #include "nimble/ble.h" #include "extmod/nimble/hal/hal_uart.h" -#include "extmod/modbluetooth_hci.h" -#include "extmod/nimble/nimble/nimble_hci_uart.h" +#include "extmod/mpbthci.h" #if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE @@ -39,6 +37,9 @@ static void *hal_uart_tx_arg; static hal_uart_rx_cb_t hal_uart_rx_cb; static void *hal_uart_rx_arg; +// Provided by the port, and also possibly shared with the driver. +extern uint8_t mp_bluetooth_hci_cmd_buf[4 + 256]; + int hal_uart_init_cbs(uint32_t port, hal_uart_tx_cb_t tx_cb, void *tx_arg, hal_uart_rx_cb_t rx_cb, void *rx_arg) { hal_uart_tx_cb = tx_cb; hal_uart_tx_arg = tx_arg; @@ -48,9 +49,7 @@ int hal_uart_init_cbs(uint32_t port, hal_uart_tx_cb_t tx_cb, void *tx_arg, hal_u } int hal_uart_config(uint32_t port, uint32_t baudrate, uint32_t bits, uint32_t stop, uint32_t parity, uint32_t flow) { - mp_bluetooth_hci_uart_init(port); - mp_bluetooth_hci_uart_set_baudrate(baudrate); - return mp_bluetooth_hci_uart_activate(); + return mp_bluetooth_hci_uart_init(port, baudrate); } void hal_uart_start_tx(uint32_t port) { @@ -71,7 +70,7 @@ void hal_uart_start_tx(uint32_t port) { printf("\n"); #endif - mp_bluetooth_nimble_hci_uart_tx_strn((void*)mp_bluetooth_hci_cmd_buf, len); + mp_bluetooth_hci_uart_write(mp_bluetooth_hci_cmd_buf, len); } int hal_uart_close(uint32_t port) { @@ -79,7 +78,17 @@ int hal_uart_close(uint32_t port) { } void mp_bluetooth_nimble_hci_uart_process(void) { - mp_bluetooth_nimble_hci_uart_rx(hal_uart_rx_cb, hal_uart_rx_arg); + bool host_wake = mp_bluetooth_hci_controller_woken(); + + int chr; + while ((chr = mp_bluetooth_hci_uart_readchar()) >= 0) { + // printf("UART RX: %02x\n", data); + hal_uart_rx_cb(hal_uart_rx_arg, chr); + } + + if (host_wake) { + mp_bluetooth_hci_controller_sleep_maybe(); + } } #endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE diff --git a/extmod/nimble/hal/hal_uart.h b/extmod/nimble/hal/hal_uart.h index 0ef04bc81e026..1ff1c17436dae 100644 --- a/extmod/nimble/hal/hal_uart.h +++ b/extmod/nimble/hal/hal_uart.h @@ -1,3 +1,29 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jim Mussared + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + #ifndef MICROPY_INCLUDED_EXTMOD_NIMBLE_HAL_HAL_UART_H #define MICROPY_INCLUDED_EXTMOD_NIMBLE_HAL_HAL_UART_H @@ -10,10 +36,13 @@ typedef int (*hal_uart_tx_cb_t)(void *arg); typedef int (*hal_uart_rx_cb_t)(void *arg, uint8_t data); -// Called by NimBLE, implemented in hal_uart.c. +// --- Called by NimBLE, implemented in hal_uart.c. --------------------------- int hal_uart_init_cbs(uint32_t port, hal_uart_tx_cb_t tx_cb, void *tx_arg, hal_uart_rx_cb_t rx_cb, void *rx_arg); int hal_uart_config(uint32_t port, uint32_t baud, uint32_t bits, uint32_t stop, uint32_t parity, uint32_t flow); void hal_uart_start_tx(uint32_t port); int hal_uart_close(uint32_t port); +// --- Called by the MicroPython port when UART data is available ------------- +void mp_bluetooth_nimble_hci_uart_process(void); + #endif // MICROPY_INCLUDED_EXTMOD_NIMBLE_HAL_HAL_UART_H diff --git a/extmod/nimble/logcfg/logcfg.h b/extmod/nimble/logcfg/logcfg.h new file mode 100644 index 0000000000000..e9023dae65b5d --- /dev/null +++ b/extmod/nimble/logcfg/logcfg.h @@ -0,0 +1,45 @@ +/** + * This file was generated by Apache newt version: 1.8.0-dev + */ + +#ifndef MICROPY_INCLUDED_EXTMOD_NIMBLE_LOGCFG_LOGCFG_H +#define MICROPY_INCLUDED_EXTMOD_NIMBLE_LOGCFG_LOGCFG_H + +#include "py/mphal.h" +#include "modlog/modlog.h" +#include "log_common/log_common.h" + +#define MICROPY_PY_BLUETOOTH_DIAGNOSTIC_LOGGING (1) + +#if MICROPY_PY_BLUETOOTH_DIAGNOSTIC_LOGGING +#define DFLT_LOG_DEBUG(...) MODLOG_DEBUG(4, __VA_ARGS__) +#else +#define DFLT_LOG_DEBUG(...) IGNORE(__VA_ARGS__) +#endif + +#if MICROPY_PY_BLUETOOTH_DIAGNOSTIC_LOGGING > 1 +#define BLE_HS_LOG_DEBUG(...) MODLOG_DEBUG(4, __VA_ARGS__) +#else +#define BLE_HS_LOG_DEBUG(...) IGNORE(__VA_ARGS__) +#endif + +#define BLE_HS_LOG_INFO(...) MODLOG_INFO(4, __VA_ARGS__) +#define BLE_HS_LOG_WARN(...) MODLOG_WARN(4, __VA_ARGS__) +#define BLE_HS_LOG_ERROR(...) MODLOG_ERROR(4, __VA_ARGS__) +#define BLE_HS_LOG_CRITICAL(...) MODLOG_CRITICAL(4, __VA_ARGS__) +#define BLE_HS_LOG_DISABLED(...) MODLOG_DISABLED(4, __VA_ARGS__) + +#define DFLT_LOG_INFO(...) MODLOG_INFO(0, __VA_ARGS__) +#define DFLT_LOG_WARN(...) MODLOG_WARN(0, __VA_ARGS__) +#define DFLT_LOG_ERROR(...) MODLOG_ERROR(0, __VA_ARGS__) +#define DFLT_LOG_CRITICAL(...) MODLOG_CRITICAL(0, __VA_ARGS__) +#define DFLT_LOG_DISABLED(...) MODLOG_DISABLED(0, __VA_ARGS__) + +#define MFG_LOG_DEBUG(...) IGNORE(__VA_ARGS__) +#define MFG_LOG_INFO(...) IGNORE(__VA_ARGS__) +#define MFG_LOG_WARN(...) IGNORE(__VA_ARGS__) +#define MFG_LOG_ERROR(...) IGNORE(__VA_ARGS__) +#define MFG_LOG_CRITICAL(...) IGNORE(__VA_ARGS__) +#define MFG_LOG_DISABLED(...) MODLOG_DISABLED(128, __VA_ARGS__) + +#endif // MICROPY_INCLUDED_EXTMOD_NIMBLE_LOGCFG_LOGCFG_H diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index e2e95aa74eb6b..c0d6d64cfc1f0 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -4,7 +4,7 @@ * The MIT License (MIT) * * Copyright (c) 2019 Damien P. George - * Copyright (c) 2019 Jim Mussared + * Copyright (c) 2019-2020 Jim Mussared * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -33,21 +33,28 @@ #include "extmod/nimble/modbluetooth_nimble.h" #include "extmod/modbluetooth.h" +#include "extmod/mpbthci.h" #include "host/ble_hs.h" #include "host/util/util.h" #include "nimble/ble.h" #include "nimble/nimble_port.h" #include "services/gap/ble_svc_gap.h" +#include "services/gatt/ble_svc_gatt.h" #ifndef MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME #define MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME "MPY NIMBLE" #endif -#define DEBUG_EVENT_printf(...) //printf(__VA_ARGS__) +#define DEBUG_printf(...) // printf("nimble: " __VA_ARGS__) #define ERRNO_BLUETOOTH_NOT_ACTIVE MP_ENODEV +STATIC uint8_t nimble_address_mode = BLE_OWN_ADDR_RANDOM; + +#define NIMBLE_STARTUP_TIMEOUT 2000 +STATIC struct ble_npl_sem startup_sem; + // Any BLE_HS_xxx code not in this table will default to MP_EIO. STATIC int8_t ble_hs_err_to_errno_table[] = { [BLE_HS_EAGAIN] = MP_EAGAIN, @@ -63,10 +70,11 @@ STATIC int8_t ble_hs_err_to_errno_table[] = { }; STATIC int ble_hs_err_to_errno(int err) { + DEBUG_printf("ble_hs_err_to_errno: %d\n", err); if (!err) { return 0; } - if (0 <= err && err < MP_ARRAY_SIZE(ble_hs_err_to_errno_table) && ble_hs_err_to_errno_table[err]) { + if (err >= 0 && (unsigned)err < MP_ARRAY_SIZE(ble_hs_err_to_errno_table) && ble_hs_err_to_errno_table[err]) { return ble_hs_err_to_errno_table[err]; } else { return MP_EIO; @@ -74,19 +82,19 @@ STATIC int ble_hs_err_to_errno(int err) { } // Note: modbluetooth UUIDs store their data in LE. -STATIC ble_uuid_t *create_nimble_uuid(const mp_obj_bluetooth_uuid_t *uuid) { +STATIC ble_uuid_t *create_nimble_uuid(const mp_obj_bluetooth_uuid_t *uuid, ble_uuid_any_t *storage) { if (uuid->type == MP_BLUETOOTH_UUID_TYPE_16) { - ble_uuid16_t *result = m_new(ble_uuid16_t, 1); + ble_uuid16_t *result = storage ? &storage->u16 : m_new(ble_uuid16_t, 1); result->u.type = BLE_UUID_TYPE_16; result->value = (uuid->data[1] << 8) | uuid->data[0]; return (ble_uuid_t *)result; } else if (uuid->type == MP_BLUETOOTH_UUID_TYPE_32) { - ble_uuid32_t *result = m_new(ble_uuid32_t, 1); + ble_uuid32_t *result = storage ? &storage->u32 : m_new(ble_uuid32_t, 1); result->u.type = BLE_UUID_TYPE_32; result->value = (uuid->data[1] << 24) | (uuid->data[1] << 16) | (uuid->data[1] << 8) | uuid->data[0]; return (ble_uuid_t *)result; } else if (uuid->type == MP_BLUETOOTH_UUID_TYPE_128) { - ble_uuid128_t *result = m_new(ble_uuid128_t, 1); + ble_uuid128_t *result = storage ? &storage->u128 : m_new(ble_uuid128_t, 1); result->u.type = BLE_UUID_TYPE_128; memcpy(result->value, uuid->data, 16); return (ble_uuid_t *)result; @@ -95,6 +103,13 @@ STATIC ble_uuid_t *create_nimble_uuid(const mp_obj_bluetooth_uuid_t *uuid) { } } +// modbluetooth (and the layers above it) work in BE for addresses, Nimble works in LE. +STATIC void reverse_addr_byte_order(uint8_t *addr_out, const uint8_t *addr_in) { + for (int i = 0; i < 6; ++i) { + addr_out[i] = addr_in[5 - i]; + } +} + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE STATIC mp_obj_bluetooth_uuid_t create_mp_uuid(const ble_uuid_any_t *uuid) { @@ -122,13 +137,6 @@ STATIC mp_obj_bluetooth_uuid_t create_mp_uuid(const ble_uuid_any_t *uuid) { return result; } -// modbluetooth (and the layers above it) work in BE for addresses, Nimble works in LE. -STATIC void reverse_addr_byte_order(uint8_t *addr_out, const uint8_t *addr_in) { - for (int i = 0; i < 6; ++i) { - addr_out[i] = addr_in[5 - i]; - } -} - STATIC ble_addr_t create_nimble_addr(uint8_t addr_type, const uint8_t *addr) { ble_addr_t addr_nimble; addr_nimble.type = addr_type; @@ -145,39 +153,64 @@ STATIC void reset_cb(int reason) { (void)reason; } -STATIC void sync_cb(void) { +STATIC bool has_public_address(void) { + return ble_hs_id_copy_addr(BLE_ADDR_PUBLIC, NULL, NULL) == 0; +} + +STATIC void set_random_address(bool nrpa) { int rc; + (void)rc; ble_addr_t addr; - - rc = ble_hs_util_ensure_addr(0); // prefer public address - if (rc != 0) { - // https://mynewt.apache.org/latest/tutorials/ble/eddystone.html#configure-the-nimble-stack-with-an-address - #if MICROPY_PY_BLUETOOTH_RANDOM_ADDR - rc = ble_hs_id_gen_rnd(1, &addr); - assert(rc == 0); - rc = ble_hs_id_set_rnd(addr.val); - assert(rc == 0); - #else - uint8_t addr_be[6]; - mp_hal_get_mac(MP_HAL_MAC_BDADDR, addr_be); - reverse_addr_byte_order(addr.val, addr_be); - // ble_hs_id_set_pub(addr.val); - rc = ble_hs_id_set_rnd(addr.val); + #if MICROPY_BLUETOOTH_USE_MP_HAL_GET_MAC_STATIC_ADDRESS + if (!nrpa) { + DEBUG_printf("set_random_address: Generating static address using mp_hal_get_mac\n"); + uint8_t hal_mac_addr[6]; + mp_hal_get_mac(MP_HAL_MAC_BDADDR, hal_mac_addr); + addr = create_nimble_addr(BLE_ADDR_RANDOM, hal_mac_addr); + // Mark it as STATIC (not RPA or NRPA). + addr.val[5] |= 0xc0; + } else + #endif + { + DEBUG_printf("set_random_address: Generating random static address\n"); + rc = ble_hs_id_gen_rnd(nrpa ? 1 : 0, &addr); assert(rc == 0); - #endif + } + rc = ble_hs_id_set_rnd(addr.val); + assert(rc == 0); + rc = ble_hs_util_ensure_addr(1); + assert(rc == 0); +} - rc = ble_hs_util_ensure_addr(0); // prefer public address - assert(rc == 0); +STATIC void sync_cb(void) { + int rc; + (void)rc; + + DEBUG_printf("sync_cb: state=%d\n", mp_bluetooth_nimble_ble_state); + + if (mp_bluetooth_nimble_ble_state != MP_BLUETOOTH_NIMBLE_BLE_STATE_WAITING_FOR_SYNC) { + return; + } + + if (has_public_address()) { + nimble_address_mode = BLE_OWN_ADDR_PUBLIC; + } else { + nimble_address_mode = BLE_OWN_ADDR_RANDOM; + set_random_address(false); } if (MP_BLUETOOTH_DEFAULT_ATTR_LEN > 20) { + DEBUG_printf("sync_cb: Setting MTU\n"); rc = ble_att_set_preferred_mtu(MP_BLUETOOTH_DEFAULT_ATTR_LEN + 3); assert(rc == 0); } + DEBUG_printf("sync_cb: Setting device name\n"); ble_svc_gap_device_name_set(MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME); mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_ACTIVE; + + ble_npl_sem_release(&startup_sem); } STATIC void gatts_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg) { @@ -187,12 +220,12 @@ STATIC void gatts_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg) { switch (ctxt->op) { case BLE_GATT_REGISTER_OP_SVC: // Called when a service is successfully registered. - DEBUG_EVENT_printf("gatts_register_cb: svc uuid=%p handle=%d\n", &ctxt->svc.svc_def->uuid, ctxt->svc.handle); + DEBUG_printf("gatts_register_cb: svc uuid=%p handle=%d\n", &ctxt->svc.svc_def->uuid, ctxt->svc.handle); break; case BLE_GATT_REGISTER_OP_CHR: // Called when a characteristic is successfully registered. - DEBUG_EVENT_printf("gatts_register_cb: chr uuid=%p def_handle=%d val_handle=%d\n", &ctxt->chr.chr_def->uuid, ctxt->chr.def_handle, ctxt->chr.val_handle); + DEBUG_printf("gatts_register_cb: chr uuid=%p def_handle=%d val_handle=%d\n", &ctxt->chr.chr_def->uuid, ctxt->chr.def_handle, ctxt->chr.val_handle); // Note: We will get this event for the default GAP Service, meaning that we allocate storage for the // "device name" and "appearance" characteristics, even though we never see the reads for them. @@ -206,7 +239,7 @@ STATIC void gatts_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg) { case BLE_GATT_REGISTER_OP_DSC: // Called when a descriptor is successfully registered. // Note: This is event is not called for the CCCD. - DEBUG_EVENT_printf("gatts_register_cb: dsc uuid=%p handle=%d\n", &ctxt->dsc.dsc_def->uuid, ctxt->dsc.handle); + DEBUG_printf("gatts_register_cb: dsc uuid=%p handle=%d\n", &ctxt->dsc.dsc_def->uuid, ctxt->dsc.handle); // See above, safe to alloc. mp_bluetooth_gatts_db_create_entry(MP_STATE_PORT(bluetooth_nimble_root_pointers)->gatts_db, ctxt->dsc.handle, MP_BLUETOOTH_DEFAULT_ATTR_LEN); @@ -216,13 +249,13 @@ STATIC void gatts_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg) { break; default: - DEBUG_EVENT_printf("gatts_register_cb: unknown op %d\n", ctxt->op); + DEBUG_printf("gatts_register_cb: unknown op %d\n", ctxt->op); break; } } STATIC int gap_event_cb(struct ble_gap_event *event, void *arg) { - DEBUG_EVENT_printf("gap_event_cb: type=%d\n", event->type); + DEBUG_printf("gap_event_cb: type=%d\n", event->type); if (!mp_bluetooth_is_active()) { return 0; } @@ -247,93 +280,236 @@ STATIC int gap_event_cb(struct ble_gap_event *event, void *arg) { reverse_addr_byte_order(addr, event->disconnect.conn.peer_id_addr.val); mp_bluetooth_gap_on_connected_disconnected(MP_BLUETOOTH_IRQ_CENTRAL_DISCONNECT, event->disconnect.conn.conn_handle, event->disconnect.conn.peer_id_addr.type, addr); break; - } + case BLE_GAP_EVENT_NOTIFY_TX: { + DEBUG_printf("gap_event_cb: notify_tx: %d %d\n", event->notify_tx.indication, event->notify_tx.status); + // This event corresponds to either a sent notify/indicate (status == 0), or an indication confirmation (status != 0). + if (event->notify_tx.indication && event->notify_tx.status != 0) { + // Map "done/ack" to 0, otherwise pass the status directly. + mp_bluetooth_gatts_on_indicate_complete(event->notify_tx.conn_handle, event->notify_tx.attr_handle, event->notify_tx.status == BLE_HS_EDONE ? 0 : event->notify_tx.status); + } + break; + } + + case BLE_GAP_EVENT_MTU: { + if (event->mtu.channel_id == BLE_L2CAP_CID_ATT) { + DEBUG_printf("gap_event_cb: mtu update: conn_handle=%d cid=%d mtu=%d\n", event->mtu.conn_handle, event->mtu.channel_id, event->mtu.value); + mp_bluetooth_gatts_on_mtu_exchanged(event->mtu.conn_handle, event->mtu.value); + } + break; + } + } return 0; } +#if !MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY + +// On ports such as ESP32 where we only implement the bindings, then +// the port must provide these functions. +// But for STM32 / Unix-H4, we provide a default implementation of the +// port-specific functionality. +// TODO: In the future if a port ever needs to customise these functions +// then investigate using MP_WEAK or splitting them out to another .c file. + +#include "transport/uart/ble_hci_uart.h" + +void mp_bluetooth_nimble_port_hci_init(void) { + DEBUG_printf("mp_bluetooth_nimble_port_hci_init (nimble default)\n"); + // This calls mp_bluetooth_hci_uart_init (via ble_hci_uart_init --> hal_uart_config --> mp_bluetooth_hci_uart_init). + ble_hci_uart_init(); + mp_bluetooth_hci_controller_init(); +} + +void mp_bluetooth_nimble_port_hci_deinit(void) { + DEBUG_printf("mp_bluetooth_nimble_port_hci_deinit (nimble default)\n"); + mp_bluetooth_hci_controller_deinit(); + mp_bluetooth_hci_uart_deinit(); +} + +void mp_bluetooth_nimble_port_start(void) { + DEBUG_printf("mp_bluetooth_nimble_port_start (nimble default)\n"); + // By default, assume port is already running its own background task (e.g. SysTick on STM32). + // ESP32 runs a FreeRTOS task, Unix has a thread. +} + +// Called when the host stop procedure has completed. +STATIC void ble_hs_shutdown_stop_cb(int status, void *arg) { + (void)status; + (void)arg; + mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF; +} + +STATIC struct ble_hs_stop_listener ble_hs_shutdown_stop_listener; + +void mp_bluetooth_nimble_port_shutdown(void) { + DEBUG_printf("mp_bluetooth_nimble_port_shutdown (nimble default)\n"); + // By default, just call ble_hs_stop directly and wait for the stack to stop. + + mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_STOPPING; + + ble_hs_stop(&ble_hs_shutdown_stop_listener, ble_hs_shutdown_stop_cb, NULL); + + while (mp_bluetooth_nimble_ble_state != MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) { + MICROPY_EVENT_POLL_HOOK + } +} + +#endif // !MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY + int mp_bluetooth_init(void) { - DEBUG_EVENT_printf("mp_bluetooth_init\n"); + DEBUG_printf("mp_bluetooth_init\n"); // Clean up if necessary. mp_bluetooth_deinit(); + mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_STARTING; + ble_hs_cfg.reset_cb = reset_cb; ble_hs_cfg.sync_cb = sync_cb; ble_hs_cfg.gatts_register_cb = gatts_register_cb; ble_hs_cfg.store_status_cb = ble_store_util_status_rr; + ble_npl_sem_init(&startup_sem, 0); + MP_STATE_PORT(bluetooth_nimble_root_pointers) = m_new0(mp_bluetooth_nimble_root_pointers_t, 1); mp_bluetooth_gatts_db_create(&MP_STATE_PORT(bluetooth_nimble_root_pointers)->gatts_db); - mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_STARTING; + #if !MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY + // Dereference any previous NimBLE mallocs. + MP_STATE_PORT(bluetooth_nimble_memory) = NULL; + #endif - mp_bluetooth_nimble_port_preinit(); - nimble_port_init(); - mp_bluetooth_nimble_port_postinit(); + // Allow port (ESP32) to override NimBLE's HCI init. + // Otherwise default implementation above calls ble_hci_uart_init(). + mp_bluetooth_nimble_port_hci_init(); - // By default, just register the default gap service. - ble_svc_gap_init(); + // Initialise NimBLE memory and data structures. + nimble_port_init(); + // Make sure that the HCI UART and event handling task is running. mp_bluetooth_nimble_port_start(); - // Wait for sync callback - while (mp_bluetooth_nimble_ble_state != MP_BLUETOOTH_NIMBLE_BLE_STATE_ACTIVE) { - MICROPY_EVENT_POLL_HOOK + // Static initialization is complete, can start processing events. + mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_WAITING_FOR_SYNC; + + ble_npl_sem_pend(&startup_sem, NIMBLE_STARTUP_TIMEOUT); + + if (mp_bluetooth_nimble_ble_state != MP_BLUETOOTH_NIMBLE_BLE_STATE_ACTIVE) { + mp_bluetooth_deinit(); + return MP_ETIMEDOUT; } - DEBUG_EVENT_printf("mp_bluetooth_init: ready\n"); - return 0; -} + // By default, just register the default gap/gatt service. + ble_svc_gap_init(); + ble_svc_gatt_init(); + // The preceeding two calls allocate service definitions on the heap, + // then we must now call gatts_start to register those services + // and free the heap memory. + // Otherwise it will be realloc'ed on the next stack startup. + ble_gatts_start(); -// Called when the host stop procedure has completed. -STATIC void ble_hs_shutdown_stop_cb(int status, void *arg) { - mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF; -} + DEBUG_printf("mp_bluetooth_init: ready\n"); -STATIC struct ble_hs_stop_listener ble_hs_shutdown_stop_listener; + return 0; +} void mp_bluetooth_deinit(void) { - DEBUG_EVENT_printf("mp_bluetooth_deinit\n"); + DEBUG_printf("mp_bluetooth_deinit\n"); if (mp_bluetooth_nimble_ble_state == MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) { return; } - mp_bluetooth_gap_advertise_stop(); - #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE - mp_bluetooth_gap_scan_stop(); - #endif - - mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_STOPPING; + // Must call ble_hs_stop() in a port-specific way to stop the background + // task. Default implementation provided above. + if (mp_bluetooth_nimble_ble_state == MP_BLUETOOTH_NIMBLE_BLE_STATE_ACTIVE) { + mp_bluetooth_gap_advertise_stop(); + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE + mp_bluetooth_gap_scan_stop(); + #endif - ble_hs_stop(&ble_hs_shutdown_stop_listener, ble_hs_shutdown_stop_cb, NULL); + DEBUG_printf("mp_bluetooth_deinit: starting port shutdown\n"); - while (mp_bluetooth_nimble_ble_state != MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) { - MICROPY_EVENT_POLL_HOOK + mp_bluetooth_nimble_port_shutdown(); + assert(mp_bluetooth_nimble_ble_state == MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF); + } else { + mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF; } - mp_bluetooth_nimble_port_deinit(); + // Shutdown the HCI controller. + mp_bluetooth_nimble_port_hci_deinit(); MP_STATE_PORT(bluetooth_nimble_root_pointers) = NULL; - DEBUG_EVENT_printf("mp_bluetooth_deinit: shut down\n"); + + #if !MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY + // Dereference any previous NimBLE mallocs. + MP_STATE_PORT(bluetooth_nimble_memory) = NULL; + #endif + + DEBUG_printf("mp_bluetooth_deinit: shut down\n"); } bool mp_bluetooth_is_active(void) { return mp_bluetooth_nimble_ble_state == MP_BLUETOOTH_NIMBLE_BLE_STATE_ACTIVE; } -void mp_bluetooth_get_device_addr(uint8_t *addr) { - #if MICROPY_PY_BLUETOOTH_RANDOM_ADDR +void mp_bluetooth_get_current_address(uint8_t *addr_type, uint8_t *addr) { + if (!mp_bluetooth_is_active()) { + mp_raise_OSError(ERRNO_BLUETOOTH_NOT_ACTIVE); + } + uint8_t addr_le[6]; - int rc = ble_hs_id_copy_addr(BLE_ADDR_RANDOM, addr_le, NULL); + + switch (nimble_address_mode) { + case BLE_OWN_ADDR_PUBLIC: + *addr_type = BLE_ADDR_PUBLIC; + break; + case BLE_OWN_ADDR_RANDOM: + *addr_type = BLE_ADDR_RANDOM; + break; + case BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT: + case BLE_OWN_ADDR_RPA_RANDOM_DEFAULT: + default: + // TODO: If RPA/NRPA in use, get the current value. + // Is this even possible in NimBLE? + mp_raise_OSError(MP_EINVAL); + } + + int rc = ble_hs_id_copy_addr(*addr_type, addr_le, NULL); if (rc != 0) { - // Even with MICROPY_PY_BLUETOOTH_RANDOM_ADDR enabled the public address may - // be used instead, in which case there is no random address. - ble_hs_id_copy_addr(BLE_ADDR_PUBLIC, addr_le, NULL); + mp_raise_OSError(MP_EINVAL); } reverse_addr_byte_order(addr, addr_le); - #else - mp_hal_get_mac(MP_HAL_MAC_BDADDR, addr); - #endif +} + +void mp_bluetooth_set_address_mode(uint8_t addr_mode) { + switch (addr_mode) { + case MP_BLUETOOTH_ADDRESS_MODE_PUBLIC: + if (!has_public_address()) { + // No public address available. + mp_raise_OSError(MP_EINVAL); + } + nimble_address_mode = BLE_OWN_ADDR_PUBLIC; + break; + case MP_BLUETOOTH_ADDRESS_MODE_RANDOM: + // Generate an static random address. + set_random_address(false); + nimble_address_mode = BLE_OWN_ADDR_RANDOM; + break; + case MP_BLUETOOTH_ADDRESS_MODE_RPA: + if (has_public_address()) { + nimble_address_mode = BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT; + } else { + // Generate an static random address to use as the identity address. + set_random_address(false); + nimble_address_mode = BLE_OWN_ADDR_RPA_RANDOM_DEFAULT; + } + break; + case MP_BLUETOOTH_ADDRESS_MODE_NRPA: + // Generate an NRPA. + set_random_address(true); + // In NimBLE, NRPA is treated like a static random address that happens to be an NRPA. + nimble_address_mode = BLE_OWN_ADDR_RANDOM; + break; + } } size_t mp_bluetooth_gap_get_device_name(const uint8_t **buf) { @@ -382,23 +558,11 @@ int mp_bluetooth_gap_advertise_start(bool connectable, int32_t interval_us, cons .channel_map = 7, // all 3 channels. }; - ret = ble_gap_adv_start(BLE_OWN_ADDR_PUBLIC, NULL, BLE_HS_FOREVER, &adv_params, gap_event_cb, NULL); + ret = ble_gap_adv_start(nimble_address_mode, NULL, BLE_HS_FOREVER, &adv_params, gap_event_cb, NULL); if (ret == 0) { return 0; } - ret = ble_gap_adv_start(BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT, NULL, BLE_HS_FOREVER, &adv_params, gap_event_cb, NULL); - if (ret == 0) { - return 0; - } - ret = ble_gap_adv_start(BLE_OWN_ADDR_RPA_RANDOM_DEFAULT, NULL, BLE_HS_FOREVER, &adv_params, gap_event_cb, NULL); - if (ret == 0) { - return 0; - } - ret = ble_gap_adv_start(BLE_OWN_ADDR_RANDOM, NULL, BLE_HS_FOREVER, &adv_params, gap_event_cb, NULL); - if (ret == 0) { - return 0; - } - DEBUG_EVENT_printf("ble_gap_adv_start: %d\n", ret); + DEBUG_printf("ble_gap_adv_start: %d\n", ret); return ble_hs_err_to_errno(ret); } @@ -410,7 +574,7 @@ void mp_bluetooth_gap_advertise_stop(void) { } static int characteristic_access_cb(uint16_t conn_handle, uint16_t value_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) { - DEBUG_EVENT_printf("characteristic_access_cb: conn_handle=%u value_handle=%u op=%u\n", conn_handle, value_handle, ctxt->op); + DEBUG_printf("characteristic_access_cb: conn_handle=%u value_handle=%u op=%u\n", conn_handle, value_handle, ctxt->op); if (!mp_bluetooth_is_active()) { return 0; } @@ -466,12 +630,13 @@ int mp_bluetooth_gatts_register_service_begin(bool append) { // Reset the gatt characteristic value db. mp_bluetooth_gatts_db_reset(MP_STATE_PORT(bluetooth_nimble_root_pointers)->gatts_db); - // By default, just register the default gap service. + // By default, just register the default gap/gatt service. ble_svc_gap_init(); + ble_svc_gatt_init(); if (!append) { // Unref any previous service definitions. - for (int i = 0; i < MP_STATE_PORT(bluetooth_nimble_root_pointers)->n_services; ++i) { + for (size_t i = 0; i < MP_STATE_PORT(bluetooth_nimble_root_pointers)->n_services; ++i) { MP_STATE_PORT(bluetooth_nimble_root_pointers)->services[i] = NULL; } MP_STATE_PORT(bluetooth_nimble_root_pointers)->n_services = 0; @@ -480,7 +645,7 @@ int mp_bluetooth_gatts_register_service_begin(bool append) { return 0; } -int mp_bluetooth_gatts_register_service_end() { +int mp_bluetooth_gatts_register_service_end(void) { int ret = ble_gatts_start(); if (ret != 0) { return ble_hs_err_to_errno(ret); @@ -498,7 +663,7 @@ int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, m struct ble_gatt_chr_def *characteristics = m_new(struct ble_gatt_chr_def, num_characteristics + 1); for (size_t i = 0; i < num_characteristics; ++i) { - characteristics[i].uuid = create_nimble_uuid(characteristic_uuids[i]); + characteristics[i].uuid = create_nimble_uuid(characteristic_uuids[i], NULL); characteristics[i].access_cb = characteristic_access_cb; characteristics[i].arg = NULL; characteristics[i].flags = characteristic_flags[i]; @@ -512,7 +677,7 @@ int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, m struct ble_gatt_dsc_def *descriptors = m_new(struct ble_gatt_dsc_def, num_descriptors[i] + 1); for (size_t j = 0; j < num_descriptors[i]; ++j) { - descriptors[j].uuid = create_nimble_uuid(descriptor_uuids[descriptor_index]); + descriptors[j].uuid = create_nimble_uuid(descriptor_uuids[descriptor_index], NULL); descriptors[j].access_cb = characteristic_access_cb; descriptors[j].att_flags = descriptor_flags[descriptor_index]; descriptors[j].min_key_size = 0; @@ -530,7 +695,7 @@ int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, m struct ble_gatt_svc_def *service = m_new(struct ble_gatt_svc_def, 2); service[0].type = BLE_GATT_SVC_TYPE_PRIMARY; - service[0].uuid = create_nimble_uuid(service_uuid); + service[0].uuid = create_nimble_uuid(service_uuid, NULL); service[0].includes = NULL; service[0].characteristics = characteristics; service[1].type = 0; // no more services @@ -584,13 +749,13 @@ int mp_bluetooth_gatts_notify(uint16_t conn_handle, uint16_t value_handle) { return ble_hs_err_to_errno(ble_gattc_notify(conn_handle, value_handle)); } -int mp_bluetooth_gatts_notify_send(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t *value_len) { +int mp_bluetooth_gatts_notify_send(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t value_len) { if (!mp_bluetooth_is_active()) { return ERRNO_BLUETOOTH_NOT_ACTIVE; } - struct os_mbuf *om = ble_hs_mbuf_from_flat(value, *value_len); + struct os_mbuf *om = ble_hs_mbuf_from_flat(value, value_len); if (om == NULL) { - return -1; + return MP_ENOMEM; } // TODO: check that notify_custom takes ownership of om, if not os_mbuf_free_chain(om). return ble_hs_err_to_errno(ble_gattc_notify_custom(conn_handle, value_handle, om)); @@ -600,6 +765,8 @@ int mp_bluetooth_gatts_indicate(uint16_t conn_handle, uint16_t value_handle) { if (!mp_bluetooth_is_active()) { return ERRNO_BLUETOOTH_NOT_ACTIVE; } + // This will raise BLE_GAP_EVENT_NOTIFY_TX with a status when it is + // acknowledged (or timeout/error). return ble_hs_err_to_errno(ble_gattc_indicate(conn_handle, value_handle)); } @@ -610,9 +777,26 @@ int mp_bluetooth_gatts_set_buffer(uint16_t value_handle, size_t len, bool append return mp_bluetooth_gatts_db_resize(MP_STATE_PORT(bluetooth_nimble_root_pointers)->gatts_db, value_handle, len, append); } +int mp_bluetooth_get_preferred_mtu(void) { + if (!mp_bluetooth_is_active()) { + mp_raise_OSError(ERRNO_BLUETOOTH_NOT_ACTIVE); + } + return ble_att_preferred_mtu(); +} + +int mp_bluetooth_set_preferred_mtu(uint16_t mtu) { + if (!mp_bluetooth_is_active()) { + return ERRNO_BLUETOOTH_NOT_ACTIVE; + } + if (ble_att_set_preferred_mtu(mtu)) { + return MP_EINVAL; + } + return 0; +} + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE -STATIC void gattc_on_data_available(uint16_t event, uint16_t conn_handle, uint16_t value_handle, const struct os_mbuf *om) { +STATIC void gattc_on_data_available(uint8_t event, uint16_t conn_handle, uint16_t value_handle, const struct os_mbuf *om) { size_t len = OS_MBUF_PKTLEN(om); mp_uint_t atomic_state; len = mp_bluetooth_gattc_on_data_available_start(event, conn_handle, value_handle, len, &atomic_state); @@ -626,7 +810,7 @@ STATIC void gattc_on_data_available(uint16_t event, uint16_t conn_handle, uint16 } STATIC int gap_scan_cb(struct ble_gap_event *event, void *arg) { - DEBUG_EVENT_printf("gap_scan_cb: event=%d type=%d\n", event->type, event->type == BLE_GAP_EVENT_DISC ? event->disc.event_type : -1); + DEBUG_printf("gap_scan_cb: event=%d type=%d\n", event->type, event->type == BLE_GAP_EVENT_DISC ? event->disc.event_type : -1); if (!mp_bluetooth_is_active()) { return 0; } @@ -647,7 +831,7 @@ STATIC int gap_scan_cb(struct ble_gap_event *event, void *arg) { return 0; } -int mp_bluetooth_gap_scan_start(int32_t duration_ms, int32_t interval_us, int32_t window_us) { +int mp_bluetooth_gap_scan_start(int32_t duration_ms, int32_t interval_us, int32_t window_us, bool active_scan) { if (!mp_bluetooth_is_active()) { return ERRNO_BLUETOOTH_NOT_ACTIVE; } @@ -659,14 +843,15 @@ int mp_bluetooth_gap_scan_start(int32_t duration_ms, int32_t interval_us, int32_ .window = MAX(BLE_HCI_SCAN_WINDOW_MIN, MIN(BLE_HCI_SCAN_WINDOW_MAX, window_us / BLE_HCI_SCAN_ITVL)), .filter_policy = BLE_HCI_CONN_FILT_NO_WL, .limited = 0, - .passive = 1, // TODO: Handle BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP in gap_scan_cb above. + .passive = active_scan ? 0 : 1, .filter_duplicates = 0, }; - int err = ble_gap_disc(BLE_OWN_ADDR_PUBLIC, duration_ms, &discover_params, gap_scan_cb, NULL); + int err = ble_gap_disc(nimble_address_mode, duration_ms, &discover_params, gap_scan_cb, NULL); return ble_hs_err_to_errno(err); } int mp_bluetooth_gap_scan_stop(void) { + DEBUG_printf("mp_bluetooth_gap_scan_stop\n"); if (!mp_bluetooth_is_active()) { return ERRNO_BLUETOOTH_NOT_ACTIVE; } @@ -683,7 +868,7 @@ int mp_bluetooth_gap_scan_stop(void) { // Central role: GAP events for a connected peripheral. STATIC int peripheral_gap_event_cb(struct ble_gap_event *event, void *arg) { - DEBUG_EVENT_printf("peripheral_gap_event_cb: event=%d\n", event->type); + DEBUG_printf("peripheral_gap_event_cb: event=%d\n", event->type); if (!mp_bluetooth_is_active()) { return 0; } @@ -724,6 +909,14 @@ STATIC int peripheral_gap_event_cb(struct ble_gap_event *event, void *arg) { // TODO break; + case BLE_GAP_EVENT_MTU: { + if (event->mtu.channel_id == BLE_L2CAP_CID_ATT) { + DEBUG_printf("peripheral_gap_event_cb: mtu update: conn_handle=%d cid=%d mtu=%d\n", event->mtu.conn_handle, event->mtu.channel_id, event->mtu.value); + mp_bluetooth_gatts_on_mtu_exchanged(event->mtu.conn_handle, event->mtu.value); + } + break; + } + default: break; } @@ -731,6 +924,7 @@ STATIC int peripheral_gap_event_cb(struct ble_gap_event *event, void *arg) { } int mp_bluetooth_gap_peripheral_connect(uint8_t addr_type, const uint8_t *addr, int32_t duration_ms) { + DEBUG_printf("mp_bluetooth_gap_peripheral_connect\n"); if (!mp_bluetooth_is_active()) { return ERRNO_BLUETOOTH_NOT_ACTIVE; } @@ -751,58 +945,78 @@ int mp_bluetooth_gap_peripheral_connect(uint8_t addr_type, const uint8_t *addr, }; ble_addr_t addr_nimble = create_nimble_addr(addr_type, addr); - int err = ble_gap_connect(BLE_OWN_ADDR_PUBLIC, &addr_nimble, duration_ms, ¶ms, &peripheral_gap_event_cb, NULL); + int err = ble_gap_connect(nimble_address_mode, &addr_nimble, duration_ms, ¶ms, &peripheral_gap_event_cb, NULL); return ble_hs_err_to_errno(err); } STATIC int peripheral_discover_service_cb(uint16_t conn_handle, const struct ble_gatt_error *error, const struct ble_gatt_svc *service, void *arg) { - DEBUG_EVENT_printf("peripheral_discover_service_cb: conn_handle=%d status=%d start_handle=%d\n", conn_handle, error->status, service ? service->start_handle : -1); + DEBUG_printf("peripheral_discover_service_cb: conn_handle=%d status=%d start_handle=%d\n", conn_handle, error->status, service ? service->start_handle : -1); if (!mp_bluetooth_is_active()) { return 0; } if (error->status == 0) { mp_obj_bluetooth_uuid_t service_uuid = create_mp_uuid(&service->uuid); mp_bluetooth_gattc_on_primary_service_result(conn_handle, service->start_handle, service->end_handle, &service_uuid); + } else { + mp_bluetooth_gattc_on_discover_complete(MP_BLUETOOTH_IRQ_GATTC_SERVICE_DONE, conn_handle, error->status == BLE_HS_EDONE ? 0 : error->status); } return 0; } -int mp_bluetooth_gattc_discover_primary_services(uint16_t conn_handle) { +int mp_bluetooth_gattc_discover_primary_services(uint16_t conn_handle, const mp_obj_bluetooth_uuid_t *uuid) { if (!mp_bluetooth_is_active()) { return ERRNO_BLUETOOTH_NOT_ACTIVE; } - int err = ble_gattc_disc_all_svcs(conn_handle, &peripheral_discover_service_cb, NULL); + int err; + if (uuid) { + ble_uuid_any_t nimble_uuid; + create_nimble_uuid(uuid, &nimble_uuid); + err = ble_gattc_disc_svc_by_uuid(conn_handle, &nimble_uuid.u, &peripheral_discover_service_cb, NULL); + } else { + err = ble_gattc_disc_all_svcs(conn_handle, &peripheral_discover_service_cb, NULL); + } return ble_hs_err_to_errno(err); } STATIC int ble_gatt_characteristic_cb(uint16_t conn_handle, const struct ble_gatt_error *error, const struct ble_gatt_chr *characteristic, void *arg) { - DEBUG_EVENT_printf("ble_gatt_characteristic_cb: conn_handle=%d status=%d def_handle=%d val_handle=%d\n", conn_handle, error->status, characteristic ? characteristic->def_handle : -1, characteristic ? characteristic->val_handle : -1); + DEBUG_printf("ble_gatt_characteristic_cb: conn_handle=%d status=%d def_handle=%d val_handle=%d\n", conn_handle, error->status, characteristic ? characteristic->def_handle : -1, characteristic ? characteristic->val_handle : -1); if (!mp_bluetooth_is_active()) { return 0; } if (error->status == 0) { mp_obj_bluetooth_uuid_t characteristic_uuid = create_mp_uuid(&characteristic->uuid); mp_bluetooth_gattc_on_characteristic_result(conn_handle, characteristic->def_handle, characteristic->val_handle, characteristic->properties, &characteristic_uuid); + } else { + mp_bluetooth_gattc_on_discover_complete(MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_DONE, conn_handle, error->status == BLE_HS_EDONE ? 0 : error->status); } return 0; } -int mp_bluetooth_gattc_discover_characteristics(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle) { +int mp_bluetooth_gattc_discover_characteristics(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle, const mp_obj_bluetooth_uuid_t *uuid) { if (!mp_bluetooth_is_active()) { return ERRNO_BLUETOOTH_NOT_ACTIVE; } - int err = ble_gattc_disc_all_chrs(conn_handle, start_handle, end_handle, &ble_gatt_characteristic_cb, NULL); + int err; + if (uuid) { + ble_uuid_any_t nimble_uuid; + create_nimble_uuid(uuid, &nimble_uuid); + err = ble_gattc_disc_chrs_by_uuid(conn_handle, start_handle, end_handle, &nimble_uuid.u, &ble_gatt_characteristic_cb, NULL); + } else { + err = ble_gattc_disc_all_chrs(conn_handle, start_handle, end_handle, &ble_gatt_characteristic_cb, NULL); + } return ble_hs_err_to_errno(err); } STATIC int ble_gatt_descriptor_cb(uint16_t conn_handle, const struct ble_gatt_error *error, uint16_t characteristic_val_handle, const struct ble_gatt_dsc *descriptor, void *arg) { - DEBUG_EVENT_printf("ble_gatt_descriptor_cb: conn_handle=%d status=%d chr_handle=%d dsc_handle=%d\n", conn_handle, error->status, characteristic_val_handle, descriptor ? descriptor->handle : -1); + DEBUG_printf("ble_gatt_descriptor_cb: conn_handle=%d status=%d chr_handle=%d dsc_handle=%d\n", conn_handle, error->status, characteristic_val_handle, descriptor ? descriptor->handle : -1); if (!mp_bluetooth_is_active()) { return 0; } if (error->status == 0) { mp_obj_bluetooth_uuid_t descriptor_uuid = create_mp_uuid(&descriptor->uuid); mp_bluetooth_gattc_on_descriptor_result(conn_handle, descriptor->handle, &descriptor_uuid); + } else { + mp_bluetooth_gattc_on_discover_complete(MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_DONE, conn_handle, error->status == BLE_HS_EDONE ? 0 : error->status); } return 0; } @@ -816,14 +1030,14 @@ int mp_bluetooth_gattc_discover_descriptors(uint16_t conn_handle, uint16_t start } STATIC int ble_gatt_attr_read_cb(uint16_t conn_handle, const struct ble_gatt_error *error, struct ble_gatt_attr *attr, void *arg) { - DEBUG_EVENT_printf("ble_gatt_attr_read_cb: conn_handle=%d status=%d handle=%d\n", conn_handle, error->status, attr ? attr->handle : -1); + DEBUG_printf("ble_gatt_attr_read_cb: conn_handle=%d status=%d handle=%d\n", conn_handle, error->status, attr ? attr->handle : -1); if (!mp_bluetooth_is_active()) { return 0; } - // TODO: Maybe send NULL if error->status non-zero. if (error->status == 0) { gattc_on_data_available(MP_BLUETOOTH_IRQ_GATTC_READ_RESULT, conn_handle, attr->handle, attr->om); } + mp_bluetooth_gattc_on_read_write_status(MP_BLUETOOTH_IRQ_GATTC_READ_DONE, conn_handle, attr ? attr->handle : -1, error->status); return 0; } @@ -837,11 +1051,11 @@ int mp_bluetooth_gattc_read(uint16_t conn_handle, uint16_t value_handle) { } STATIC int ble_gatt_attr_write_cb(uint16_t conn_handle, const struct ble_gatt_error *error, struct ble_gatt_attr *attr, void *arg) { - DEBUG_EVENT_printf("ble_gatt_attr_write_cb: conn_handle=%d status=%d handle=%d\n", conn_handle, error->status, attr ? attr->handle : -1); + DEBUG_printf("ble_gatt_attr_write_cb: conn_handle=%d status=%d handle=%d\n", conn_handle, error->status, attr ? attr->handle : -1); if (!mp_bluetooth_is_active()) { return 0; } - mp_bluetooth_gattc_on_write_status(conn_handle, attr->handle, error->status); + mp_bluetooth_gattc_on_read_write_status(MP_BLUETOOTH_IRQ_GATTC_WRITE_DONE, conn_handle, attr->handle, error->status); return 0; } @@ -861,6 +1075,13 @@ int mp_bluetooth_gattc_write(uint16_t conn_handle, uint16_t value_handle, const return ble_hs_err_to_errno(err); } +int mp_bluetooth_gattc_exchange_mtu(uint16_t conn_handle) { + DEBUG_printf("mp_bluetooth_exchange_mtu: conn_handle=%d mtu=%d\n", conn_handle, ble_att_preferred_mtu()); + + // Using NULL callback (we'll get notified in gap_event_cb instead). + return ble_hs_err_to_errno(ble_gattc_exchange_mtu(conn_handle, NULL, NULL)); +} + #endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE #endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE diff --git a/extmod/nimble/modbluetooth_nimble.h b/extmod/nimble/modbluetooth_nimble.h index f44e1d69dca52..7e401781e7f9c 100644 --- a/extmod/nimble/modbluetooth_nimble.h +++ b/extmod/nimble/modbluetooth_nimble.h @@ -43,15 +43,27 @@ typedef struct _mp_bluetooth_nimble_root_pointers_t { enum { MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF, MP_BLUETOOTH_NIMBLE_BLE_STATE_STARTING, + MP_BLUETOOTH_NIMBLE_BLE_STATE_WAITING_FOR_SYNC, MP_BLUETOOTH_NIMBLE_BLE_STATE_ACTIVE, MP_BLUETOOTH_NIMBLE_BLE_STATE_STOPPING, }; extern volatile int mp_bluetooth_nimble_ble_state; -void mp_bluetooth_nimble_port_preinit(void); -void mp_bluetooth_nimble_port_postinit(void); -void mp_bluetooth_nimble_port_deinit(void); +// --- Optionally provided by the MicroPython port. --------------------------- +// (default implementations provided by modbluetooth_nimble.c) + +// Tell the port to init the UART and start the HCI controller. +void mp_bluetooth_nimble_port_hci_init(void); + +// Tell the port to deinit the UART and shutdown the HCI controller. +void mp_bluetooth_nimble_port_hci_deinit(void); + +// Tell the port to run its background task (i.e. poll the UART and pump events). void mp_bluetooth_nimble_port_start(void); +// Tell the port to stop its background task. +void mp_bluetooth_nimble_port_shutdown(void); + + #endif // MICROPY_INCLUDED_EXTMOD_NIMBLE_MODBLUETOOTH_NIMBLE_H diff --git a/extmod/nimble/nimble.mk b/extmod/nimble/nimble.mk index 4e1642c98a998..f8a68bc6f52b6 100644 --- a/extmod/nimble/nimble.mk +++ b/extmod/nimble/nimble.mk @@ -2,16 +2,19 @@ ifeq ($(MICROPY_BLUETOOTH_NIMBLE),1) -EXTMOD_SRC_C += extmod/nimble/modbluetooth_nimble.c +EXTMOD_DIR = extmod +NIMBLE_EXTMOD_DIR = $(EXTMOD_DIR)/nimble -CFLAGS_MOD += -DMICROPY_BLUETOOTH_NIMBLE=1 +EXTMOD_SRC_C += $(NIMBLE_EXTMOD_DIR)/modbluetooth_nimble.c -NIMBLE_EXTMOD_DIR = extmod/nimble +CFLAGS_MOD += -DMICROPY_BLUETOOTH_NIMBLE=1 # Use NimBLE from the submodule in lib/mynewt-nimble by default, # allowing a port to use their own system version (e.g. ESP32). MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY ?= 0 +CFLAGS_MOD += -DMICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY=$(MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY) + ifeq ($(MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY),0) NIMBLE_LIB_DIR = lib/mynewt-nimble @@ -82,7 +85,7 @@ LIB_SRC_C += $(addprefix $(NIMBLE_LIB_DIR)/, \ ) EXTMOD_SRC_C += $(addprefix $(NIMBLE_EXTMOD_DIR)/, \ - nimble/npl_os.c \ + nimble/nimble_npl_os.c \ hal/hal_uart.c \ ) @@ -98,7 +101,7 @@ INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/nimble/include INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/nimble/transport/uart/include INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/porting/nimble/include -$(BUILD)/$(NIMBLE_LIB_DIR)/%.o: CFLAGS += -Wno-maybe-uninitialized -Wno-pointer-arith -Wno-unused-but-set-variable -Wno-format +$(BUILD)/$(NIMBLE_LIB_DIR)/%.o: CFLAGS += -Wno-maybe-uninitialized -Wno-pointer-arith -Wno-unused-but-set-variable -Wno-format -Wno-sign-compare endif diff --git a/extmod/nimble/nimble/npl_os.c b/extmod/nimble/nimble/nimble_npl_os.c similarity index 57% rename from extmod/nimble/nimble/npl_os.c rename to extmod/nimble/nimble/nimble_npl_os.c index 620dcb0aec13c..2ec012940f763 100644 --- a/extmod/nimble/nimble/npl_os.c +++ b/extmod/nimble/nimble/nimble_npl_os.c @@ -29,16 +29,19 @@ #include "py/runtime.h" #include "nimble/ble.h" #include "nimble/nimble_npl.h" -#include "nimble/nimble_hci_uart.h" +#include "extmod/nimble/hal/hal_uart.h" -#define DEBUG_OS_printf(...) //printf(__VA_ARGS__) -#define DEBUG_MALLOC_printf(...) //printf(__VA_ARGS__) -#define DEBUG_EVENT_printf(...) //printf(__VA_ARGS__) -#define DEBUG_MUTEX_printf(...) //printf(__VA_ARGS__) -#define DEBUG_SEM_printf(...) //printf(__VA_ARGS__) -#define DEBUG_CALLOUT_printf(...) //printf(__VA_ARGS__) -#define DEBUG_TIME_printf(...) //printf(__VA_ARGS__) -#define DEBUG_CRIT_printf(...) //printf(__VA_ARGS__) +#include "extmod/modbluetooth.h" +#include "extmod/nimble/modbluetooth_nimble.h" + +#define DEBUG_OS_printf(...) // printf(__VA_ARGS__) +#define DEBUG_MALLOC_printf(...) // printf(__VA_ARGS__) +#define DEBUG_EVENT_printf(...) // printf(__VA_ARGS__) +#define DEBUG_MUTEX_printf(...) // printf(__VA_ARGS__) +#define DEBUG_SEM_printf(...) // printf(__VA_ARGS__) +#define DEBUG_CALLOUT_printf(...) // printf(__VA_ARGS__) +#define DEBUG_TIME_printf(...) // printf(__VA_ARGS__) +#define DEBUG_CRIT_printf(...) // printf(__VA_ARGS__) bool ble_npl_os_started(void) { DEBUG_OS_printf("ble_npl_os_started\n"); @@ -56,44 +59,58 @@ void *ble_npl_get_current_task_id(void) { // Maintain a linked list of heap memory that we've passed to Nimble, // discoverable via the bluetooth_nimble_memory root pointer. -// MP_STATE_PORT(bluetooth_nimble_memory) is a pointer to [next, prev, data...]. +typedef struct _mp_bluetooth_nimble_malloc_t { + struct _mp_bluetooth_nimble_malloc_t *prev; + struct _mp_bluetooth_nimble_malloc_t *next; + size_t size; + uint8_t data[]; +} mp_bluetooth_nimble_malloc_t; // TODO: This is duplicated from mbedtls. Perhaps make this a generic feature? -void *m_malloc_bluetooth(size_t size) { - void **ptr = m_malloc0(size + 2 * sizeof(uintptr_t)); - if (MP_STATE_PORT(bluetooth_nimble_memory) != NULL) { - MP_STATE_PORT(bluetooth_nimble_memory)[0] = ptr; +STATIC void *m_malloc_bluetooth(size_t size) { + size += sizeof(mp_bluetooth_nimble_malloc_t); + mp_bluetooth_nimble_malloc_t *alloc = m_malloc0(size); + alloc->size = size; + alloc->next = MP_STATE_PORT(bluetooth_nimble_memory); + if (alloc->next) { + alloc->next->prev = alloc; } - ptr[0] = NULL; - ptr[1] = MP_STATE_PORT(bluetooth_nimble_memory); - MP_STATE_PORT(bluetooth_nimble_memory) = ptr; - return &ptr[2]; + MP_STATE_PORT(bluetooth_nimble_memory) = alloc; + return alloc->data; +} + +STATIC mp_bluetooth_nimble_malloc_t* get_nimble_malloc(void *ptr) { + return (mp_bluetooth_nimble_malloc_t*)((uintptr_t)ptr - sizeof(mp_bluetooth_nimble_malloc_t)); } -void m_free_bluetooth(void *ptr_in) { - void **ptr = &((void**)ptr_in)[-2]; - if (ptr[1] != NULL) { - ((void**)ptr[1])[0] = ptr[0]; +STATIC void m_free_bluetooth(void *ptr) { + mp_bluetooth_nimble_malloc_t *alloc = get_nimble_malloc(ptr); + if (alloc->next) { + alloc->next->prev = alloc->prev; } - if (ptr[0] != NULL) { - ((void**)ptr[0])[1] = ptr[1]; + if (alloc->prev) { + alloc->prev->next = alloc->next; } else { - MP_STATE_PORT(bluetooth_nimble_memory) = ptr[1]; + MP_STATE_PORT(bluetooth_nimble_memory) = NULL; } - m_free(ptr); + m_free(alloc + #if MICROPY_MALLOC_USES_ALLOCATED_SIZE + , alloc->size + #endif + ); } // Check if a nimble ptr is tracked. // If it isn't, that means that it's from a previous soft-reset cycle. STATIC bool is_valid_nimble_malloc(void *ptr) { DEBUG_MALLOC_printf("NIMBLE is_valid_nimble_malloc(%p)\n", ptr); - void** search = MP_STATE_PORT(bluetooth_nimble_memory); - while (search) { - if (&search[2] == ptr) { + mp_bluetooth_nimble_malloc_t *alloc = MP_STATE_PORT(bluetooth_nimble_memory); + while (alloc) { + DEBUG_MALLOC_printf("NIMBLE checking: %p\n", alloc->data); + if (alloc->data == ptr) { return true; } - - search = (void**)search[1]; + alloc = alloc->next; } return false; } @@ -108,33 +125,64 @@ void *nimble_malloc(size_t size) { // Only free if it's still a valid pointer. void nimble_free(void *ptr) { DEBUG_MALLOC_printf("NIMBLE free(%p)\n", ptr); - if (ptr && is_valid_nimble_malloc(ptr)) { - m_free_bluetooth(ptr); + + if (ptr) { + // After a stack re-init, NimBLE has variables in BSS that might be + // still pointing to old allocations from a previous init. We can't do + // anything about this (e.g. ble_gatts_free_mem is private). But we + // can identify that this is a non-null, invalid alloc because it + // won't be in our list, so ignore it because it is effectively free'd + // anyway (it's not referenced by anything the GC can find). + if (is_valid_nimble_malloc(ptr)) { + m_free_bluetooth(ptr); + } } } // Only realloc if it's still a valid pointer. Otherwise just malloc. -void *nimble_realloc(void *ptr, size_t size) { - // This is only used by ble_gatts.c to grow the queue of pending services to be registered. - DEBUG_MALLOC_printf("NIMBLE realloc(%p, %u)\n", ptr, (uint)size); - void *ptr2 = nimble_malloc(size); - if (ptr && is_valid_nimble_malloc(ptr)) { - // If it's a realloc and we still have the old data, then copy it. - // This will happen as we add services. - memcpy(ptr2, ptr, size); - m_free_bluetooth(ptr); +void *nimble_realloc(void *ptr, size_t new_size) { + DEBUG_MALLOC_printf("NIMBLE realloc(%p, %u)\n", ptr, (uint)new_size); + + if (!ptr) { + return nimble_malloc(new_size); } + + assert(is_valid_nimble_malloc(ptr)); + + // Existing alloc is big enough. + mp_bluetooth_nimble_malloc_t *alloc = get_nimble_malloc(ptr); + size_t old_size = alloc->size - sizeof(mp_bluetooth_nimble_malloc_t); + if (old_size >= new_size) { + return ptr; + } + + // Allocate a new, larger region. + void *ptr2 = m_malloc_bluetooth(new_size); + + // Copy old, smaller region into new region. + memcpy(ptr2, ptr, old_size); + m_free_bluetooth(ptr); + + DEBUG_MALLOC_printf(" --> %p\n", ptr2); + return ptr2; } +// No-op implementation (only used by NimBLE logging). +int nimble_sprintf(char *str, const char *fmt, ...) { + str[0] = 0; + return 0; +} + /******************************************************************************/ // EVENTQ struct ble_npl_eventq *global_eventq = NULL; -void os_eventq_run_all(void) { +void mp_bluetooth_nimble_os_eventq_run_all(void) { for (struct ble_npl_eventq *evq = global_eventq; evq != NULL; evq = evq->nextq) { - while (evq->head != NULL) { + int n = 0; + while (evq->head != NULL && mp_bluetooth_nimble_ble_state > MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) { struct ble_npl_event *ev = evq->head; evq->head = ev->next; if (ev->next) { @@ -145,6 +193,13 @@ void os_eventq_run_all(void) { DEBUG_EVENT_printf("event_run(%p)\n", ev); ev->fn(ev); DEBUG_EVENT_printf("event_run(%p) done\n", ev); + + if (++n > 3) { + // Limit to running 3 tasks per queue. + // Some tasks (such as reset) can enqueue themselves + // making this an infinite loop (while in PENDSV). + break; + } } } } @@ -203,21 +258,62 @@ void ble_npl_event_set_arg(struct ble_npl_event *ev, void *arg) { /******************************************************************************/ // MUTEX +// This is what MICROPY_BEGIN_ATOMIC_SECTION returns on Unix (i.e. we don't +// need to preserve the atomic state to unlock). +#define ATOMIC_STATE_MUTEX_NOT_HELD 0xffffffff + ble_npl_error_t ble_npl_mutex_init(struct ble_npl_mutex *mu) { DEBUG_MUTEX_printf("ble_npl_mutex_init(%p)\n", mu); mu->locked = 0; + mu->atomic_state = ATOMIC_STATE_MUTEX_NOT_HELD; return BLE_NPL_OK; } ble_npl_error_t ble_npl_mutex_pend(struct ble_npl_mutex *mu, ble_npl_time_t timeout) { - DEBUG_MUTEX_printf("ble_npl_mutex_pend(%p, %u) locked=%u\n", mu, (uint)timeout, (uint)mu->locked); - mu->locked = 1; + DEBUG_MUTEX_printf("ble_npl_mutex_pend(%p, %u) locked=%u irq=%d\n", mu, (uint)timeout, (uint)mu->locked); + + // This is a recursive mutex which we implement on top of the IRQ priority + // scheme. Unfortunately we have a single piece of global storage, where + // enter/exit critical needs an "atomic state". + + // There are two different acquirers, either running in a VM thread (i.e. + // a direct Python call into NimBLE), or in the NimBLE task (i.e. polling + // or UART RX). + + // On STM32 the NimBLE task runs in PENDSV, so cannot be interrupted by a VM thread. + // Therefore we only need to ensure that a VM thread that acquires a currently-unlocked mutex + // now raises the priority (thus preventing context switches to other VM threads and + // the PENDSV irq). If the mutex is already locked, then it must have been acquired + // by us. + + // On Unix, the critical section is completely recursive and doesn't require us to manage + // state so we just acquire and release every time. + + // TODO: The "volatile" on locked/atomic_state isn't enough to protect against memory re-ordering. + + // First acquirer of this mutex always enters the critical section, unless + // we're on Unix where it happens every time. + if (mu->atomic_state == ATOMIC_STATE_MUTEX_NOT_HELD) { + mu->atomic_state = mp_bluetooth_nimble_hci_uart_enter_critical(); + } + + ++mu->locked; + return BLE_NPL_OK; } ble_npl_error_t ble_npl_mutex_release(struct ble_npl_mutex *mu) { - DEBUG_MUTEX_printf("ble_npl_mutex_release(%p) locked=%u\n", mu, (uint)mu->locked); - mu->locked = 0; + DEBUG_MUTEX_printf("ble_npl_mutex_release(%p) locked=%u irq=%d\n", mu, (uint)mu->locked); + assert(mu->locked > 0); + + --mu->locked; + + // Only exit the critical section for the final release, unless we're on Unix. + if (mu->locked == 0 || mu->atomic_state == ATOMIC_STATE_MUTEX_NOT_HELD) { + mp_bluetooth_nimble_hci_uart_exit_critical(mu->atomic_state); + mu->atomic_state = ATOMIC_STATE_MUTEX_NOT_HELD; + } + return BLE_NPL_OK; } @@ -232,24 +328,40 @@ ble_npl_error_t ble_npl_sem_init(struct ble_npl_sem *sem, uint16_t tokens) { ble_npl_error_t ble_npl_sem_pend(struct ble_npl_sem *sem, ble_npl_time_t timeout) { DEBUG_SEM_printf("ble_npl_sem_pend(%p, %u) count=%u\n", sem, (uint)timeout, (uint)sem->count); + + // This is called by NimBLE to synchronously wait for an HCI ACK. The + // corresponding ble_npl_sem_release is called directly by the UART rx + // handler (i.e. hal_uart_rx_cb in extmod/nimble/hal/hal_uart.c). + + // So this implementation just polls the UART until either the semaphore + // is released, or the timeout occurs. + if (sem->count == 0) { uint32_t t0 = mp_hal_ticks_ms(); while (sem->count == 0 && mp_hal_ticks_ms() - t0 < timeout) { - // This function may be called at thread-level, so execute - // mp_bluetooth_nimble_hci_uart_process at raised priority. + // This can be called either from code running in NimBLE's "task" + // (i.e. PENDSV) or directly by application code, so for the + // latter case, prevent the "task" from running while we poll the + // UART directly. MICROPY_PY_BLUETOOTH_ENTER mp_bluetooth_nimble_hci_uart_process(); MICROPY_PY_BLUETOOTH_EXIT + if (sem->count != 0) { break; } - __WFI(); + + // Because we're polling, might as well wait for a UART IRQ indicating + // more data available. + mp_bluetooth_nimble_hci_uart_wfi(); } + if (sem->count == 0) { - printf("timeout\n"); + DEBUG_SEM_printf("ble_npl_sem_pend: semaphore timeout\n"); return BLE_NPL_TIMEOUT; } - DEBUG_SEM_printf("got response in %u ms\n", (int)(mp_hal_ticks_ms() - t0)); + + DEBUG_SEM_printf("ble_npl_sem_pend: acquired in %u ms\n", (int)(mp_hal_ticks_ms() - t0)); } sem->count -= 1; return BLE_NPL_OK; @@ -271,7 +383,7 @@ uint16_t ble_npl_sem_get_count(struct ble_npl_sem *sem) { static struct ble_npl_callout *global_callout = NULL; -void os_callout_process(void) { +void mp_bluetooth_nimble_os_callout_process(void) { uint32_t tnow = mp_hal_ticks_ms(); for (struct ble_npl_callout *c = global_callout; c != NULL; c = c->nextc) { if (!c->active) { @@ -380,12 +492,24 @@ void ble_npl_time_delay(ble_npl_time_t ticks) { /******************************************************************************/ // CRITICAL +// This is used anywhere NimBLE modifies global data structures. +// We need to protect between: +// - A MicroPython VM thread. +// - The NimBLE "task" (e.g. PENDSV on STM32, pthread on Unix). +// On STM32, by disabling PENDSV, we ensure that either: +// - If we're in the NimBLE task, we're exclusive anyway. +// - If we're in a VM thread, we can't be interrupted by the NimBLE task, or switched to another thread. +// On Unix, there's a global mutex. + +// TODO: Both ports currently use MICROPY_PY_BLUETOOTH_ENTER in their implementation, +// maybe this doesn't need to be port-specific? + uint32_t ble_npl_hw_enter_critical(void) { DEBUG_CRIT_printf("ble_npl_hw_enter_critical()\n"); - return raise_irq_pri(15); + return mp_bluetooth_nimble_hci_uart_enter_critical(); } void ble_npl_hw_exit_critical(uint32_t ctx) { DEBUG_CRIT_printf("ble_npl_hw_exit_critical(%u)\n", (uint)ctx); - restore_irq_pri(ctx); + mp_bluetooth_nimble_hci_uart_exit_critical(ctx); } diff --git a/extmod/nimble/nimble/nimble_npl_os.h b/extmod/nimble/nimble/nimble_npl_os.h index 3d886fedb58fd..3ef07aa9ccd12 100644 --- a/extmod/nimble/nimble/nimble_npl_os.h +++ b/extmod/nimble/nimble/nimble_npl_os.h @@ -24,12 +24,16 @@ * THE SOFTWARE. */ -#ifndef MICROPY_INCLUDED_STM32_NIMBLE_NIMBLE_NPL_OS_H -#define MICROPY_INCLUDED_STM32_NIMBLE_NIMBLE_NPL_OS_H +#ifndef MICROPY_INCLUDED_STM32_NIMBLE_NIMBLE_NIMBLE_NPL_OS_H +#define MICROPY_INCLUDED_STM32_NIMBLE_NIMBLE_NIMBLE_NPL_OS_H + +// This is included by nimble/nimble_npl.h -- include that rather than this file directly. #include -#define BLE_NPL_OS_ALIGNMENT (4) +// --- Configuration of NimBLE data structures -------------------------------- + +#define BLE_NPL_OS_ALIGNMENT (sizeof(uintptr_t)) #define BLE_NPL_TIME_FOREVER (0xffffffff) typedef uint32_t ble_npl_time_t; @@ -57,10 +61,22 @@ struct ble_npl_callout { struct ble_npl_mutex { volatile uint8_t locked; + volatile uint32_t atomic_state; }; struct ble_npl_sem { volatile uint16_t count; }; +// --- Called by the MicroPython port ----------------------------------------- + +void mp_bluetooth_nimble_os_eventq_run_all(void); +void mp_bluetooth_nimble_os_callout_process(void); + +// --- Must be provided by the MicroPython port ------------------------------- + +void mp_bluetooth_nimble_hci_uart_wfi(void); +uint32_t mp_bluetooth_nimble_hci_uart_enter_critical(void); +void mp_bluetooth_nimble_hci_uart_exit_critical(uint32_t atomic_state); + #endif // MICROPY_INCLUDED_STM32_NIMBLE_NIMBLE_NPL_OS_H diff --git a/extmod/nimble/syscfg/syscfg.h b/extmod/nimble/syscfg/syscfg.h index 6ac420f35e550..8a6f2338be2ab 100644 --- a/extmod/nimble/syscfg/syscfg.h +++ b/extmod/nimble/syscfg/syscfg.h @@ -2,12 +2,14 @@ #define MICROPY_INCLUDED_EXTMOD_NIMBLE_SYSCFG_H #include "py/mphal.h" -#include "uart.h" + +#include "mpnimbleport.h" void *nimble_malloc(size_t size); void nimble_free(void *ptr); void *nimble_realloc(void *ptr, size_t size); +// Redirect NimBLE malloc to the GC heap. #define malloc(size) nimble_malloc(size) #define free(ptr) nimble_free(ptr) #define realloc(ptr, size) nimble_realloc(ptr, size) @@ -88,6 +90,7 @@ int nimble_sprintf(char *str, const char *fmt, ...); #define MYNEWT_VAL_BLE_GATT_WRITE_NO_RSP (MYNEWT_VAL_BLE_ROLE_CENTRAL) #define MYNEWT_VAL_BLE_GATT_WRITE_RELIABLE (MYNEWT_VAL_BLE_ROLE_CENTRAL) #define MYNEWT_VAL_BLE_HOST (1) +#define MYNEWT_VAL_BLE_HS_AUTO_START (1) #define MYNEWT_VAL_BLE_HS_DEBUG (0) #define MYNEWT_VAL_BLE_HS_FLOW_CTRL (0) #define MYNEWT_VAL_BLE_HS_FLOW_CTRL_ITVL (1000) diff --git a/extmod/uasyncio/__init__.py b/extmod/uasyncio/__init__.py index da8b58061e6ca..08f924cf29b9b 100644 --- a/extmod/uasyncio/__init__.py +++ b/extmod/uasyncio/__init__.py @@ -7,6 +7,7 @@ _attrs = { "wait_for": "funcs", + "wait_for_ms": "funcs", "gather": "funcs", "Event": "event", "Lock": "lock", diff --git a/extmod/uasyncio/core.py b/extmod/uasyncio/core.py index 689487d36a6fd..045b4cd139182 100644 --- a/extmod/uasyncio/core.py +++ b/extmod/uasyncio/core.py @@ -53,7 +53,7 @@ def __next__(self): # Use a SingletonGenerator to do it without allocating on the heap def sleep_ms(t, sgen=SingletonGenerator()): assert sgen.state is None - sgen.state = ticks_add(ticks(), t) + sgen.state = ticks_add(ticks(), max(0, t)) return sgen diff --git a/extmod/uasyncio/funcs.py b/extmod/uasyncio/funcs.py index 7a4bddf25695a..6e1305c94f418 100644 --- a/extmod/uasyncio/funcs.py +++ b/extmod/uasyncio/funcs.py @@ -4,16 +4,16 @@ from . import core -async def wait_for(aw, timeout): +async def wait_for(aw, timeout, sleep=core.sleep): aw = core._promote_to_task(aw) if timeout is None: return await aw - def cancel(aw, timeout): - await core.sleep(timeout) + def cancel(aw, timeout, sleep): + await sleep(timeout) aw.cancel() - cancel_task = core.create_task(cancel(aw, timeout)) + cancel_task = core.create_task(cancel(aw, timeout, sleep)) try: ret = await aw except core.CancelledError: @@ -29,6 +29,10 @@ def cancel(aw, timeout): return ret +def wait_for_ms(aw, timeout): + return wait_for(aw, timeout, core.sleep_ms) + + async def gather(*aws, return_exceptions=False): ts = [core._promote_to_task(aw) for aw in aws] for i in range(len(ts)): diff --git a/extmod/uasyncio/stream.py b/extmod/uasyncio/stream.py index 2a1efd1a17bfc..b6d787e4f0336 100644 --- a/extmod/uasyncio/stream.py +++ b/extmod/uasyncio/stream.py @@ -30,6 +30,18 @@ async def read(self, n): yield core._io_queue.queue_read(self.s) return self.s.read(n) + async def readexactly(self, n): + r = b"" + while n: + yield core._io_queue.queue_read(self.s) + r2 = self.s.read(n) + if r2 is not None: + if not len(r2): + raise EOFError + r += r2 + n -= len(r2) + return r + async def readline(self): l = b"" while True: diff --git a/extmod/utime_mphal.c b/extmod/utime_mphal.c index 6aff2cac725c2..d053cf128b871 100644 --- a/extmod/utime_mphal.c +++ b/extmod/utime_mphal.c @@ -99,4 +99,10 @@ STATIC mp_obj_t time_ticks_add(mp_obj_t ticks_in, mp_obj_t delta_in) { } MP_DEFINE_CONST_FUN_OBJ_2(mp_utime_ticks_add_obj, time_ticks_add); +// Returns the number of nanoseconds since the Epoch, as an integer. +STATIC mp_obj_t time_time_ns(void) { + return mp_obj_new_int_from_ull(mp_hal_time_ns()); +} +MP_DEFINE_CONST_FUN_OBJ_0(mp_utime_time_ns_obj, time_time_ns); + #endif // MICROPY_PY_UTIME_MP_HAL diff --git a/extmod/utime_mphal.h b/extmod/utime_mphal.h index 88a9ed4d3756d..57fc348832812 100644 --- a/extmod/utime_mphal.h +++ b/extmod/utime_mphal.h @@ -37,5 +37,6 @@ MP_DECLARE_CONST_FUN_OBJ_0(mp_utime_ticks_us_obj); MP_DECLARE_CONST_FUN_OBJ_0(mp_utime_ticks_cpu_obj); MP_DECLARE_CONST_FUN_OBJ_2(mp_utime_ticks_diff_obj); MP_DECLARE_CONST_FUN_OBJ_2(mp_utime_ticks_add_obj); +MP_DECLARE_CONST_FUN_OBJ_0(mp_utime_time_ns_obj); #endif // MICROPY_INCLUDED_EXTMOD_UTIME_MPHAL_H diff --git a/extmod/vfs.c b/extmod/vfs.c index 79a8e8509ded1..3cb7af1b434f9 100644 --- a/extmod/vfs.c +++ b/extmod/vfs.c @@ -83,12 +83,8 @@ mp_vfs_mount_t *mp_vfs_lookup_path(const char *path, const char **path_out) { } } - // if we get here then there's nothing mounted on / - - if (is_abs) { - // path began with / and was not found - return MP_VFS_NONE; - } + // if we get here then there's nothing mounted on /, so the path doesn't exist + return MP_VFS_NONE; } // a relative path within a mounted device @@ -322,7 +318,6 @@ MP_DEFINE_CONST_FUN_OBJ_KW(mp_vfs_open_obj, 0, mp_vfs_open); mp_obj_t mp_vfs_chdir(mp_obj_t path_in) { mp_obj_t path_out; mp_vfs_mount_t *vfs = lookup_path(path_in, &path_out); - MP_STATE_VM(vfs_cur) = vfs; if (vfs == MP_VFS_ROOT) { // If we change to the root dir and a VFS is mounted at the root then // we must change that VFS's current dir to the root dir so that any @@ -334,9 +329,11 @@ mp_obj_t mp_vfs_chdir(mp_obj_t path_in) { break; } } + vfs = MP_VFS_ROOT; } else { mp_vfs_proxy_call(vfs, MP_QSTR_chdir, 1, &path_out); } + MP_STATE_VM(vfs_cur) = vfs; return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_1(mp_vfs_chdir_obj, mp_vfs_chdir); diff --git a/extmod/vfs_fat.c b/extmod/vfs_fat.c index e7af4057b7433..95b7ad9944116 100644 --- a/extmod/vfs_fat.c +++ b/extmod/vfs_fat.c @@ -311,7 +311,7 @@ STATIC mp_obj_t fat_vfs_stat(mp_obj_t vfs_in, mp_obj_t path_in) { } else { mode |= MP_S_IFREG; } - mp_int_t seconds = timeutils_seconds_since_2000( + mp_int_t seconds = timeutils_seconds_since_epoch( 1980 + ((fno.fdate >> 9) & 0x7f), (fno.fdate >> 5) & 0x0f, fno.fdate & 0x1f, @@ -326,9 +326,9 @@ STATIC mp_obj_t fat_vfs_stat(mp_obj_t vfs_in, mp_obj_t path_in) { t->items[4] = MP_OBJ_NEW_SMALL_INT(0); // st_uid t->items[5] = MP_OBJ_NEW_SMALL_INT(0); // st_gid t->items[6] = mp_obj_new_int_from_uint(fno.fsize); // st_size - t->items[7] = MP_OBJ_NEW_SMALL_INT(seconds); // st_atime - t->items[8] = MP_OBJ_NEW_SMALL_INT(seconds); // st_mtime - t->items[9] = MP_OBJ_NEW_SMALL_INT(seconds); // st_ctime + t->items[7] = mp_obj_new_int_from_uint(seconds); // st_atime + t->items[8] = mp_obj_new_int_from_uint(seconds); // st_mtime + t->items[9] = mp_obj_new_int_from_uint(seconds); // st_ctime return MP_OBJ_FROM_PTR(t); } diff --git a/extmod/vfs_fat_diskio.c b/extmod/vfs_fat_diskio.c index ae1bef57d5e07..1bcd471f2162e 100644 --- a/extmod/vfs_fat_diskio.c +++ b/extmod/vfs_fat_diskio.c @@ -52,7 +52,7 @@ STATIC fs_user_mount_t *disk_get_device(void *bdev) { /* Read Sector(s) */ /*-----------------------------------------------------------------------*/ -DRESULT disk_read ( +DRESULT disk_read( bdev_t pdrv, /* Physical drive nmuber (0..) */ BYTE *buff, /* Data buffer to store read data */ DWORD sector, /* Sector address (LBA) */ @@ -72,7 +72,7 @@ DRESULT disk_read ( /* Write Sector(s) */ /*-----------------------------------------------------------------------*/ -DRESULT disk_write ( +DRESULT disk_write( bdev_t pdrv, /* Physical drive nmuber (0..) */ const BYTE *buff, /* Data to be written */ DWORD sector, /* Sector address (LBA) */ @@ -98,7 +98,7 @@ DRESULT disk_write ( /* Miscellaneous Functions */ /*-----------------------------------------------------------------------*/ -DRESULT disk_ioctl ( +DRESULT disk_ioctl( bdev_t pdrv, /* Physical drive nmuber (0..) */ BYTE cmd, /* Control code */ void *buff /* Buffer to send/receive control data */ diff --git a/extmod/vfs_lfs.c b/extmod/vfs_lfs.c index 90a1996f9ce4a..9cf3eb11083cf 100644 --- a/extmod/vfs_lfs.c +++ b/extmod/vfs_lfs.c @@ -3,7 +3,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2019 Damien P. George + * Copyright (c) 2019-2020 Damien P. George * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -25,18 +25,21 @@ */ #include "py/runtime.h" +#include "py/mphal.h" +#include "lib/timeutils/timeutils.h" #include "extmod/vfs.h" #include "extmod/vfs_lfs.h" #if MICROPY_VFS && (MICROPY_VFS_LFS1 || MICROPY_VFS_LFS2) -enum { LFS_MAKE_ARG_bdev, LFS_MAKE_ARG_readsize, LFS_MAKE_ARG_progsize, LFS_MAKE_ARG_lookahead }; +enum { LFS_MAKE_ARG_bdev, LFS_MAKE_ARG_readsize, LFS_MAKE_ARG_progsize, LFS_MAKE_ARG_lookahead, LFS_MAKE_ARG_mtime }; static const mp_arg_t lfs_make_allowed_args[] = { { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, { MP_QSTR_readsize, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 32} }, { MP_QSTR_progsize, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 32} }, { MP_QSTR_lookahead, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 32} }, + { MP_QSTR_mtime, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = true} }, }; #if MICROPY_VFS_LFS1 @@ -98,9 +101,13 @@ mp_obj_t mp_vfs_lfs1_file_open(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t mode #define MP_TYPE_VFS_LFSx mp_type_vfs_lfs2 #define MP_TYPE_VFS_LFSx_(s) mp_type_vfs_lfs2##s +// Attribute ids for lfs2_attr.type. +#define LFS_ATTR_MTIME (1) // 64-bit little endian, nanoseconds since 1970/1/1 + typedef struct _mp_obj_vfs_lfs2_t { mp_obj_base_t base; mp_vfs_blockdev_t blockdev; + bool enable_mtime; vstr_t cur_dir; struct lfs2_config config; lfs2_t lfs; @@ -109,14 +116,26 @@ typedef struct _mp_obj_vfs_lfs2_t { typedef struct _mp_obj_vfs_lfs2_file_t { mp_obj_base_t base; mp_obj_vfs_lfs2_t *vfs; + uint8_t mtime[8]; lfs2_file_t file; struct lfs2_file_config cfg; + struct lfs2_attr attrs[1]; uint8_t file_buffer[0]; } mp_obj_vfs_lfs2_file_t; const char *mp_vfs_lfs2_make_path(mp_obj_vfs_lfs2_t *self, mp_obj_t path_in); mp_obj_t mp_vfs_lfs2_file_open(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t mode_in); +STATIC void lfs_get_mtime(uint8_t buf[8]) { + // On-disk storage of timestamps uses 1970 as the Epoch, so convert from host's Epoch. + uint64_t ns = timeutils_nanoseconds_since_epoch_to_nanoseconds_since_1970(mp_hal_time_ns()); + // Store "ns" to "buf" in little-endian format (essentially htole64). + for (size_t i = 0; i < 8; ++i) { + buf[i] = ns; + ns >>= 8; + } +} + #include "extmod/vfs_lfsx.c" #include "extmod/vfs_lfsx_file.c" diff --git a/extmod/vfs_lfsx.c b/extmod/vfs_lfsx.c index c6240967822aa..35d5f03c59a2c 100644 --- a/extmod/vfs_lfsx.c +++ b/extmod/vfs_lfsx.c @@ -3,7 +3,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2019 Damien P. George + * Copyright (c) 2019-2020 Damien P. George * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -34,6 +34,7 @@ #include "py/objstr.h" #include "py/mperrno.h" #include "extmod/vfs.h" +#include "lib/timeutils/timeutils.h" STATIC int MP_VFS_LFSx(dev_ioctl)(const struct LFSx_API (config) * c, int cmd, int arg, bool must_return_int) { mp_obj_t ret = mp_vfs_blockdev_ioctl(c->context, cmd, arg); @@ -120,6 +121,9 @@ STATIC mp_obj_t MP_VFS_LFSx(make_new)(const mp_obj_type_t * type, size_t n_args, self->base.type = type; vstr_init(&self->cur_dir, 16); vstr_add_byte(&self->cur_dir, '/'); + #if LFS_BUILD_VERSION == 2 + self->enable_mtime = args[LFS_MAKE_ARG_mtime].u_bool; + #endif MP_VFS_LFSx(init_config)(self, args[LFS_MAKE_ARG_bdev].u_obj, args[LFS_MAKE_ARG_readsize].u_int, args[LFS_MAKE_ARG_progsize].u_int, args[LFS_MAKE_ARG_lookahead].u_int); int ret = LFSx_API(mount)(&self->lfs, &self->config); @@ -352,6 +356,20 @@ STATIC mp_obj_t MP_VFS_LFSx(stat)(mp_obj_t self_in, mp_obj_t path_in) { mp_raise_OSError(-ret); } + mp_uint_t mtime = 0; + #if LFS_BUILD_VERSION == 2 + uint8_t mtime_buf[8]; + lfs2_ssize_t sz = lfs2_getattr(&self->lfs, path, LFS_ATTR_MTIME, &mtime_buf, sizeof(mtime_buf)); + if (sz == sizeof(mtime_buf)) { + uint64_t ns = 0; + for (size_t i = sizeof(mtime_buf); i > 0; --i) { + ns = ns << 8 | mtime_buf[i - 1]; + } + // On-disk storage of timestamps uses 1970 as the Epoch, so convert to host's Epoch. + mtime = timeutils_seconds_since_epoch_from_nanoseconds_since_1970(ns); + } + #endif + mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL)); t->items[0] = MP_OBJ_NEW_SMALL_INT(info.type == LFSx_MACRO(_TYPE_REG) ? MP_S_IFREG : MP_S_IFDIR); // st_mode t->items[1] = MP_OBJ_NEW_SMALL_INT(0); // st_ino @@ -360,9 +378,9 @@ STATIC mp_obj_t MP_VFS_LFSx(stat)(mp_obj_t self_in, mp_obj_t path_in) { t->items[4] = MP_OBJ_NEW_SMALL_INT(0); // st_uid t->items[5] = MP_OBJ_NEW_SMALL_INT(0); // st_gid t->items[6] = mp_obj_new_int_from_uint(info.size); // st_size - t->items[7] = MP_OBJ_NEW_SMALL_INT(0); // st_atime - t->items[8] = MP_OBJ_NEW_SMALL_INT(0); // st_mtime - t->items[9] = MP_OBJ_NEW_SMALL_INT(0); // st_ctime + t->items[7] = mp_obj_new_int_from_uint(mtime); // st_atime + t->items[8] = mp_obj_new_int_from_uint(mtime); // st_mtime + t->items[9] = mp_obj_new_int_from_uint(mtime); // st_ctime return MP_OBJ_FROM_PTR(t); } @@ -442,7 +460,7 @@ STATIC mp_import_stat_t MP_VFS_LFSx(import_stat)(void *self_in, const char *path MP_OBJ_VFS_LFSx *self = self_in; struct LFSx_API (info) info; mp_obj_str_t path_obj = { { &mp_type_str }, 0, 0, (const byte *)path }; - path = MP_VFS_LFSx(make_path)(self, &path_obj); + path = MP_VFS_LFSx(make_path)(self, MP_OBJ_FROM_PTR(&path_obj)); int ret = LFSx_API(stat)(&self->lfs, path, &info); if (ret == 0) { if (info.type == LFSx_MACRO(_TYPE_REG)) { diff --git a/extmod/vfs_lfsx_file.c b/extmod/vfs_lfsx_file.c index f74b41837d32d..bc1a37b90bdb8 100644 --- a/extmod/vfs_lfsx_file.c +++ b/extmod/vfs_lfsx_file.c @@ -3,7 +3,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2019 Damien P. George + * Copyright (c) 2019-2020 Damien P. George * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -101,6 +101,17 @@ mp_obj_t MP_VFS_LFSx(file_open)(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t mod #endif o->cfg.buffer = &o->file_buffer[0]; + #if LFS_BUILD_VERSION == 2 + if (self->enable_mtime) { + lfs_get_mtime(&o->mtime[0]); + o->attrs[0].type = LFS_ATTR_MTIME; + o->attrs[0].buffer = &o->mtime[0]; + o->attrs[0].size = sizeof(o->mtime); + o->cfg.attrs = &o->attrs[0]; + o->cfg.attr_count = MP_ARRAY_SIZE(o->attrs); + } + #endif + const char *path = MP_VFS_LFSx(make_path)(self, path_in); int ret = LFSx_API(file_opencfg)(&self->lfs, &o->file, path, flags, &o->cfg); if (ret < 0) { @@ -131,6 +142,11 @@ STATIC mp_uint_t MP_VFS_LFSx(file_read)(mp_obj_t self_in, void *buf, mp_uint_t s STATIC mp_uint_t MP_VFS_LFSx(file_write)(mp_obj_t self_in, const void *buf, mp_uint_t size, int *errcode) { MP_OBJ_VFS_LFSx_FILE *self = MP_OBJ_TO_PTR(self_in); MP_VFS_LFSx(check_open)(self); + #if LFS_BUILD_VERSION == 2 + if (self->vfs->enable_mtime) { + lfs_get_mtime(&self->mtime[0]); + } + #endif LFSx_API(ssize_t) sz = LFSx_API(file_write)(&self->vfs->lfs, &self->file, buf, size); if (sz < 0) { *errcode = -sz; diff --git a/extmod/vfs_posix.c b/extmod/vfs_posix.c index f14f56e81ec82..719afe28fe87f 100644 --- a/extmod/vfs_posix.c +++ b/extmod/vfs_posix.c @@ -295,15 +295,15 @@ STATIC mp_obj_t vfs_posix_stat(mp_obj_t self_in, mp_obj_t path_in) { MP_HAL_RETRY_SYSCALL(ret, stat(path, &sb), mp_raise_OSError(err)); mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL)); t->items[0] = MP_OBJ_NEW_SMALL_INT(sb.st_mode); - t->items[1] = MP_OBJ_NEW_SMALL_INT(sb.st_ino); - t->items[2] = MP_OBJ_NEW_SMALL_INT(sb.st_dev); - t->items[3] = MP_OBJ_NEW_SMALL_INT(sb.st_nlink); - t->items[4] = MP_OBJ_NEW_SMALL_INT(sb.st_uid); - t->items[5] = MP_OBJ_NEW_SMALL_INT(sb.st_gid); - t->items[6] = MP_OBJ_NEW_SMALL_INT(sb.st_size); - t->items[7] = MP_OBJ_NEW_SMALL_INT(sb.st_atime); - t->items[8] = MP_OBJ_NEW_SMALL_INT(sb.st_mtime); - t->items[9] = MP_OBJ_NEW_SMALL_INT(sb.st_ctime); + t->items[1] = mp_obj_new_int_from_uint(sb.st_ino); + t->items[2] = mp_obj_new_int_from_uint(sb.st_dev); + t->items[3] = mp_obj_new_int_from_uint(sb.st_nlink); + t->items[4] = mp_obj_new_int_from_uint(sb.st_uid); + t->items[5] = mp_obj_new_int_from_uint(sb.st_gid); + t->items[6] = mp_obj_new_int_from_uint(sb.st_size); + t->items[7] = mp_obj_new_int_from_uint(sb.st_atime); + t->items[8] = mp_obj_new_int_from_uint(sb.st_mtime); + t->items[9] = mp_obj_new_int_from_uint(sb.st_ctime); return MP_OBJ_FROM_PTR(t); } STATIC MP_DEFINE_CONST_FUN_OBJ_2(vfs_posix_stat_obj, vfs_posix_stat); diff --git a/extmod/vfs_reader.c b/extmod/vfs_reader.c index db13ce3c33141..d3904c5c50693 100644 --- a/extmod/vfs_reader.c +++ b/extmod/vfs_reader.c @@ -71,8 +71,11 @@ STATIC void mp_reader_vfs_close(void *data) { void mp_reader_new_file(mp_reader_t *reader, const char *filename) { mp_reader_vfs_t *rf = m_new_obj(mp_reader_vfs_t); - mp_obj_t arg = mp_obj_new_str(filename, strlen(filename)); - rf->file = mp_vfs_open(1, &arg, (mp_map_t *)&mp_const_empty_map); + mp_obj_t args[2] = { + mp_obj_new_str(filename, strlen(filename)), + MP_OBJ_NEW_QSTR(MP_QSTR_rb), + }; + rf->file = mp_vfs_open(MP_ARRAY_SIZE(args), &args[0], (mp_map_t *)&mp_const_empty_map); int errcode; rf->len = mp_stream_rw(rf->file, rf->buf, sizeof(rf->buf), &errcode, MP_STREAM_RW_READ | MP_STREAM_RW_ONCE); if (errcode != 0) { diff --git a/extmod/webrepl/manifest.py b/extmod/webrepl/manifest.py index 6ce7d85466c06..6eceb3eeb6c56 100644 --- a/extmod/webrepl/manifest.py +++ b/extmod/webrepl/manifest.py @@ -1 +1 @@ -freeze(".", ("webrepl.py", "webrepl_setup.py", "websocket_helper.py",)) +freeze(".", ("webrepl.py", "webrepl_setup.py", "websocket_helper.py")) diff --git a/extmod/webrepl/websocket_helper.py b/extmod/webrepl/websocket_helper.py index 5ca80534eb33f..3260acc52f377 100644 --- a/extmod/webrepl/websocket_helper.py +++ b/extmod/webrepl/websocket_helper.py @@ -1,4 +1,7 @@ -import sys +try: + import usys as sys +except ImportError: + import sys try: import ubinascii as binascii diff --git a/lib/libc/string0.c b/lib/libc/string0.c index 8c86bf65f7b40..19ad14d0f7a77 100644 --- a/lib/libc/string0.c +++ b/lib/libc/string0.c @@ -169,6 +169,25 @@ char *strcpy(char *dest, const char *src) { return dest; } +// Public Domain implementation of strncpy from: +// http://en.wikibooks.org/wiki/C_Programming/Strings#The_strncpy_function +char *strncpy(char *s1, const char *s2, size_t n) { + char *dst = s1; + const char *src = s2; + /* Copy bytes, one at a time. */ + while (n > 0) { + n--; + if ((*dst++ = *src++) == '\0') { + /* If we get here, we found a null character at the end + of s2, so use memset to put null bytes at the end of + s1. */ + memset(dst, '\0', n); + break; + } + } + return s1; + } + // needed because gcc optimises strcpy + strcat to this char *stpcpy(char *dest, const char *src) { while (*src) { diff --git a/lib/libm/ef_rem_pio2.c b/lib/libm/ef_rem_pio2.c index 8111ca197f0e4..fcdfb18287e67 100644 --- a/lib/libm/ef_rem_pio2.c +++ b/lib/libm/ef_rem_pio2.c @@ -35,9 +35,9 @@ * Table of constants for 2/pi, 396 Hex digits (476 decimal) of 2/pi */ #ifdef __STDC__ -static const __int32_t two_over_pi[] = { +static const __uint8_t two_over_pi[] = { #else -static __int32_t two_over_pi[] = { +static __uint8_t two_over_pi[] = { #endif 0xA2, 0xF9, 0x83, 0x6E, 0x4E, 0x44, 0x15, 0x29, 0xFC, 0x27, 0x57, 0xD1, 0xF5, 0x34, 0xDD, 0xC0, 0xDB, 0x62, diff --git a/lib/libm/fdlibm.h b/lib/libm/fdlibm.h index ace3b2da22f94..22cd8f16b4c12 100644 --- a/lib/libm/fdlibm.h +++ b/lib/libm/fdlibm.h @@ -188,7 +188,7 @@ extern float __ieee754_scalbf __P((float,float)); extern float __kernel_sinf __P((float,float,int)); extern float __kernel_cosf __P((float,float)); extern float __kernel_tanf __P((float,float,int)); -extern int __kernel_rem_pio2f __P((float*,float*,int,int,int,const __int32_t*)); +extern int __kernel_rem_pio2f __P((float*,float*,int,int,int,const __uint8_t*)); /* A union which permits us to convert between a float and a 32 bit int. */ diff --git a/lib/libm/kf_rem_pio2.c b/lib/libm/kf_rem_pio2.c index 6bd4d287e6905..43e4b4687f506 100644 --- a/lib/libm/kf_rem_pio2.c +++ b/lib/libm/kf_rem_pio2.c @@ -62,10 +62,10 @@ two8 = 2.5600000000e+02f, /* 0x43800000 */ twon8 = 3.9062500000e-03f; /* 0x3b800000 */ #ifdef __STDC__ - int __kernel_rem_pio2f(float *x, float *y, int e0, int nx, int prec, const __int32_t *ipio2) + int __kernel_rem_pio2f(float *x, float *y, int e0, int nx, int prec, const __uint8_t *ipio2) #else int __kernel_rem_pio2f(x,y,e0,nx,prec,ipio2) - float x[], y[]; int e0,nx,prec; __int32_t ipio2[]; + float x[], y[]; int e0,nx,prec; __uint8_t ipio2[]; #endif { __int32_t jz,jx,jv,jp,jk,carry,n,iq[20],i,j,k,m,q0,ih; diff --git a/lib/libm_dbl/round.c b/lib/libm_dbl/round.c new file mode 100644 index 0000000000000..130d58d2571e7 --- /dev/null +++ b/lib/libm_dbl/round.c @@ -0,0 +1,35 @@ +#include "libm.h" + +#if FLT_EVAL_METHOD==0 || FLT_EVAL_METHOD==1 +#define EPS DBL_EPSILON +#elif FLT_EVAL_METHOD==2 +#define EPS LDBL_EPSILON +#endif +static const double_t toint = 1/EPS; + +double round(double x) +{ + union {double f; uint64_t i;} u = {x}; + int e = u.i >> 52 & 0x7ff; + double_t y; + + if (e >= 0x3ff+52) + return x; + if (u.i >> 63) + x = -x; + if (e < 0x3ff-1) { + /* raise inexact if x!=0 */ + FORCE_EVAL(x + toint); + return 0*u.f; + } + y = x + toint - toint - x; + if (y > 0.5) + y = y + x - 1; + else if (y <= -0.5) + y = y + x + 1; + else + y = y + x; + if (u.i >> 63) + y = -y; + return y; +} diff --git a/lib/mbedtls_errors/README.md b/lib/mbedtls_errors/README.md new file mode 100644 index 0000000000000..0e13021eb1910 --- /dev/null +++ b/lib/mbedtls_errors/README.md @@ -0,0 +1,42 @@ +MBEDTLS Error Strings for MicroPython +===================================== + +This directory contains source code and tools to rework the Mbedtls error strings for +micropython to use less space. In short, instead of storing and printing something like +"SSL - Our own certificate(s) is/are too large to send in an SSL message" it prints +the name of the error #define, which would be "MBEDTLS_ERR_SSL_CERTIFICATE_TOO_LARGE" in +this case, and only stores `SSL_CERTIFICATE_TOO_LARGE` in flash. The exact Mbedtls error +defines are used because they're easy to search for to find more detailed information. + +Mbedtls defines a specific format for error value #defines and +includes a Perl script to gather all `MBEDTLS_ERR` defines from includes files together with +english error text. From that the Perl script generates `mbedtls_strerror()`. The files in this +directory modify this process to produce a more space efficient error lookup table with +shorter error strings. + +The files are as follows: +- `generate_errors.diff` - diff for original mbedtls perl script +- `error.fmt` - modified code template for MicroPython +- `mp_mbedtls_errors.c` - source file with `mbedtls_strerror` this is built using the include + files in `../mbedtls` +- `do-mp.sh` - shell script to produce `mp_mbedtls_errors.c` +- `tester.c` - simple C main to test `mp_mbedtls_errors.c` locally on a dev box +- `do-test.sh` - shell script to produce `mp_mbedtls_errors.c` and compile the `tester` app +- `do-esp32.sh` - shell script to produce `esp32_mbedtls_errors.c` -- see below + +In order not to store multiple copies of `mbedtls_errors.c` +([https://github.com/micropython/micropython/pull/5819#discussion_r445528006](see)) +it is assumed that all ports use the same version of mbedtls with the same error #defines. +This is true as of MP v1.13, and ESP-IDF versions 3.3.2 and 4.0.1. If anything changes in the +future the `do-esp32.sh` script can be used to generate an esp32-specific version. + +### How-to + +- To build MicroPython all that is needed is to include the `mp_mbedtls_errors.c` into the build + (the Makefiles do this automatically). Note that Perl is not needed for routine MicroPython + builds. +- When a new version of Mbedtls is pulled-in the `do-mp.sh` script should be run to + re-generate `mp_mbedtls_errors.c`. +- The `tester` app should be run if changes to the string handling in `error.fmt` are made: + it tests that there is not an off-by-one error in the string copying/appending, etc. +- To include `mbedtls_strerror` error strings define `MBEDTLS_ERROR_C` in the build. diff --git a/lib/mbedtls_errors/do-esp32.sh b/lib/mbedtls_errors/do-esp32.sh new file mode 100755 index 0000000000000..6fd4682415c3f --- /dev/null +++ b/lib/mbedtls_errors/do-esp32.sh @@ -0,0 +1,7 @@ +#! /bin/bash -e +# Generate esp32_mbedtls_errors.c for use in the Esp32 port, with the ESP-IDF version of mbedtls +# The IDF_PATH env var must be set to the top-level dir of ESPIDF +echo "IDF_PATH=$IDF_PATH" +MBEDTLS=$IDF_PATH/components/mbedtls/mbedtls +patch -o esp32_generate_errors.pl $MBEDTLS/scripts/generate_errors.pl +#endif + +#if defined(MBEDTLS_PLATFORM_C) +#include "mbedtls/platform.h" +#else +#define mbedtls_snprintf snprintf +#define mbedtls_time_t time_t +#endif + +#if defined(MBEDTLS_ERROR_C) + +#include + +HEADER_INCLUDED + +// Error code table type +struct ssl_errs { + int16_t errnum; + const char *errstr; +}; + +// Table of high level error codes +static const struct ssl_errs mbedtls_high_level_error_tab[] = { +// BEGIN generated code +HIGH_LEVEL_CODE_CHECKS +// END generated code +}; + +static const struct ssl_errs mbedtls_low_level_error_tab[] = { +// Low level error codes +// +// BEGIN generated code +LOW_LEVEL_CODE_CHECKS +// END generated code +}; + +static const char *mbedtls_err_prefix = "MBEDTLS_ERR_"; +#define MBEDTLS_ERR_PREFIX_LEN ( sizeof("MBEDTLS_ERR_")-1 ) + +// copy error text into buffer, ensure null termination, return strlen of result +static size_t mbedtls_err_to_str(int err, const struct ssl_errs tab[], int tab_len, char *buf, size_t buflen) { + if (buflen == 0) return 0; + + // prefix for all error names + strncpy(buf, mbedtls_err_prefix, buflen); + if (buflen <= MBEDTLS_ERR_PREFIX_LEN+1) { + buf[buflen-1] = 0; + return buflen-1; + } + + // append error name from table + for (int i = 0; i < tab_len; i++) { + if (tab[i].errnum == err) { + strncpy(buf+MBEDTLS_ERR_PREFIX_LEN, tab[i].errstr, buflen-MBEDTLS_ERR_PREFIX_LEN); + buf[buflen-1] = 0; + return strlen(buf); + } + } + + mbedtls_snprintf(buf+MBEDTLS_ERR_PREFIX_LEN, buflen-MBEDTLS_ERR_PREFIX_LEN, "UNKNOWN (0x%04X)", + err); + return strlen(buf); +} + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) + +void mbedtls_strerror(int ret, char *buf, size_t buflen) { + int use_ret; + + if (buflen == 0) return; + + buf[buflen-1] = 0; + + if (ret < 0) ret = -ret; + + // + // High-level error codes + // + uint8_t got_hl = (ret & 0xFF80) != 0; + if (got_hl) { + use_ret = ret & 0xFF80; + + // special case +#if defined(MBEDTLS_SSL_TLS_C) + if (use_ret == -(MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE)) { + strncpy(buf, "MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE", buflen); + buf[buflen-1] = 0; + return; + } +#endif + + size_t len = mbedtls_err_to_str(use_ret, mbedtls_high_level_error_tab, + ARRAY_SIZE(mbedtls_high_level_error_tab), buf, buflen); + + buf += len; + buflen -= len; + if (buflen == 0) return; + } + + // + // Low-level error codes + // + use_ret = ret & ~0xFF80; + + if (use_ret == 0) return; + + // If high level code is present, make a concatenation between both error strings. + if (got_hl) { + if (buflen < 2) return; + *buf++ = '+'; + buflen--; + } + + mbedtls_err_to_str(use_ret, mbedtls_low_level_error_tab, + ARRAY_SIZE(mbedtls_low_level_error_tab), buf, buflen); +} + +#else /* MBEDTLS_ERROR_C */ + +#if defined(MBEDTLS_ERROR_STRERROR_DUMMY) + +/* + * Provide an non-function in case MBEDTLS_ERROR_C is not defined + */ +void mbedtls_strerror( int ret, char *buf, size_t buflen ) +{ + ((void) ret); + + if( buflen > 0 ) + buf[0] = '\0'; +} + +#endif /* MBEDTLS_ERROR_STRERROR_DUMMY */ + +#endif /* MBEDTLS_ERROR_C */ diff --git a/lib/mbedtls_errors/generate_errors.diff b/lib/mbedtls_errors/generate_errors.diff new file mode 100644 index 0000000000000..ad24c372faed7 --- /dev/null +++ b/lib/mbedtls_errors/generate_errors.diff @@ -0,0 +1,22 @@ +--- generate_errors_orig.pl 2020-06-20 08:40:38.819060379 -0700 ++++ generate_errors.pl 2020-06-20 08:47:26.511163591 -0700 +@@ -162,16 +162,12 @@ + + if ($error_name eq "MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE") + { +- ${$code_check} .= "${white_space}if( use_ret == -($error_name) )\n". +- "${white_space}\{\n". +- "${white_space} mbedtls_snprintf( buf, buflen, \"$module_name - $description\" );\n". +- "${white_space} return;\n". +- "${white_space}}\n" ++ # no-op, this case is hard-coded in error.fmt + } + else + { +- ${$code_check} .= "${white_space}if( use_ret == -($error_name) )\n". +- "${white_space} mbedtls_snprintf( buf, buflen, \"$module_name - $description\" );\n" ++ my $error_text = $error_name =~ s/^MBEDTLS_ERR_//r; ++ ${$code_check} .= "${white_space}{ -($error_name), \"$error_text\" },\n" + } + }; + diff --git a/lib/mbedtls_errors/mp_mbedtls_errors.c b/lib/mbedtls_errors/mp_mbedtls_errors.c new file mode 100644 index 0000000000000..03a91f0dc9495 --- /dev/null +++ b/lib/mbedtls_errors/mp_mbedtls_errors.c @@ -0,0 +1,705 @@ +/* + * Error message information + * + * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is part of mbed TLS (https://tls.mbed.org) + */ + +#if !defined(MBEDTLS_CONFIG_FILE) +#include "mbedtls/config.h" +#else +#include MBEDTLS_CONFIG_FILE +#endif + +#if defined(MBEDTLS_ERROR_C) || defined(MBEDTLS_ERROR_STRERROR_DUMMY) +#include "mbedtls/error.h" +#include +#endif + +#if defined(MBEDTLS_PLATFORM_C) +#include "mbedtls/platform.h" +#else +#define mbedtls_snprintf snprintf +#define mbedtls_time_t time_t +#endif + +#if defined(MBEDTLS_ERROR_C) + +#include + +#if defined(MBEDTLS_AES_C) +#include "mbedtls/aes.h" +#endif + +#if defined(MBEDTLS_ARC4_C) +#include "mbedtls/arc4.h" +#endif + +#if defined(MBEDTLS_ARIA_C) +#include "mbedtls/aria.h" +#endif + +#if defined(MBEDTLS_BASE64_C) +#include "mbedtls/base64.h" +#endif + +#if defined(MBEDTLS_BIGNUM_C) +#include "mbedtls/bignum.h" +#endif + +#if defined(MBEDTLS_BLOWFISH_C) +#include "mbedtls/blowfish.h" +#endif + +#if defined(MBEDTLS_CAMELLIA_C) +#include "mbedtls/camellia.h" +#endif + +#if defined(MBEDTLS_CCM_C) +#include "mbedtls/ccm.h" +#endif + +#if defined(MBEDTLS_CHACHA20_C) +#include "mbedtls/chacha20.h" +#endif + +#if defined(MBEDTLS_CHACHAPOLY_C) +#include "mbedtls/chachapoly.h" +#endif + +#if defined(MBEDTLS_CIPHER_C) +#include "mbedtls/cipher.h" +#endif + +#if defined(MBEDTLS_CMAC_C) +#include "mbedtls/cmac.h" +#endif + +#if defined(MBEDTLS_CTR_DRBG_C) +#include "mbedtls/ctr_drbg.h" +#endif + +#if defined(MBEDTLS_DES_C) +#include "mbedtls/des.h" +#endif + +#if defined(MBEDTLS_DHM_C) +#include "mbedtls/dhm.h" +#endif + +#if defined(MBEDTLS_ECP_C) +#include "mbedtls/ecp.h" +#endif + +#if defined(MBEDTLS_ENTROPY_C) +#include "mbedtls/entropy.h" +#endif + +#if defined(MBEDTLS_GCM_C) +#include "mbedtls/gcm.h" +#endif + +#if defined(MBEDTLS_HKDF_C) +#include "mbedtls/hkdf.h" +#endif + +#if defined(MBEDTLS_HMAC_DRBG_C) +#include "mbedtls/hmac_drbg.h" +#endif + +#if defined(MBEDTLS_MD_C) +#include "mbedtls/md.h" +#endif + +#if defined(MBEDTLS_MD2_C) +#include "mbedtls/md2.h" +#endif + +#if defined(MBEDTLS_MD4_C) +#include "mbedtls/md4.h" +#endif + +#if defined(MBEDTLS_MD5_C) +#include "mbedtls/md5.h" +#endif + +#if defined(MBEDTLS_NET_C) +#include "mbedtls/net_sockets.h" +#endif + +#if defined(MBEDTLS_OID_C) +#include "mbedtls/oid.h" +#endif + +#if defined(MBEDTLS_PADLOCK_C) +#include "mbedtls/padlock.h" +#endif + +#if defined(MBEDTLS_PEM_PARSE_C) || defined(MBEDTLS_PEM_WRITE_C) +#include "mbedtls/pem.h" +#endif + +#if defined(MBEDTLS_PK_C) +#include "mbedtls/pk.h" +#endif + +#if defined(MBEDTLS_PKCS12_C) +#include "mbedtls/pkcs12.h" +#endif + +#if defined(MBEDTLS_PKCS5_C) +#include "mbedtls/pkcs5.h" +#endif + +#if defined(MBEDTLS_PLATFORM_C) +#include "mbedtls/platform.h" +#endif + +#if defined(MBEDTLS_POLY1305_C) +#include "mbedtls/poly1305.h" +#endif + +#if defined(MBEDTLS_RIPEMD160_C) +#include "mbedtls/ripemd160.h" +#endif + +#if defined(MBEDTLS_RSA_C) +#include "mbedtls/rsa.h" +#endif + +#if defined(MBEDTLS_SHA1_C) +#include "mbedtls/sha1.h" +#endif + +#if defined(MBEDTLS_SHA256_C) +#include "mbedtls/sha256.h" +#endif + +#if defined(MBEDTLS_SHA512_C) +#include "mbedtls/sha512.h" +#endif + +#if defined(MBEDTLS_SSL_TLS_C) +#include "mbedtls/ssl.h" +#endif + +#if defined(MBEDTLS_THREADING_C) +#include "mbedtls/threading.h" +#endif + +#if defined(MBEDTLS_X509_USE_C) || defined(MBEDTLS_X509_CREATE_C) +#include "mbedtls/x509.h" +#endif + +#if defined(MBEDTLS_XTEA_C) +#include "mbedtls/xtea.h" +#endif + + +// Error code table type +struct ssl_errs { + int16_t errnum; + const char *errstr; +}; + +// Table of high level error codes +static const struct ssl_errs mbedtls_high_level_error_tab[] = { +// BEGIN generated code +#if defined(MBEDTLS_CIPHER_C) + { -(MBEDTLS_ERR_CIPHER_FEATURE_UNAVAILABLE), "CIPHER_FEATURE_UNAVAILABLE" }, + { -(MBEDTLS_ERR_CIPHER_BAD_INPUT_DATA), "CIPHER_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_CIPHER_ALLOC_FAILED), "CIPHER_ALLOC_FAILED" }, + { -(MBEDTLS_ERR_CIPHER_INVALID_PADDING), "CIPHER_INVALID_PADDING" }, + { -(MBEDTLS_ERR_CIPHER_FULL_BLOCK_EXPECTED), "CIPHER_FULL_BLOCK_EXPECTED" }, + { -(MBEDTLS_ERR_CIPHER_AUTH_FAILED), "CIPHER_AUTH_FAILED" }, + { -(MBEDTLS_ERR_CIPHER_INVALID_CONTEXT), "CIPHER_INVALID_CONTEXT" }, + { -(MBEDTLS_ERR_CIPHER_HW_ACCEL_FAILED), "CIPHER_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_CIPHER_C */ + +#if defined(MBEDTLS_DHM_C) + { -(MBEDTLS_ERR_DHM_BAD_INPUT_DATA), "DHM_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_DHM_READ_PARAMS_FAILED), "DHM_READ_PARAMS_FAILED" }, + { -(MBEDTLS_ERR_DHM_MAKE_PARAMS_FAILED), "DHM_MAKE_PARAMS_FAILED" }, + { -(MBEDTLS_ERR_DHM_READ_PUBLIC_FAILED), "DHM_READ_PUBLIC_FAILED" }, + { -(MBEDTLS_ERR_DHM_MAKE_PUBLIC_FAILED), "DHM_MAKE_PUBLIC_FAILED" }, + { -(MBEDTLS_ERR_DHM_CALC_SECRET_FAILED), "DHM_CALC_SECRET_FAILED" }, + { -(MBEDTLS_ERR_DHM_INVALID_FORMAT), "DHM_INVALID_FORMAT" }, + { -(MBEDTLS_ERR_DHM_ALLOC_FAILED), "DHM_ALLOC_FAILED" }, + { -(MBEDTLS_ERR_DHM_FILE_IO_ERROR), "DHM_FILE_IO_ERROR" }, + { -(MBEDTLS_ERR_DHM_HW_ACCEL_FAILED), "DHM_HW_ACCEL_FAILED" }, + { -(MBEDTLS_ERR_DHM_SET_GROUP_FAILED), "DHM_SET_GROUP_FAILED" }, +#endif /* MBEDTLS_DHM_C */ + +#if defined(MBEDTLS_ECP_C) + { -(MBEDTLS_ERR_ECP_BAD_INPUT_DATA), "ECP_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_ECP_BUFFER_TOO_SMALL), "ECP_BUFFER_TOO_SMALL" }, + { -(MBEDTLS_ERR_ECP_FEATURE_UNAVAILABLE), "ECP_FEATURE_UNAVAILABLE" }, + { -(MBEDTLS_ERR_ECP_VERIFY_FAILED), "ECP_VERIFY_FAILED" }, + { -(MBEDTLS_ERR_ECP_ALLOC_FAILED), "ECP_ALLOC_FAILED" }, + { -(MBEDTLS_ERR_ECP_RANDOM_FAILED), "ECP_RANDOM_FAILED" }, + { -(MBEDTLS_ERR_ECP_INVALID_KEY), "ECP_INVALID_KEY" }, + { -(MBEDTLS_ERR_ECP_SIG_LEN_MISMATCH), "ECP_SIG_LEN_MISMATCH" }, + { -(MBEDTLS_ERR_ECP_HW_ACCEL_FAILED), "ECP_HW_ACCEL_FAILED" }, + { -(MBEDTLS_ERR_ECP_IN_PROGRESS), "ECP_IN_PROGRESS" }, +#endif /* MBEDTLS_ECP_C */ + +#if defined(MBEDTLS_MD_C) + { -(MBEDTLS_ERR_MD_FEATURE_UNAVAILABLE), "MD_FEATURE_UNAVAILABLE" }, + { -(MBEDTLS_ERR_MD_BAD_INPUT_DATA), "MD_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_MD_ALLOC_FAILED), "MD_ALLOC_FAILED" }, + { -(MBEDTLS_ERR_MD_FILE_IO_ERROR), "MD_FILE_IO_ERROR" }, + { -(MBEDTLS_ERR_MD_HW_ACCEL_FAILED), "MD_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_MD_C */ + +#if defined(MBEDTLS_PEM_PARSE_C) || defined(MBEDTLS_PEM_WRITE_C) + { -(MBEDTLS_ERR_PEM_NO_HEADER_FOOTER_PRESENT), "PEM_NO_HEADER_FOOTER_PRESENT" }, + { -(MBEDTLS_ERR_PEM_INVALID_DATA), "PEM_INVALID_DATA" }, + { -(MBEDTLS_ERR_PEM_ALLOC_FAILED), "PEM_ALLOC_FAILED" }, + { -(MBEDTLS_ERR_PEM_INVALID_ENC_IV), "PEM_INVALID_ENC_IV" }, + { -(MBEDTLS_ERR_PEM_UNKNOWN_ENC_ALG), "PEM_UNKNOWN_ENC_ALG" }, + { -(MBEDTLS_ERR_PEM_PASSWORD_REQUIRED), "PEM_PASSWORD_REQUIRED" }, + { -(MBEDTLS_ERR_PEM_PASSWORD_MISMATCH), "PEM_PASSWORD_MISMATCH" }, + { -(MBEDTLS_ERR_PEM_FEATURE_UNAVAILABLE), "PEM_FEATURE_UNAVAILABLE" }, + { -(MBEDTLS_ERR_PEM_BAD_INPUT_DATA), "PEM_BAD_INPUT_DATA" }, +#endif /* MBEDTLS_PEM_PARSE_C || MBEDTLS_PEM_WRITE_C */ + +#if defined(MBEDTLS_PK_C) + { -(MBEDTLS_ERR_PK_ALLOC_FAILED), "PK_ALLOC_FAILED" }, + { -(MBEDTLS_ERR_PK_TYPE_MISMATCH), "PK_TYPE_MISMATCH" }, + { -(MBEDTLS_ERR_PK_BAD_INPUT_DATA), "PK_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_PK_FILE_IO_ERROR), "PK_FILE_IO_ERROR" }, + { -(MBEDTLS_ERR_PK_KEY_INVALID_VERSION), "PK_KEY_INVALID_VERSION" }, + { -(MBEDTLS_ERR_PK_KEY_INVALID_FORMAT), "PK_KEY_INVALID_FORMAT" }, + { -(MBEDTLS_ERR_PK_UNKNOWN_PK_ALG), "PK_UNKNOWN_PK_ALG" }, + { -(MBEDTLS_ERR_PK_PASSWORD_REQUIRED), "PK_PASSWORD_REQUIRED" }, + { -(MBEDTLS_ERR_PK_PASSWORD_MISMATCH), "PK_PASSWORD_MISMATCH" }, + { -(MBEDTLS_ERR_PK_INVALID_PUBKEY), "PK_INVALID_PUBKEY" }, + { -(MBEDTLS_ERR_PK_INVALID_ALG), "PK_INVALID_ALG" }, + { -(MBEDTLS_ERR_PK_UNKNOWN_NAMED_CURVE), "PK_UNKNOWN_NAMED_CURVE" }, + { -(MBEDTLS_ERR_PK_FEATURE_UNAVAILABLE), "PK_FEATURE_UNAVAILABLE" }, + { -(MBEDTLS_ERR_PK_SIG_LEN_MISMATCH), "PK_SIG_LEN_MISMATCH" }, + { -(MBEDTLS_ERR_PK_HW_ACCEL_FAILED), "PK_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_PK_C */ + +#if defined(MBEDTLS_PKCS12_C) + { -(MBEDTLS_ERR_PKCS12_BAD_INPUT_DATA), "PKCS12_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_PKCS12_FEATURE_UNAVAILABLE), "PKCS12_FEATURE_UNAVAILABLE" }, + { -(MBEDTLS_ERR_PKCS12_PBE_INVALID_FORMAT), "PKCS12_PBE_INVALID_FORMAT" }, + { -(MBEDTLS_ERR_PKCS12_PASSWORD_MISMATCH), "PKCS12_PASSWORD_MISMATCH" }, +#endif /* MBEDTLS_PKCS12_C */ + +#if defined(MBEDTLS_PKCS5_C) + { -(MBEDTLS_ERR_PKCS5_BAD_INPUT_DATA), "PKCS5_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_PKCS5_INVALID_FORMAT), "PKCS5_INVALID_FORMAT" }, + { -(MBEDTLS_ERR_PKCS5_FEATURE_UNAVAILABLE), "PKCS5_FEATURE_UNAVAILABLE" }, + { -(MBEDTLS_ERR_PKCS5_PASSWORD_MISMATCH), "PKCS5_PASSWORD_MISMATCH" }, +#endif /* MBEDTLS_PKCS5_C */ + +#if defined(MBEDTLS_RSA_C) + { -(MBEDTLS_ERR_RSA_BAD_INPUT_DATA), "RSA_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_RSA_INVALID_PADDING), "RSA_INVALID_PADDING" }, + { -(MBEDTLS_ERR_RSA_KEY_GEN_FAILED), "RSA_KEY_GEN_FAILED" }, + { -(MBEDTLS_ERR_RSA_KEY_CHECK_FAILED), "RSA_KEY_CHECK_FAILED" }, + { -(MBEDTLS_ERR_RSA_PUBLIC_FAILED), "RSA_PUBLIC_FAILED" }, + { -(MBEDTLS_ERR_RSA_PRIVATE_FAILED), "RSA_PRIVATE_FAILED" }, + { -(MBEDTLS_ERR_RSA_VERIFY_FAILED), "RSA_VERIFY_FAILED" }, + { -(MBEDTLS_ERR_RSA_OUTPUT_TOO_LARGE), "RSA_OUTPUT_TOO_LARGE" }, + { -(MBEDTLS_ERR_RSA_RNG_FAILED), "RSA_RNG_FAILED" }, + { -(MBEDTLS_ERR_RSA_UNSUPPORTED_OPERATION), "RSA_UNSUPPORTED_OPERATION" }, + { -(MBEDTLS_ERR_RSA_HW_ACCEL_FAILED), "RSA_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_RSA_C */ + +#if defined(MBEDTLS_SSL_TLS_C) + { -(MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE), "SSL_FEATURE_UNAVAILABLE" }, + { -(MBEDTLS_ERR_SSL_BAD_INPUT_DATA), "SSL_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_SSL_INVALID_MAC), "SSL_INVALID_MAC" }, + { -(MBEDTLS_ERR_SSL_INVALID_RECORD), "SSL_INVALID_RECORD" }, + { -(MBEDTLS_ERR_SSL_CONN_EOF), "SSL_CONN_EOF" }, + { -(MBEDTLS_ERR_SSL_UNKNOWN_CIPHER), "SSL_UNKNOWN_CIPHER" }, + { -(MBEDTLS_ERR_SSL_NO_CIPHER_CHOSEN), "SSL_NO_CIPHER_CHOSEN" }, + { -(MBEDTLS_ERR_SSL_NO_RNG), "SSL_NO_RNG" }, + { -(MBEDTLS_ERR_SSL_NO_CLIENT_CERTIFICATE), "SSL_NO_CLIENT_CERTIFICATE" }, + { -(MBEDTLS_ERR_SSL_CERTIFICATE_TOO_LARGE), "SSL_CERTIFICATE_TOO_LARGE" }, + { -(MBEDTLS_ERR_SSL_CERTIFICATE_REQUIRED), "SSL_CERTIFICATE_REQUIRED" }, + { -(MBEDTLS_ERR_SSL_PRIVATE_KEY_REQUIRED), "SSL_PRIVATE_KEY_REQUIRED" }, + { -(MBEDTLS_ERR_SSL_CA_CHAIN_REQUIRED), "SSL_CA_CHAIN_REQUIRED" }, + { -(MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE), "SSL_UNEXPECTED_MESSAGE" }, + { -(MBEDTLS_ERR_SSL_PEER_VERIFY_FAILED), "SSL_PEER_VERIFY_FAILED" }, + { -(MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY), "SSL_PEER_CLOSE_NOTIFY" }, + { -(MBEDTLS_ERR_SSL_BAD_HS_CLIENT_HELLO), "SSL_BAD_HS_CLIENT_HELLO" }, + { -(MBEDTLS_ERR_SSL_BAD_HS_SERVER_HELLO), "SSL_BAD_HS_SERVER_HELLO" }, + { -(MBEDTLS_ERR_SSL_BAD_HS_CERTIFICATE), "SSL_BAD_HS_CERTIFICATE" }, + { -(MBEDTLS_ERR_SSL_BAD_HS_CERTIFICATE_REQUEST), "SSL_BAD_HS_CERTIFICATE_REQUEST" }, + { -(MBEDTLS_ERR_SSL_BAD_HS_SERVER_KEY_EXCHANGE), "SSL_BAD_HS_SERVER_KEY_EXCHANGE" }, + { -(MBEDTLS_ERR_SSL_BAD_HS_SERVER_HELLO_DONE), "SSL_BAD_HS_SERVER_HELLO_DONE" }, + { -(MBEDTLS_ERR_SSL_BAD_HS_CLIENT_KEY_EXCHANGE), "SSL_BAD_HS_CLIENT_KEY_EXCHANGE" }, + { -(MBEDTLS_ERR_SSL_BAD_HS_CLIENT_KEY_EXCHANGE_RP), "SSL_BAD_HS_CLIENT_KEY_EXCHANGE_RP" }, + { -(MBEDTLS_ERR_SSL_BAD_HS_CLIENT_KEY_EXCHANGE_CS), "SSL_BAD_HS_CLIENT_KEY_EXCHANGE_CS" }, + { -(MBEDTLS_ERR_SSL_BAD_HS_CERTIFICATE_VERIFY), "SSL_BAD_HS_CERTIFICATE_VERIFY" }, + { -(MBEDTLS_ERR_SSL_BAD_HS_CHANGE_CIPHER_SPEC), "SSL_BAD_HS_CHANGE_CIPHER_SPEC" }, + { -(MBEDTLS_ERR_SSL_BAD_HS_FINISHED), "SSL_BAD_HS_FINISHED" }, + { -(MBEDTLS_ERR_SSL_ALLOC_FAILED), "SSL_ALLOC_FAILED" }, + { -(MBEDTLS_ERR_SSL_HW_ACCEL_FAILED), "SSL_HW_ACCEL_FAILED" }, + { -(MBEDTLS_ERR_SSL_HW_ACCEL_FALLTHROUGH), "SSL_HW_ACCEL_FALLTHROUGH" }, + { -(MBEDTLS_ERR_SSL_COMPRESSION_FAILED), "SSL_COMPRESSION_FAILED" }, + { -(MBEDTLS_ERR_SSL_BAD_HS_PROTOCOL_VERSION), "SSL_BAD_HS_PROTOCOL_VERSION" }, + { -(MBEDTLS_ERR_SSL_BAD_HS_NEW_SESSION_TICKET), "SSL_BAD_HS_NEW_SESSION_TICKET" }, + { -(MBEDTLS_ERR_SSL_SESSION_TICKET_EXPIRED), "SSL_SESSION_TICKET_EXPIRED" }, + { -(MBEDTLS_ERR_SSL_PK_TYPE_MISMATCH), "SSL_PK_TYPE_MISMATCH" }, + { -(MBEDTLS_ERR_SSL_UNKNOWN_IDENTITY), "SSL_UNKNOWN_IDENTITY" }, + { -(MBEDTLS_ERR_SSL_INTERNAL_ERROR), "SSL_INTERNAL_ERROR" }, + { -(MBEDTLS_ERR_SSL_COUNTER_WRAPPING), "SSL_COUNTER_WRAPPING" }, + { -(MBEDTLS_ERR_SSL_WAITING_SERVER_HELLO_RENEGO), "SSL_WAITING_SERVER_HELLO_RENEGO" }, + { -(MBEDTLS_ERR_SSL_HELLO_VERIFY_REQUIRED), "SSL_HELLO_VERIFY_REQUIRED" }, + { -(MBEDTLS_ERR_SSL_BUFFER_TOO_SMALL), "SSL_BUFFER_TOO_SMALL" }, + { -(MBEDTLS_ERR_SSL_NO_USABLE_CIPHERSUITE), "SSL_NO_USABLE_CIPHERSUITE" }, + { -(MBEDTLS_ERR_SSL_WANT_READ), "SSL_WANT_READ" }, + { -(MBEDTLS_ERR_SSL_WANT_WRITE), "SSL_WANT_WRITE" }, + { -(MBEDTLS_ERR_SSL_TIMEOUT), "SSL_TIMEOUT" }, + { -(MBEDTLS_ERR_SSL_CLIENT_RECONNECT), "SSL_CLIENT_RECONNECT" }, + { -(MBEDTLS_ERR_SSL_UNEXPECTED_RECORD), "SSL_UNEXPECTED_RECORD" }, + { -(MBEDTLS_ERR_SSL_NON_FATAL), "SSL_NON_FATAL" }, + { -(MBEDTLS_ERR_SSL_INVALID_VERIFY_HASH), "SSL_INVALID_VERIFY_HASH" }, + { -(MBEDTLS_ERR_SSL_CONTINUE_PROCESSING), "SSL_CONTINUE_PROCESSING" }, + { -(MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS), "SSL_ASYNC_IN_PROGRESS" }, + { -(MBEDTLS_ERR_SSL_EARLY_MESSAGE), "SSL_EARLY_MESSAGE" }, + { -(MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS), "SSL_CRYPTO_IN_PROGRESS" }, +#endif /* MBEDTLS_SSL_TLS_C */ + +#if defined(MBEDTLS_X509_USE_C) || defined(MBEDTLS_X509_CREATE_C) + { -(MBEDTLS_ERR_X509_FEATURE_UNAVAILABLE), "X509_FEATURE_UNAVAILABLE" }, + { -(MBEDTLS_ERR_X509_UNKNOWN_OID), "X509_UNKNOWN_OID" }, + { -(MBEDTLS_ERR_X509_INVALID_FORMAT), "X509_INVALID_FORMAT" }, + { -(MBEDTLS_ERR_X509_INVALID_VERSION), "X509_INVALID_VERSION" }, + { -(MBEDTLS_ERR_X509_INVALID_SERIAL), "X509_INVALID_SERIAL" }, + { -(MBEDTLS_ERR_X509_INVALID_ALG), "X509_INVALID_ALG" }, + { -(MBEDTLS_ERR_X509_INVALID_NAME), "X509_INVALID_NAME" }, + { -(MBEDTLS_ERR_X509_INVALID_DATE), "X509_INVALID_DATE" }, + { -(MBEDTLS_ERR_X509_INVALID_SIGNATURE), "X509_INVALID_SIGNATURE" }, + { -(MBEDTLS_ERR_X509_INVALID_EXTENSIONS), "X509_INVALID_EXTENSIONS" }, + { -(MBEDTLS_ERR_X509_UNKNOWN_VERSION), "X509_UNKNOWN_VERSION" }, + { -(MBEDTLS_ERR_X509_UNKNOWN_SIG_ALG), "X509_UNKNOWN_SIG_ALG" }, + { -(MBEDTLS_ERR_X509_SIG_MISMATCH), "X509_SIG_MISMATCH" }, + { -(MBEDTLS_ERR_X509_CERT_VERIFY_FAILED), "X509_CERT_VERIFY_FAILED" }, + { -(MBEDTLS_ERR_X509_CERT_UNKNOWN_FORMAT), "X509_CERT_UNKNOWN_FORMAT" }, + { -(MBEDTLS_ERR_X509_BAD_INPUT_DATA), "X509_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_X509_ALLOC_FAILED), "X509_ALLOC_FAILED" }, + { -(MBEDTLS_ERR_X509_FILE_IO_ERROR), "X509_FILE_IO_ERROR" }, + { -(MBEDTLS_ERR_X509_BUFFER_TOO_SMALL), "X509_BUFFER_TOO_SMALL" }, + { -(MBEDTLS_ERR_X509_FATAL_ERROR), "X509_FATAL_ERROR" }, +#endif /* MBEDTLS_X509_USE_C || MBEDTLS_X509_CREATE_C */ +// END generated code +}; + +static const struct ssl_errs mbedtls_low_level_error_tab[] = { +// Low level error codes +// +// BEGIN generated code +#if defined(MBEDTLS_AES_C) + { -(MBEDTLS_ERR_AES_INVALID_KEY_LENGTH), "AES_INVALID_KEY_LENGTH" }, + { -(MBEDTLS_ERR_AES_INVALID_INPUT_LENGTH), "AES_INVALID_INPUT_LENGTH" }, + { -(MBEDTLS_ERR_AES_BAD_INPUT_DATA), "AES_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_AES_FEATURE_UNAVAILABLE), "AES_FEATURE_UNAVAILABLE" }, + { -(MBEDTLS_ERR_AES_HW_ACCEL_FAILED), "AES_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_AES_C */ + +#if defined(MBEDTLS_ARC4_C) + { -(MBEDTLS_ERR_ARC4_HW_ACCEL_FAILED), "ARC4_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_ARC4_C */ + +#if defined(MBEDTLS_ARIA_C) + { -(MBEDTLS_ERR_ARIA_BAD_INPUT_DATA), "ARIA_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_ARIA_INVALID_INPUT_LENGTH), "ARIA_INVALID_INPUT_LENGTH" }, + { -(MBEDTLS_ERR_ARIA_FEATURE_UNAVAILABLE), "ARIA_FEATURE_UNAVAILABLE" }, + { -(MBEDTLS_ERR_ARIA_HW_ACCEL_FAILED), "ARIA_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_ARIA_C */ + +#if defined(MBEDTLS_ASN1_PARSE_C) + { -(MBEDTLS_ERR_ASN1_OUT_OF_DATA), "ASN1_OUT_OF_DATA" }, + { -(MBEDTLS_ERR_ASN1_UNEXPECTED_TAG), "ASN1_UNEXPECTED_TAG" }, + { -(MBEDTLS_ERR_ASN1_INVALID_LENGTH), "ASN1_INVALID_LENGTH" }, + { -(MBEDTLS_ERR_ASN1_LENGTH_MISMATCH), "ASN1_LENGTH_MISMATCH" }, + { -(MBEDTLS_ERR_ASN1_INVALID_DATA), "ASN1_INVALID_DATA" }, + { -(MBEDTLS_ERR_ASN1_ALLOC_FAILED), "ASN1_ALLOC_FAILED" }, + { -(MBEDTLS_ERR_ASN1_BUF_TOO_SMALL), "ASN1_BUF_TOO_SMALL" }, +#endif /* MBEDTLS_ASN1_PARSE_C */ + +#if defined(MBEDTLS_BASE64_C) + { -(MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL), "BASE64_BUFFER_TOO_SMALL" }, + { -(MBEDTLS_ERR_BASE64_INVALID_CHARACTER), "BASE64_INVALID_CHARACTER" }, +#endif /* MBEDTLS_BASE64_C */ + +#if defined(MBEDTLS_BIGNUM_C) + { -(MBEDTLS_ERR_MPI_FILE_IO_ERROR), "MPI_FILE_IO_ERROR" }, + { -(MBEDTLS_ERR_MPI_BAD_INPUT_DATA), "MPI_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_MPI_INVALID_CHARACTER), "MPI_INVALID_CHARACTER" }, + { -(MBEDTLS_ERR_MPI_BUFFER_TOO_SMALL), "MPI_BUFFER_TOO_SMALL" }, + { -(MBEDTLS_ERR_MPI_NEGATIVE_VALUE), "MPI_NEGATIVE_VALUE" }, + { -(MBEDTLS_ERR_MPI_DIVISION_BY_ZERO), "MPI_DIVISION_BY_ZERO" }, + { -(MBEDTLS_ERR_MPI_NOT_ACCEPTABLE), "MPI_NOT_ACCEPTABLE" }, + { -(MBEDTLS_ERR_MPI_ALLOC_FAILED), "MPI_ALLOC_FAILED" }, +#endif /* MBEDTLS_BIGNUM_C */ + +#if defined(MBEDTLS_BLOWFISH_C) + { -(MBEDTLS_ERR_BLOWFISH_BAD_INPUT_DATA), "BLOWFISH_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_BLOWFISH_INVALID_INPUT_LENGTH), "BLOWFISH_INVALID_INPUT_LENGTH" }, + { -(MBEDTLS_ERR_BLOWFISH_HW_ACCEL_FAILED), "BLOWFISH_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_BLOWFISH_C */ + +#if defined(MBEDTLS_CAMELLIA_C) + { -(MBEDTLS_ERR_CAMELLIA_BAD_INPUT_DATA), "CAMELLIA_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_CAMELLIA_INVALID_INPUT_LENGTH), "CAMELLIA_INVALID_INPUT_LENGTH" }, + { -(MBEDTLS_ERR_CAMELLIA_HW_ACCEL_FAILED), "CAMELLIA_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_CAMELLIA_C */ + +#if defined(MBEDTLS_CCM_C) + { -(MBEDTLS_ERR_CCM_BAD_INPUT), "CCM_BAD_INPUT" }, + { -(MBEDTLS_ERR_CCM_AUTH_FAILED), "CCM_AUTH_FAILED" }, + { -(MBEDTLS_ERR_CCM_HW_ACCEL_FAILED), "CCM_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_CCM_C */ + +#if defined(MBEDTLS_CHACHA20_C) + { -(MBEDTLS_ERR_CHACHA20_BAD_INPUT_DATA), "CHACHA20_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_CHACHA20_FEATURE_UNAVAILABLE), "CHACHA20_FEATURE_UNAVAILABLE" }, + { -(MBEDTLS_ERR_CHACHA20_HW_ACCEL_FAILED), "CHACHA20_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_CHACHA20_C */ + +#if defined(MBEDTLS_CHACHAPOLY_C) + { -(MBEDTLS_ERR_CHACHAPOLY_BAD_STATE), "CHACHAPOLY_BAD_STATE" }, + { -(MBEDTLS_ERR_CHACHAPOLY_AUTH_FAILED), "CHACHAPOLY_AUTH_FAILED" }, +#endif /* MBEDTLS_CHACHAPOLY_C */ + +#if defined(MBEDTLS_CMAC_C) + { -(MBEDTLS_ERR_CMAC_HW_ACCEL_FAILED), "CMAC_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_CMAC_C */ + +#if defined(MBEDTLS_CTR_DRBG_C) + { -(MBEDTLS_ERR_CTR_DRBG_ENTROPY_SOURCE_FAILED), "CTR_DRBG_ENTROPY_SOURCE_FAILED" }, + { -(MBEDTLS_ERR_CTR_DRBG_REQUEST_TOO_BIG), "CTR_DRBG_REQUEST_TOO_BIG" }, + { -(MBEDTLS_ERR_CTR_DRBG_INPUT_TOO_BIG), "CTR_DRBG_INPUT_TOO_BIG" }, + { -(MBEDTLS_ERR_CTR_DRBG_FILE_IO_ERROR), "CTR_DRBG_FILE_IO_ERROR" }, +#endif /* MBEDTLS_CTR_DRBG_C */ + +#if defined(MBEDTLS_DES_C) + { -(MBEDTLS_ERR_DES_INVALID_INPUT_LENGTH), "DES_INVALID_INPUT_LENGTH" }, + { -(MBEDTLS_ERR_DES_HW_ACCEL_FAILED), "DES_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_DES_C */ + +#if defined(MBEDTLS_ENTROPY_C) + { -(MBEDTLS_ERR_ENTROPY_SOURCE_FAILED), "ENTROPY_SOURCE_FAILED" }, + { -(MBEDTLS_ERR_ENTROPY_MAX_SOURCES), "ENTROPY_MAX_SOURCES" }, + { -(MBEDTLS_ERR_ENTROPY_NO_SOURCES_DEFINED), "ENTROPY_NO_SOURCES_DEFINED" }, + { -(MBEDTLS_ERR_ENTROPY_NO_STRONG_SOURCE), "ENTROPY_NO_STRONG_SOURCE" }, + { -(MBEDTLS_ERR_ENTROPY_FILE_IO_ERROR), "ENTROPY_FILE_IO_ERROR" }, +#endif /* MBEDTLS_ENTROPY_C */ + +#if defined(MBEDTLS_GCM_C) + { -(MBEDTLS_ERR_GCM_AUTH_FAILED), "GCM_AUTH_FAILED" }, + { -(MBEDTLS_ERR_GCM_HW_ACCEL_FAILED), "GCM_HW_ACCEL_FAILED" }, + { -(MBEDTLS_ERR_GCM_BAD_INPUT), "GCM_BAD_INPUT" }, +#endif /* MBEDTLS_GCM_C */ + +#if defined(MBEDTLS_HKDF_C) + { -(MBEDTLS_ERR_HKDF_BAD_INPUT_DATA), "HKDF_BAD_INPUT_DATA" }, +#endif /* MBEDTLS_HKDF_C */ + +#if defined(MBEDTLS_HMAC_DRBG_C) + { -(MBEDTLS_ERR_HMAC_DRBG_REQUEST_TOO_BIG), "HMAC_DRBG_REQUEST_TOO_BIG" }, + { -(MBEDTLS_ERR_HMAC_DRBG_INPUT_TOO_BIG), "HMAC_DRBG_INPUT_TOO_BIG" }, + { -(MBEDTLS_ERR_HMAC_DRBG_FILE_IO_ERROR), "HMAC_DRBG_FILE_IO_ERROR" }, + { -(MBEDTLS_ERR_HMAC_DRBG_ENTROPY_SOURCE_FAILED), "HMAC_DRBG_ENTROPY_SOURCE_FAILED" }, +#endif /* MBEDTLS_HMAC_DRBG_C */ + +#if defined(MBEDTLS_MD2_C) + { -(MBEDTLS_ERR_MD2_HW_ACCEL_FAILED), "MD2_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_MD2_C */ + +#if defined(MBEDTLS_MD4_C) + { -(MBEDTLS_ERR_MD4_HW_ACCEL_FAILED), "MD4_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_MD4_C */ + +#if defined(MBEDTLS_MD5_C) + { -(MBEDTLS_ERR_MD5_HW_ACCEL_FAILED), "MD5_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_MD5_C */ + +#if defined(MBEDTLS_NET_C) + { -(MBEDTLS_ERR_NET_SOCKET_FAILED), "NET_SOCKET_FAILED" }, + { -(MBEDTLS_ERR_NET_CONNECT_FAILED), "NET_CONNECT_FAILED" }, + { -(MBEDTLS_ERR_NET_BIND_FAILED), "NET_BIND_FAILED" }, + { -(MBEDTLS_ERR_NET_LISTEN_FAILED), "NET_LISTEN_FAILED" }, + { -(MBEDTLS_ERR_NET_ACCEPT_FAILED), "NET_ACCEPT_FAILED" }, + { -(MBEDTLS_ERR_NET_RECV_FAILED), "NET_RECV_FAILED" }, + { -(MBEDTLS_ERR_NET_SEND_FAILED), "NET_SEND_FAILED" }, + { -(MBEDTLS_ERR_NET_CONN_RESET), "NET_CONN_RESET" }, + { -(MBEDTLS_ERR_NET_UNKNOWN_HOST), "NET_UNKNOWN_HOST" }, + { -(MBEDTLS_ERR_NET_BUFFER_TOO_SMALL), "NET_BUFFER_TOO_SMALL" }, + { -(MBEDTLS_ERR_NET_INVALID_CONTEXT), "NET_INVALID_CONTEXT" }, + { -(MBEDTLS_ERR_NET_POLL_FAILED), "NET_POLL_FAILED" }, + { -(MBEDTLS_ERR_NET_BAD_INPUT_DATA), "NET_BAD_INPUT_DATA" }, +#endif /* MBEDTLS_NET_C */ + +#if defined(MBEDTLS_OID_C) + { -(MBEDTLS_ERR_OID_NOT_FOUND), "OID_NOT_FOUND" }, + { -(MBEDTLS_ERR_OID_BUF_TOO_SMALL), "OID_BUF_TOO_SMALL" }, +#endif /* MBEDTLS_OID_C */ + +#if defined(MBEDTLS_PADLOCK_C) + { -(MBEDTLS_ERR_PADLOCK_DATA_MISALIGNED), "PADLOCK_DATA_MISALIGNED" }, +#endif /* MBEDTLS_PADLOCK_C */ + +#if defined(MBEDTLS_PLATFORM_C) + { -(MBEDTLS_ERR_PLATFORM_HW_ACCEL_FAILED), "PLATFORM_HW_ACCEL_FAILED" }, + { -(MBEDTLS_ERR_PLATFORM_FEATURE_UNSUPPORTED), "PLATFORM_FEATURE_UNSUPPORTED" }, +#endif /* MBEDTLS_PLATFORM_C */ + +#if defined(MBEDTLS_POLY1305_C) + { -(MBEDTLS_ERR_POLY1305_BAD_INPUT_DATA), "POLY1305_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_POLY1305_FEATURE_UNAVAILABLE), "POLY1305_FEATURE_UNAVAILABLE" }, + { -(MBEDTLS_ERR_POLY1305_HW_ACCEL_FAILED), "POLY1305_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_POLY1305_C */ + +#if defined(MBEDTLS_RIPEMD160_C) + { -(MBEDTLS_ERR_RIPEMD160_HW_ACCEL_FAILED), "RIPEMD160_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_RIPEMD160_C */ + +#if defined(MBEDTLS_SHA1_C) + { -(MBEDTLS_ERR_SHA1_HW_ACCEL_FAILED), "SHA1_HW_ACCEL_FAILED" }, + { -(MBEDTLS_ERR_SHA1_BAD_INPUT_DATA), "SHA1_BAD_INPUT_DATA" }, +#endif /* MBEDTLS_SHA1_C */ + +#if defined(MBEDTLS_SHA256_C) + { -(MBEDTLS_ERR_SHA256_HW_ACCEL_FAILED), "SHA256_HW_ACCEL_FAILED" }, + { -(MBEDTLS_ERR_SHA256_BAD_INPUT_DATA), "SHA256_BAD_INPUT_DATA" }, +#endif /* MBEDTLS_SHA256_C */ + +#if defined(MBEDTLS_SHA512_C) + { -(MBEDTLS_ERR_SHA512_HW_ACCEL_FAILED), "SHA512_HW_ACCEL_FAILED" }, + { -(MBEDTLS_ERR_SHA512_BAD_INPUT_DATA), "SHA512_BAD_INPUT_DATA" }, +#endif /* MBEDTLS_SHA512_C */ + +#if defined(MBEDTLS_THREADING_C) + { -(MBEDTLS_ERR_THREADING_FEATURE_UNAVAILABLE), "THREADING_FEATURE_UNAVAILABLE" }, + { -(MBEDTLS_ERR_THREADING_BAD_INPUT_DATA), "THREADING_BAD_INPUT_DATA" }, + { -(MBEDTLS_ERR_THREADING_MUTEX_ERROR), "THREADING_MUTEX_ERROR" }, +#endif /* MBEDTLS_THREADING_C */ + +#if defined(MBEDTLS_XTEA_C) + { -(MBEDTLS_ERR_XTEA_INVALID_INPUT_LENGTH), "XTEA_INVALID_INPUT_LENGTH" }, + { -(MBEDTLS_ERR_XTEA_HW_ACCEL_FAILED), "XTEA_HW_ACCEL_FAILED" }, +#endif /* MBEDTLS_XTEA_C */ +// END generated code +}; + +static const char *mbedtls_err_prefix = "MBEDTLS_ERR_"; +#define MBEDTLS_ERR_PREFIX_LEN ( sizeof("MBEDTLS_ERR_")-1 ) + +// copy error text into buffer, ensure null termination, return strlen of result +static size_t mbedtls_err_to_str(int err, const struct ssl_errs tab[], int tab_len, char *buf, size_t buflen) { + if (buflen == 0) return 0; + + // prefix for all error names + strncpy(buf, mbedtls_err_prefix, buflen); + if (buflen <= MBEDTLS_ERR_PREFIX_LEN+1) { + buf[buflen-1] = 0; + return buflen-1; + } + + // append error name from table + for (int i = 0; i < tab_len; i++) { + if (tab[i].errnum == err) { + strncpy(buf+MBEDTLS_ERR_PREFIX_LEN, tab[i].errstr, buflen-MBEDTLS_ERR_PREFIX_LEN); + buf[buflen-1] = 0; + return strlen(buf); + } + } + + mbedtls_snprintf(buf+MBEDTLS_ERR_PREFIX_LEN, buflen-MBEDTLS_ERR_PREFIX_LEN, "UNKNOWN (0x%04X)", + err); + return strlen(buf); +} + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) + +void mbedtls_strerror(int ret, char *buf, size_t buflen) { + int use_ret; + + if (buflen == 0) return; + + buf[buflen-1] = 0; + + if (ret < 0) ret = -ret; + + // + // High-level error codes + // + uint8_t got_hl = (ret & 0xFF80) != 0; + if (got_hl) { + use_ret = ret & 0xFF80; + + // special case +#if defined(MBEDTLS_SSL_TLS_C) + if (use_ret == -(MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE)) { + strncpy(buf, "MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE", buflen); + buf[buflen-1] = 0; + return; + } +#endif + + size_t len = mbedtls_err_to_str(use_ret, mbedtls_high_level_error_tab, + ARRAY_SIZE(mbedtls_high_level_error_tab), buf, buflen); + + buf += len; + buflen -= len; + if (buflen == 0) return; + } + + // + // Low-level error codes + // + use_ret = ret & ~0xFF80; + + if (use_ret == 0) return; + + // If high level code is present, make a concatenation between both error strings. + if (got_hl) { + if (buflen < 2) return; + *buf++ = '+'; + buflen--; + } + + mbedtls_err_to_str(use_ret, mbedtls_low_level_error_tab, + ARRAY_SIZE(mbedtls_low_level_error_tab), buf, buflen); +} + +#else /* MBEDTLS_ERROR_C */ + +#if defined(MBEDTLS_ERROR_STRERROR_DUMMY) + +/* + * Provide an non-function in case MBEDTLS_ERROR_C is not defined + */ +void mbedtls_strerror( int ret, char *buf, size_t buflen ) +{ + ((void) ret); + + if( buflen > 0 ) + buf[0] = '\0'; +} + +#endif /* MBEDTLS_ERROR_STRERROR_DUMMY */ + +#endif /* MBEDTLS_ERROR_C */ diff --git a/lib/mbedtls_errors/tester.c b/lib/mbedtls_errors/tester.c new file mode 100644 index 0000000000000..6f1c788f50ad8 --- /dev/null +++ b/lib/mbedtls_errors/tester.c @@ -0,0 +1,58 @@ +#include "mbedtls/error.h" +#include +#include + +// test_code checks that the provided code results in the provided error string for any size +// buffer. It calls mbedtls_strerror() to fill a buffer that is from 1 to 100 bytes in length +// and then checks that the buffer contents is OK and that a few guard bytes before and after +// the buffer were not overwritten. +int test_code(int code, char *str) { + char buf[100]; + int ok = 1; + int res; + + // test zero-length buffer + memset(buf, -3, 100); + mbedtls_strerror(code, buf + 4, 0); + for (int i = 0; i < 10; i++) { + if (buf[i] != -3) { + printf("Error: guard overwritten buflen=0 i=%d buf[i]=%d\n", i, buf[i]); + ok = 0; + } + } + + // test + for (size_t buflen = 1; buflen < 90; buflen++) { + memset(buf, -3, 100); + mbedtls_strerror(code, buf + 4, buflen); + for (int i = 0; i < 4; i++) { + if (buf[i] != -3) { + printf("Error: pre-guard overwritten buflen=%d i=%d buf[i]=%d\n", buflen, i, buf[i]); + ok = 0; + } + } + for (int i = 4 + buflen; i < 100; i++) { + if (buf[i] != -3) { + printf("Error: post-guard overwritten buflen=%d i=%d buf[i]=%d\n", buflen, i, buf[i]); + ok = 0; + } + } + char exp[100]; + strncpy(exp, str, buflen); + exp[buflen - 1] = 0; + if (strcmp(buf + 4, exp) != 0) { + printf("Error: expected %s, got %s\n", exp, buf); + ok = 0; + } + } + + printf("Test %x -> %s is %s\n", code, str, ok?"OK":"*** BAD ***"); +} + +int main() { + test_code(0x7200, "MBEDTLS_ERR_SSL_INVALID_RECORD"); + test_code(0x7780, "MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE"); + test_code(0x0074, "MBEDTLS_ERR_SHA256_BAD_INPUT_DATA"); + test_code(0x6600 | 0x0074, "MBEDTLS_ERR_SSL_INVALID_VERIFY_HASH+MBEDTLS_ERR_SHA256_BAD_INPUT_DATA"); + test_code(103, "MBEDTLS_ERR_UNKNOWN (0x0067)"); +} diff --git a/lib/stm32lib b/lib/stm32lib index 668d7a9e54aea..58fee7c92bd57 160000 --- a/lib/stm32lib +++ b/lib/stm32lib @@ -1 +1 @@ -Subproject commit 668d7a9e54aea98f8fe8a858eac1d3daa80fa824 +Subproject commit 58fee7c92bd576814d3f2afd92fbc62990270ecc diff --git a/lib/timeutils/timeutils.h b/lib/timeutils/timeutils.h index cb7a72123a119..14da831dc8b58 100644 --- a/lib/timeutils/timeutils.h +++ b/lib/timeutils/timeutils.h @@ -27,6 +27,10 @@ #ifndef MICROPY_INCLUDED_LIB_TIMEUTILS_TIMEUTILS_H #define MICROPY_INCLUDED_LIB_TIMEUTILS_TIMEUTILS_H +// The number of seconds between 1970/1/1 and 2000/1/1 is calculated using: +// time.mktime((2000,1,1,0,0,0,0,0,0)) - time.mktime((1970,1,1,0,0,0,0,0,0)) +#define TIMEUTILS_SECONDS_1970_TO_2000 (946684800ULL) + typedef struct _timeutils_struct_time_t { uint16_t tm_year; // i.e. 2014 uint8_t tm_mon; // 1..12 @@ -51,4 +55,39 @@ mp_uint_t timeutils_seconds_since_2000(mp_uint_t year, mp_uint_t month, mp_uint_t timeutils_mktime(mp_uint_t year, mp_int_t month, mp_int_t mday, mp_int_t hours, mp_int_t minutes, mp_int_t seconds); +// Select the Epoch used by the port. +#if MICROPY_EPOCH_IS_1970 + +static inline uint64_t timeutils_seconds_since_epoch(mp_uint_t year, mp_uint_t month, + mp_uint_t date, mp_uint_t hour, mp_uint_t minute, mp_uint_t second) { + return timeutils_seconds_since_2000(year, month, date, hour, minute, second) + TIMEUTILS_SECONDS_1970_TO_2000; +} + +static inline mp_uint_t timeutils_seconds_since_epoch_from_nanoseconds_since_1970(uint64_t ns) { + return ns / 1000000000ULL; +} + +static inline uint64_t timeutils_nanoseconds_since_epoch_to_nanoseconds_since_1970(uint64_t ns) { + return ns; +} + +#else // Epoch is 2000 + +#define timeutils_seconds_since_epoch_to_struct_time timeutils_seconds_since_2000_to_struct_time +#define timeutils_seconds_since_epoch timeutils_seconds_since_2000 + +static inline uint64_t timeutils_seconds_since_epoch_to_nanoseconds_since_1970(mp_uint_t s) { + return ((uint64_t)s + TIMEUTILS_SECONDS_1970_TO_2000) * 1000000000ULL; +} + +static inline mp_uint_t timeutils_seconds_since_epoch_from_nanoseconds_since_1970(uint64_t ns) { + return ns / 1000000000ULL - TIMEUTILS_SECONDS_1970_TO_2000; +} + +static inline int64_t timeutils_nanoseconds_since_epoch_to_nanoseconds_since_1970(int64_t ns) { + return ns + TIMEUTILS_SECONDS_1970_TO_2000 * 1000000000ULL; +} + +#endif + #endif // MICROPY_INCLUDED_LIB_TIMEUTILS_TIMEUTILS_H diff --git a/lib/utils/mpirq.c b/lib/utils/mpirq.c index d04fab68bbb52..02139f24dc5fc 100644 --- a/lib/utils/mpirq.c +++ b/lib/utils/mpirq.c @@ -31,6 +31,8 @@ #include "py/gc.h" #include "lib/utils/mpirq.h" +#if MICROPY_ENABLE_SCHEDULER + /****************************************************************************** DECLARE PUBLIC DATA ******************************************************************************/ @@ -51,19 +53,25 @@ const mp_arg_t mp_irq_init_args[] = { mp_irq_obj_t *mp_irq_new(const mp_irq_methods_t *methods, mp_obj_t parent) { mp_irq_obj_t *self = m_new0(mp_irq_obj_t, 1); + mp_irq_init(self, methods, parent); + return self; +} + +void mp_irq_init(mp_irq_obj_t *self, const mp_irq_methods_t *methods, mp_obj_t parent) { self->base.type = &mp_irq_type; self->methods = (mp_irq_methods_t *)methods; self->parent = parent; self->handler = mp_const_none; self->ishard = false; - return self; } void mp_irq_handler(mp_irq_obj_t *self) { if (self->handler != mp_const_none) { if (self->ishard) { - // When executing code within a handler we must lock the GC to prevent - // any memory allocations. + // When executing code within a handler we must lock the scheduler to + // prevent any scheduled callbacks from running, and lock the GC to + // prevent any memory allocations. + mp_sched_lock(); gc_lock(); nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { @@ -77,6 +85,7 @@ void mp_irq_handler(mp_irq_obj_t *self) { mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val)); } gc_unlock(); + mp_sched_unlock(); } else { // Schedule call to user function mp_sched_schedule(self->handler, self->parent); @@ -122,3 +131,5 @@ const mp_obj_type_t mp_irq_type = { .call = mp_irq_call, .locals_dict = (mp_obj_dict_t *)&mp_irq_locals_dict, }; + +#endif // MICROPY_ENABLE_SCHEDULER diff --git a/lib/utils/mpirq.h b/lib/utils/mpirq.h index 548185b531147..dd423c0101137 100644 --- a/lib/utils/mpirq.h +++ b/lib/utils/mpirq.h @@ -26,6 +26,8 @@ #ifndef MICROPY_INCLUDED_LIB_UTILS_MPIRQ_H #define MICROPY_INCLUDED_LIB_UTILS_MPIRQ_H +#include "py/runtime.h" + /****************************************************************************** DEFINE CONSTANTS ******************************************************************************/ @@ -41,20 +43,17 @@ enum { DEFINE TYPES ******************************************************************************/ -typedef mp_obj_t (*mp_irq_init_t)(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); -typedef mp_uint_t (*mp_irq_uint_method_one_uint_para_t)(mp_obj_t self, mp_uint_t trigger); -typedef mp_uint_t (*mp_irq_int_method_one_para_t)(mp_obj_t self, mp_uint_t info_type); +typedef mp_uint_t (*mp_irq_trigger_fun_t)(mp_obj_t self, mp_uint_t trigger); +typedef mp_uint_t (*mp_irq_info_fun_t)(mp_obj_t self, mp_uint_t info_type); enum { MP_IRQ_INFO_FLAGS, MP_IRQ_INFO_TRIGGERS, - MP_IRQ_INFO_CNT }; typedef struct _mp_irq_methods_t { - mp_irq_init_t init; - mp_irq_uint_method_one_uint_para_t trigger; - mp_irq_int_method_one_para_t info; + mp_irq_trigger_fun_t trigger; + mp_irq_info_fun_t info; } mp_irq_methods_t; typedef struct _mp_irq_obj_t { @@ -77,6 +76,7 @@ extern const mp_obj_type_t mp_irq_type; ******************************************************************************/ mp_irq_obj_t *mp_irq_new(const mp_irq_methods_t *methods, mp_obj_t parent); +void mp_irq_init(mp_irq_obj_t *self, const mp_irq_methods_t *methods, mp_obj_t parent); void mp_irq_handler(mp_irq_obj_t *self); #endif // MICROPY_INCLUDED_LIB_UTILS_MPIRQ_H diff --git a/lib/utils/printf.c b/lib/utils/printf.c index 6266ab66d3cdc..e8db2b999c123 100644 --- a/lib/utils/printf.c +++ b/lib/utils/printf.c @@ -104,7 +104,7 @@ STATIC void strn_print_strn(void *data, const char *str, size_t len) { // when linkings against it statically. // GCC 9 gives a warning about missing attributes so it's excluded until // uClibc+GCC9 support is needed. -int __GI_vsnprintf(char *str, size_t size, const char *fmt, va_list ap) __attribute__((weak, alias ("vsnprintf"))); +int __GI_vsnprintf(char *str, size_t size, const char *fmt, va_list ap) __attribute__((weak, alias("vsnprintf"))); #endif int vsnprintf(char *str, size_t size, const char *fmt, va_list ap) { diff --git a/lib/utils/pyexec.c b/lib/utils/pyexec.c index ec20daff4b23d..2c8ca2de0cecc 100644 --- a/lib/utils/pyexec.c +++ b/lib/utils/pyexec.c @@ -96,7 +96,7 @@ STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input mp_parse_tree_t parse_tree = mp_parse(lex, input_kind); module_fun = mp_compile(&parse_tree, source_name, exec_flags & EXEC_FLAG_IS_REPL); #else - mp_raise_msg(&mp_type_RuntimeError, "script compilation not supported"); + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("script compilation not supported")); #endif } diff --git a/ports/bare-arm/mpconfigport.h b/ports/bare-arm/mpconfigport.h index 4567676580324..7fd236bfba9e4 100644 --- a/ports/bare-arm/mpconfigport.h +++ b/ports/bare-arm/mpconfigport.h @@ -21,6 +21,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_PY_ASSIGN_EXPR (0) #define MICROPY_PY_BUILTINS_BYTEARRAY (0) #define MICROPY_PY_BUILTINS_DICT_FROMKEYS (0) #define MICROPY_PY_BUILTINS_MEMORYVIEW (0) diff --git a/ports/cc3200/boards/make-pins.py b/ports/cc3200/boards/make-pins.py index a204561cfbc32..0cf0d565606f2 100644 --- a/ports/cc3200/boards/make-pins.py +++ b/ports/cc3200/boards/make-pins.py @@ -216,7 +216,10 @@ def main(): default="cc3200_af.csv", ) parser.add_argument( - "-b", "--board", dest="board_filename", help="Specifies the board file", + "-b", + "--board", + dest="board_filename", + help="Specifies the board file", ) parser.add_argument( "-p", diff --git a/ports/cc3200/main.c b/ports/cc3200/main.c index b9f67194b6fc0..bdd73e7bf9ba7 100644 --- a/ports/cc3200/main.c +++ b/ports/cc3200/main.c @@ -51,8 +51,8 @@ ******************************************************************************/ // This is the static memory (TCB and stack) for the idle task -static StaticTask_t xIdleTaskTCB __attribute__ ((section (".rtos_heap"))); -static StackType_t uxIdleTaskStack[configMINIMAL_STACK_SIZE] __attribute__ ((section (".rtos_heap"))) __attribute__((aligned (8))); +static StaticTask_t xIdleTaskTCB __attribute__ ((section(".rtos_heap"))); +static StackType_t uxIdleTaskStack[configMINIMAL_STACK_SIZE] __attribute__ ((section(".rtos_heap"))) __attribute__((aligned(8))); /****************************************************************************** DECLARE PUBLIC DATA @@ -62,18 +62,18 @@ OsiTaskHandle mpTaskHandle; #endif // This is the FreeRTOS heap, defined here so we can put it in a special segment -uint8_t ucHeap[ configTOTAL_HEAP_SIZE ] __attribute__ ((section (".rtos_heap"))) __attribute__((aligned (8))); +uint8_t ucHeap[ configTOTAL_HEAP_SIZE ] __attribute__ ((section(".rtos_heap"))) __attribute__((aligned(8))); // This is the static memory (TCB and stack) for the main MicroPython task -StaticTask_t mpTaskTCB __attribute__ ((section (".rtos_heap"))); -StackType_t mpTaskStack[MICROPY_TASK_STACK_LEN] __attribute__ ((section (".rtos_heap"))) __attribute__((aligned (8))); +StaticTask_t mpTaskTCB __attribute__ ((section(".rtos_heap"))); +StackType_t mpTaskStack[MICROPY_TASK_STACK_LEN] __attribute__ ((section(".rtos_heap"))) __attribute__((aligned(8))); /****************************************************************************** DEFINE PUBLIC FUNCTIONS ******************************************************************************/ -__attribute__ ((section (".boot"))) -int main (void) { +__attribute__ ((section(".boot"))) +int main(void) { // Initialize the clocks and the interrupt system HAL_SystemInit(); diff --git a/ports/cc3200/mods/modmachine.c b/ports/cc3200/mods/modmachine.c index f70b399c07ebd..ddecd47f2b013 100644 --- a/ports/cc3200/mods/modmachine.c +++ b/ports/cc3200/mods/modmachine.c @@ -29,7 +29,6 @@ #include "py/runtime.h" #include "py/mphal.h" -#include "irq.h" #include "inc/hw_types.h" #include "inc/hw_gpio.h" #include "inc/hw_ints.h" @@ -69,6 +68,9 @@ extern OsiTaskHandle xSimpleLinkSpawnTaskHndl; /// \module machine - functions related to the SoC /// +MP_DECLARE_CONST_FUN_OBJ_0(machine_disable_irq_obj); +MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(machine_enable_irq_obj); + /******************************************************************************/ // MicroPython bindings; @@ -176,8 +178,8 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_reset_cause), MP_ROM_PTR(&machine_reset_cause_obj) }, { MP_ROM_QSTR(MP_QSTR_wake_reason), MP_ROM_PTR(&machine_wake_reason_obj) }, - { MP_ROM_QSTR(MP_QSTR_disable_irq), MP_ROM_PTR(&pyb_disable_irq_obj) }, - { MP_ROM_QSTR(MP_QSTR_enable_irq), MP_ROM_PTR(&pyb_enable_irq_obj) }, + { MP_ROM_QSTR(MP_QSTR_disable_irq), MP_ROM_PTR(&machine_disable_irq_obj) }, + { MP_ROM_QSTR(MP_QSTR_enable_irq), MP_ROM_PTR(&machine_enable_irq_obj) }, { MP_ROM_QSTR(MP_QSTR_RTC), MP_ROM_PTR(&pyb_rtc_type) }, { MP_ROM_QSTR(MP_QSTR_Pin), MP_ROM_PTR(&pin_type) }, diff --git a/ports/cc3200/mods/modutime.c b/ports/cc3200/mods/modutime.c index a729d62f9cbb2..e77065ef450db 100644 --- a/ports/cc3200/mods/modutime.c +++ b/ports/cc3200/mods/modutime.c @@ -133,6 +133,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(time_sleep_obj, time_sleep); STATIC const mp_rom_map_elem_t time_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_utime) }, + { MP_ROM_QSTR(MP_QSTR_gmtime), MP_ROM_PTR(&time_localtime_obj) }, { MP_ROM_QSTR(MP_QSTR_localtime), MP_ROM_PTR(&time_localtime_obj) }, { MP_ROM_QSTR(MP_QSTR_mktime), MP_ROM_PTR(&time_mktime_obj) }, { MP_ROM_QSTR(MP_QSTR_time), MP_ROM_PTR(&time_time_obj) }, diff --git a/ports/cc3200/mptask.c b/ports/cc3200/mptask.c index f06a502776087..71b650ff858c8 100644 --- a/ports/cc3200/mptask.c +++ b/ports/cc3200/mptask.c @@ -113,7 +113,7 @@ static const char fresh_boot_py[] = "# boot.py -- run on boot-up\r\n" uintptr_t cortex_m3_get_sp(void); -void TASK_MicroPython (void *pvParameters) { +void TASK_MicroPython(void *pvParameters) { // get the top of the stack to initialize the garbage collector uint32_t sp = cortex_m3_get_sp(); @@ -262,16 +262,16 @@ void TASK_MicroPython (void *pvParameters) { /****************************************************************************** DEFINE PRIVATE FUNCTIONS ******************************************************************************/ -__attribute__ ((section (".boot"))) -STATIC void mptask_pre_init (void) { +__attribute__ ((section(".boot"))) +STATIC void mptask_pre_init(void) { // this one only makes sense after a poweron reset pyb_rtc_pre_init(); // Create the simple link spawn task - ASSERT (OSI_OK == VStartSimpleLinkSpawnTask(SIMPLELINK_SPAWN_TASK_PRIORITY)); + ASSERT(OSI_OK == VStartSimpleLinkSpawnTask(SIMPLELINK_SPAWN_TASK_PRIORITY)); // Allocate memory for the flash file system - ASSERT ((sflash_vfs_fat = mem_Malloc(sizeof(*sflash_vfs_fat))) != NULL); + ASSERT((sflash_vfs_fat = mem_Malloc(sizeof(*sflash_vfs_fat))) != NULL); // this one allocates memory for the nvic vault pyb_sleep_pre_init(); @@ -295,7 +295,7 @@ STATIC void mptask_pre_init (void) { ASSERT(svTaskHandle != NULL); } -STATIC void mptask_init_sflash_filesystem (void) { +STATIC void mptask_init_sflash_filesystem(void) { FILINFO fno; // Initialise the local flash filesystem. @@ -378,16 +378,16 @@ STATIC void mptask_init_sflash_filesystem (void) { } } -STATIC void mptask_enter_ap_mode (void) { +STATIC void mptask_enter_ap_mode(void) { // append the mac only if it's not the first boot bool add_mac = !PRCMGetSpecialBit(PRCM_FIRST_BOOT_BIT); // enable simplelink in ap mode (use the MAC address to make the ssid unique) - wlan_sl_init (ROLE_AP, MICROPY_PORT_WLAN_AP_SSID, strlen(MICROPY_PORT_WLAN_AP_SSID), + wlan_sl_init(ROLE_AP, MICROPY_PORT_WLAN_AP_SSID, strlen(MICROPY_PORT_WLAN_AP_SSID), MICROPY_PORT_WLAN_AP_SECURITY, MICROPY_PORT_WLAN_AP_KEY, strlen(MICROPY_PORT_WLAN_AP_KEY), MICROPY_PORT_WLAN_AP_CHANNEL, ANTENNA_TYPE_INTERNAL, add_mac); } -STATIC void mptask_create_main_py (void) { +STATIC void mptask_create_main_py(void) { // create empty main.py FIL fp; f_open(&sflash_vfs_fat->fatfs, &fp, "/main.py", FA_WRITE | FA_CREATE_ALWAYS); diff --git a/ports/cc3200/serverstask.c b/ports/cc3200/serverstask.c index 517a7a2281bdb..03eed8eeb0cf4 100644 --- a/ports/cc3200/serverstask.c +++ b/ports/cc3200/serverstask.c @@ -68,8 +68,8 @@ static volatile bool sleep_sockets = false; ******************************************************************************/ // This is the static memory (TCB and stack) for the servers task -StaticTask_t svTaskTCB __attribute__ ((section (".rtos_heap"))); -StackType_t svTaskStack[SERVERS_STACK_LEN] __attribute__ ((section (".rtos_heap"))) __attribute__((aligned (8))); +StaticTask_t svTaskTCB __attribute__ ((section(".rtos_heap"))); +StackType_t svTaskStack[SERVERS_STACK_LEN] __attribute__ ((section(".rtos_heap"))) __attribute__((aligned(8))); char servers_user[SERVERS_USER_PASS_LEN_MAX + 1]; char servers_pass[SERVERS_USER_PASS_LEN_MAX + 1]; @@ -77,12 +77,12 @@ char servers_pass[SERVERS_USER_PASS_LEN_MAX + 1]; /****************************************************************************** DECLARE PUBLIC FUNCTIONS ******************************************************************************/ -void TASK_Servers (void *pvParameters) { +void TASK_Servers(void *pvParameters) { bool cycle = false; - strcpy (servers_user, SERVERS_DEF_USER); - strcpy (servers_pass, SERVERS_DEF_PASS); + strcpy(servers_user, SERVERS_DEF_USER); + strcpy(servers_pass, SERVERS_DEF_PASS); telnet_init(); ftp_init(); @@ -143,12 +143,12 @@ void TASK_Servers (void *pvParameters) { } } -void servers_start (void) { +void servers_start(void) { servers_data.do_enable = true; mp_hal_delay_ms(SERVERS_CYCLE_TIME_MS * 3); } -void servers_stop (void) { +void servers_stop(void) { servers_data.do_disable = true; do { mp_hal_delay_ms(SERVERS_CYCLE_TIME_MS); @@ -156,24 +156,24 @@ void servers_stop (void) { mp_hal_delay_ms(SERVERS_CYCLE_TIME_MS * 3); } -void servers_reset (void) { +void servers_reset(void) { servers_data.do_reset = true; } -void servers_wlan_cycle_power (void) { +void servers_wlan_cycle_power(void) { servers_data.do_wlan_cycle_power = true; } -bool servers_are_enabled (void) { +bool servers_are_enabled(void) { return servers_data.enabled; } -void server_sleep_sockets (void) { +void server_sleep_sockets(void) { sleep_sockets = true; mp_hal_delay_ms(SERVERS_CYCLE_TIME_MS + 1); } -void servers_close_socket (int16_t *sd) { +void servers_close_socket(int16_t *sd) { if (*sd > 0) { modusocket_socket_delete(*sd); sl_Close(*sd); @@ -181,7 +181,7 @@ void servers_close_socket (int16_t *sd) { } } -void servers_set_login (char *user, char *pass) { +void servers_set_login(char *user, char *pass) { if (strlen(user) > SERVERS_USER_PASS_LEN_MAX || strlen(pass) > SERVERS_USER_PASS_LEN_MAX) { mp_raise_ValueError(MP_ERROR_TEXT("invalid argument(s) value")); } @@ -189,7 +189,7 @@ void servers_set_login (char *user, char *pass) { memcpy(servers_pass, pass, SERVERS_USER_PASS_LEN_MAX); } -void servers_set_timeout (uint32_t timeout) { +void servers_set_timeout(uint32_t timeout) { if (timeout < SERVERS_MIN_TIMEOUT_MS) { // timeout is too low mp_raise_ValueError(MP_ERROR_TEXT("invalid argument(s) value")); @@ -197,7 +197,7 @@ void servers_set_timeout (uint32_t timeout) { servers_data.timeout = timeout; } -uint32_t servers_get_timeout (void) { +uint32_t servers_get_timeout(void) { return servers_data.timeout; } diff --git a/ports/esp32/Makefile b/ports/esp32/Makefile index bd89e8b0c5610..be282577229c4 100644 --- a/ports/esp32/Makefile +++ b/ports/esp32/Makefile @@ -54,9 +54,9 @@ SDKCONFIG_COMBINED = $(BUILD)/sdkconfig.combined SDKCONFIG_H = $(BUILD)/sdkconfig.h # The git hash of the currently supported ESP IDF version. -# These correspond to v3.3.2 and v4.0. +# These correspond to v3.3.2 and v4.0.1. ESPIDF_SUPHASH_V3 := 9e70825d1e1cbf7988cf36981774300066580ea7 -ESPIDF_SUPHASH_V4 := 463a9d8b7f9af8205222b80707f9bdbba7c530e1 +ESPIDF_SUPHASH_V4 := 4c81978a3e2220674a432a588292a4c860eef27b define print_supported_git_hash $(info Supported git hash (v3.3): $(ESPIDF_SUPHASH_V3)) @@ -136,9 +136,6 @@ include $(SDKCONFIG) INC += -I. INC += -I$(TOP) -INC += -I$(TOP)/lib/mp-readline -INC += -I$(TOP)/lib/netutils -INC += -I$(TOP)/lib/timeutils INC += -I$(BUILD) INC_ESPCOMP += -I$(ESPCOMP)/bootloader_support/include @@ -341,7 +338,7 @@ SRC_C = \ modnetwork.c \ network_lan.c \ network_ppp.c \ - nimble.c \ + mpnimbleport.c \ modsocket.c \ modesp.c \ esp32_partition.c \ @@ -362,6 +359,7 @@ EXTMOD_SRC_C += $(addprefix extmod/,\ ) LIB_SRC_C = $(addprefix lib/,\ + mbedtls_errors/mp_mbedtls_errors.c \ mp-readline/readline.c \ netutils/netutils.c \ timeutils/timeutils.c \ @@ -506,16 +504,17 @@ ESPIDF_LWIP_O = $(patsubst %.c,%.o,\ $(wildcard $(ESPCOMP)/lwip/port/esp32/*/*.c) \ ) -ESPIDF_MBEDTLS_O = $(patsubst %.c,%.o,\ +# Mbedtls source files, exclude error.c in favor of lib/mbedtls_errors/mp_mbedtls_errors.c +ESPIDF_MBEDTLS_O = $(patsubst %.c,%.o, $(filter-out %/error.c,\ $(wildcard $(ESPCOMP)/mbedtls/mbedtls/library/*.c) \ $(wildcard $(ESPCOMP)/mbedtls/port/*.c) \ $(wildcard $(ESPCOMP)/mbedtls/port/esp32/*.c) \ - ) + )) ESPIDF_MDNS_O = $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/mdns/*.c)) ifeq ($(ESPIDF_CURHASH),$(ESPIDF_SUPHASH_V4)) -$(BUILD)/$(ESPCOMP)/wpa_supplicant/%.o: CFLAGS += -DESP_SUPPLICANT -DIEEE8021X_EAPOL -DEAP_PEER_METHOD -DEAP_TLS -DEAP_TTLS -DEAP_PEAP -DEAP_MSCHAPv2 -DUSE_WPA2_TASK -DCONFIG_WPS2 -DCONFIG_WPS_PIN -DUSE_WPS_TASK -DESPRESSIF_USE -DESP32_WORKAROUND -DCONFIG_ECC -D__ets__ -Wno-strict-aliasing -I$(ESPCOMP)/wpa_supplicant/src -Wno-implicit-function-declaration +$(BUILD)/$(ESPCOMP)/wpa_supplicant/%.o: CFLAGS += -DCONFIG_WPA3_SAE -DCONFIG_IEEE80211W -DESP_SUPPLICANT -DIEEE8021X_EAPOL -DEAP_PEER_METHOD -DEAP_TLS -DEAP_TTLS -DEAP_PEAP -DEAP_MSCHAPv2 -DUSE_WPA2_TASK -DCONFIG_WPS2 -DCONFIG_WPS_PIN -DUSE_WPS_TASK -DESPRESSIF_USE -DESP32_WORKAROUND -DCONFIG_ECC -D__ets__ -Wno-strict-aliasing -I$(ESPCOMP)/wpa_supplicant/src -Wno-implicit-function-declaration else $(BUILD)/$(ESPCOMP)/wpa_supplicant/%.o: CFLAGS += -DEMBEDDED_SUPP -DIEEE8021X_EAPOL -DEAP_PEER_METHOD -DEAP_MSCHAPv2 -DEAP_TTLS -DEAP_TLS -DEAP_PEAP -DUSE_WPA2_TASK -DCONFIG_WPS2 -DCONFIG_WPS_PIN -DUSE_WPS_TASK -DESPRESSIF_USE -DESP32_WORKAROUND -DALLOW_EVEN_MOD -D__ets__ -Wno-strict-aliasing endif @@ -555,6 +554,7 @@ ESPIDF_BT_NIMBLE_O = $(patsubst %.c,%.o,\ $(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/src/*.c) \ $(wildcard $(ESPCOMP)/bt/host/nimble/nimble/porting/nimble/src/*.c) \ $(wildcard $(ESPCOMP)/bt/host/nimble/nimble/porting/npl/freertos/src/*.c) \ + $(wildcard $(ESPCOMP)/bt/host/nimble/port/src/*.c) \ ) endif diff --git a/ports/esp32/boards/sdkconfig.base b/ports/esp32/boards/sdkconfig.base index f44ec4e173e9b..67e2424a1246d 100644 --- a/ports/esp32/boards/sdkconfig.base +++ b/ports/esp32/boards/sdkconfig.base @@ -11,6 +11,11 @@ CONFIG_APP_EXCLUDE_PROJECT_NAME_VAR=y # Bootloader config CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y +# Change default log level to "ERROR" (instead of "INFO") +CONFIG_LOG_DEFAULT_LEVEL_INFO=n +CONFIG_LOG_DEFAULT_LEVEL_ERROR=y +CONFIG_LOG_DEFAULT_LEVEL=1 + # ESP32-specific CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=n CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=n diff --git a/ports/esp32/boards/sdkconfig.ble b/ports/esp32/boards/sdkconfig.ble index cdbb621a63112..f714ce4629b32 100644 --- a/ports/esp32/boards/sdkconfig.ble +++ b/ports/esp32/boards/sdkconfig.ble @@ -9,9 +9,12 @@ CONFIG_BT_NIMBLE_ENABLED=y CONFIG_BT_NIMBLE_MAX_CONNECTIONS=4 # Pin to the same core as MP. -CONFIG_BT_NIMBLE_PINNED_TO_CORE_0=n -CONFIG_BT_NIMBLE_PINNED_TO_CORE_1=y -CONFIG_BT_NIMBLE_PINNED_TO_CORE=1 +# Until we move to IDF 4.2+, we need NimBLE on core 0, and for synchronisation +# with the ringbuffer and scheduler MP needs to be on the same core. +# See https://github.com/micropython/micropython/issues/5489 +CONFIG_BT_NIMBLE_PINNED_TO_CORE_0=y +CONFIG_BT_NIMBLE_PINNED_TO_CORE_1=n +CONFIG_BT_NIMBLE_PINNED_TO_CORE=0 # v3.3-only (renamed in 4.0) CONFIG_BTDM_CONTROLLER_MODE_BLE_ONLY=y diff --git a/ports/esp32/esp32_rmt.c b/ports/esp32/esp32_rmt.c index 25b7b3808981c..7971ca5d1c5af 100644 --- a/ports/esp32/esp32_rmt.c +++ b/ports/esp32/esp32_rmt.c @@ -52,8 +52,11 @@ typedef struct _esp32_rmt_obj_t { uint8_t channel_id; gpio_num_t pin; uint8_t clock_div; + uint16_t carrier_duty_percent; + uint32_t carrier_freq; mp_uint_t num_items; rmt_item32_t *items; + bool loop_en; } esp32_rmt_obj_t; 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) { @@ -61,6 +64,8 @@ STATIC mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, siz { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} }, { MP_QSTR_pin, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, { MP_QSTR_clock_div, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 8} }, // 100ns resolution + { MP_QSTR_carrier_duty_percent, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 50} }, + { MP_QSTR_carrier_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, }; 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); @@ -68,6 +73,16 @@ STATIC mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, siz gpio_num_t pin_id = machine_pin_get_id(args[1].u_obj); mp_uint_t clock_div = args[2].u_int; + bool carrier_en = false; + mp_uint_t carrier_duty_percent = 0; + mp_uint_t carrier_freq = 0; + + if (args[4].u_int > 0) { + carrier_en = true; + carrier_duty_percent = args[3].u_int; + carrier_freq = args[4].u_int; + } + if (clock_div < 1 || clock_div > 255) { mp_raise_ValueError(MP_ERROR_TEXT("clock_div must be between 1 and 255")); } @@ -77,6 +92,9 @@ STATIC mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, siz self->channel_id = channel_id; self->pin = pin_id; self->clock_div = clock_div; + self->carrier_duty_percent = carrier_duty_percent; + self->carrier_freq = carrier_freq; + self->loop_en = false; rmt_config_t config; config.rmt_mode = RMT_MODE_TX; @@ -85,11 +103,11 @@ STATIC mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, siz config.mem_block_num = 1; config.tx_config.loop_en = 0; - config.tx_config.carrier_en = 0; + config.tx_config.carrier_en = carrier_en; config.tx_config.idle_output_en = 1; config.tx_config.idle_level = 0; - config.tx_config.carrier_duty_percent = 0; - config.tx_config.carrier_freq_hz = 0; + config.tx_config.carrier_duty_percent = self->carrier_duty_percent; + config.tx_config.carrier_freq_hz = self->carrier_freq; config.tx_config.carrier_level = 1; config.clk_div = self->clock_div; @@ -103,8 +121,14 @@ STATIC mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, siz STATIC void esp32_rmt_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); if (self->pin != -1) { - mp_printf(print, "RMT(channel=%u, pin=%u, source_freq=%u, clock_div=%u)", + mp_printf(print, "RMT(channel=%u, pin=%u, source_freq=%u, clock_div=%u", self->channel_id, self->pin, APB_CLK_FREQ, self->clock_div); + if (self->carrier_freq > 0) { + mp_printf(print, ", carrier_freq=%u, carrier_duty_percent=%u)", + self->carrier_freq, self->carrier_duty_percent); + } else { + mp_printf(print, ")"); + } } else { mp_printf(print, "RMT()"); } @@ -158,7 +182,15 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_KW(esp32_rmt_wait_done_obj, 1, esp32_rmt_wait_don STATIC mp_obj_t esp32_rmt_loop(mp_obj_t self_in, mp_obj_t loop) { esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); - check_esp_err(rmt_set_tx_loop_mode(self->channel_id, mp_obj_get_int(loop))); + self->loop_en = mp_obj_get_int(loop); + if (!self->loop_en) { + bool loop_en; + check_esp_err(rmt_get_tx_loop_mode(self->channel_id, &loop_en)); + if (loop_en) { + check_esp_err(rmt_set_tx_loop_mode(self->channel_id, false)); + check_esp_err(rmt_set_tx_intr_en(self->channel_id, true)); + } + } return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_2(esp32_rmt_loop_obj, esp32_rmt_loop); @@ -200,8 +232,24 @@ STATIC mp_obj_t esp32_rmt_write_pulses(size_t n_args, const mp_obj_t *pos_args, self->items[item_index].level1 = start++; } } + + if (self->loop_en) { + bool loop_en; + check_esp_err(rmt_get_tx_loop_mode(self->channel_id, &loop_en)); + if (loop_en) { + check_esp_err(rmt_set_tx_intr_en(self->channel_id, true)); + check_esp_err(rmt_set_tx_loop_mode(self->channel_id, false)); + } + check_esp_err(rmt_wait_tx_done(self->channel_id, portMAX_DELAY)); + check_esp_err(rmt_set_tx_intr_en(self->channel_id, false)); + } + check_esp_err(rmt_write_items(self->channel_id, self->items, num_items, false /* non-blocking */)); + if (self->loop_en) { + check_esp_err(rmt_set_tx_loop_mode(self->channel_id, true)); + } + return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(esp32_rmt_write_pulses_obj, 2, esp32_rmt_write_pulses); diff --git a/ports/esp32/fatfs_port.c b/ports/esp32/fatfs_port.c index 779ba1c88accc..7fce654c0997c 100644 --- a/ports/esp32/fatfs_port.c +++ b/ports/esp32/fatfs_port.c @@ -28,13 +28,13 @@ #include #include "lib/oofatfs/ff.h" -#include "timeutils.h" +#include "lib/timeutils/timeutils.h" DWORD get_fattime(void) { struct timeval tv; gettimeofday(&tv, NULL); timeutils_struct_time_t tm; - timeutils_seconds_since_2000_to_struct_time(tv.tv_sec, &tm); + timeutils_seconds_since_epoch_to_struct_time(tv.tv_sec, &tm); return ((DWORD)(tm.tm_year - 1980) << 25) | ((DWORD)tm.tm_mon << 21) | ((DWORD)tm.tm_mday << 16) | ((DWORD)tm.tm_hour << 11) | ((DWORD)tm.tm_min << 5) | ((DWORD)tm.tm_sec >> 1); diff --git a/ports/esp32/machine_hw_spi.c b/ports/esp32/machine_hw_spi.c index 3962e26b78610..3790b4e0cf142 100644 --- a/ports/esp32/machine_hw_spi.c +++ b/ports/esp32/machine_hw_spi.c @@ -351,6 +351,8 @@ STATIC void machine_hw_spi_init(mp_obj_base_t *self_in, size_t n_args, const mp_ } mp_obj_t machine_hw_spi_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + MP_MACHINE_SPI_CHECK_FOR_LEGACY_SOFTSPI_CONSTRUCTION(n_args, n_kw, all_args); + enum { ARG_id, ARG_baudrate, ARG_polarity, ARG_phase, ARG_bits, ARG_firstbit, ARG_sck, ARG_mosi, ARG_miso }; static const mp_arg_t allowed_args[] = { { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} }, diff --git a/ports/esp32/machine_i2c.c b/ports/esp32/machine_i2c.c index 8362ed5e7be5a..a1d0ad0f4d93c 100644 --- a/ports/esp32/machine_i2c.c +++ b/ports/esp32/machine_i2c.c @@ -28,6 +28,7 @@ #include "py/mphal.h" #include "py/mperrno.h" #include "extmod/machine_i2c.h" +#include "modmachine.h" #include "driver/i2c.h" @@ -45,7 +46,6 @@ typedef struct _machine_hw_i2c_obj_t { gpio_num_t sda : 8; } machine_hw_i2c_obj_t; -STATIC const mp_obj_type_t machine_hw_i2c_type; 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) { @@ -115,6 +115,8 @@ STATIC void machine_hw_i2c_print(const mp_print_t *print, mp_obj_t self_in, mp_p } mp_obj_t machine_hw_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + MP_MACHINE_I2C_CHECK_FOR_LEGACY_SOFTI2C_CONSTRUCTION(n_args, n_kw, all_args); + // Parse args enum { ARG_id, ARG_scl, ARG_sda, ARG_freq, ARG_timeout }; static const mp_arg_t allowed_args[] = { @@ -169,7 +171,7 @@ STATIC const mp_machine_i2c_p_t machine_hw_i2c_p = { .transfer = machine_hw_i2c_transfer, }; -STATIC const mp_obj_type_t machine_hw_i2c_type = { +const mp_obj_type_t machine_hw_i2c_type = { { &mp_type_type }, .name = MP_QSTR_I2C, .print = machine_hw_i2c_print, diff --git a/ports/esp32/machine_rtc.c b/ports/esp32/machine_rtc.c index c776eaa8d3d1c..1b6a71b5b4181 100644 --- a/ports/esp32/machine_rtc.c +++ b/ports/esp32/machine_rtc.c @@ -36,7 +36,7 @@ #include "py/obj.h" #include "py/runtime.h" #include "py/mphal.h" -#include "timeutils.h" +#include "lib/timeutils/timeutils.h" #include "modmachine.h" #include "machine_rtc.h" @@ -93,7 +93,7 @@ STATIC mp_obj_t machine_rtc_datetime_helper(mp_uint_t n_args, const mp_obj_t *ar gettimeofday(&tv, NULL); timeutils_struct_time_t tm; - timeutils_seconds_since_2000_to_struct_time(tv.tv_sec, &tm); + timeutils_seconds_since_epoch_to_struct_time(tv.tv_sec, &tm); mp_obj_t tuple[8] = { mp_obj_new_int(tm.tm_year), @@ -114,7 +114,7 @@ STATIC mp_obj_t machine_rtc_datetime_helper(mp_uint_t n_args, const mp_obj_t *ar mp_obj_get_array_fixed_n(args[1], 8, &items); struct timeval tv = {0}; - tv.tv_sec = timeutils_seconds_since_2000(mp_obj_get_int(items[0]), mp_obj_get_int(items[1]), mp_obj_get_int(items[2]), mp_obj_get_int(items[4]), mp_obj_get_int(items[5]), mp_obj_get_int(items[6])); + tv.tv_sec = timeutils_seconds_since_epoch(mp_obj_get_int(items[0]), mp_obj_get_int(items[1]), mp_obj_get_int(items[2]), mp_obj_get_int(items[4]), mp_obj_get_int(items[5]), mp_obj_get_int(items[6])); tv.tv_usec = mp_obj_get_int(items[7]); settimeofday(&tv, NULL); diff --git a/ports/esp32/machine_sdcard.c b/ports/esp32/machine_sdcard.c index 0fd4e86211e86..40686508a1e20 100644 --- a/ports/esp32/machine_sdcard.c +++ b/ports/esp32/machine_sdcard.c @@ -126,6 +126,7 @@ STATIC mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args ARG_mosi, ARG_sck, ARG_cs, + ARG_freq, }; STATIC const mp_arg_t allowed_args[] = { { MP_QSTR_slot, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1} }, @@ -137,6 +138,8 @@ STATIC mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args { MP_QSTR_mosi, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, { MP_QSTR_sck, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, { MP_QSTR_cs, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + // freq is valid for both SPI and SDMMC interfaces + { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 20000000} }, }; mp_arg_val_t arg_vals[MP_ARRAY_SIZE(allowed_args)]; mp_map_t kw_args; @@ -175,11 +178,14 @@ STATIC mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args self->flags = 0; // Note that these defaults are macros that expand to structure // constants so we can't directly assign them to fields. + int freq = arg_vals[ARG_freq].u_int; if (is_spi) { sdmmc_host_t _temp_host = SDSPI_HOST_DEFAULT(); + _temp_host.max_freq_khz = freq / 1000; self->host = _temp_host; } else { sdmmc_host_t _temp_host = SDMMC_HOST_DEFAULT(); + _temp_host.max_freq_khz = freq / 1000; self->host = _temp_host; } diff --git a/ports/esp32/main.c b/ports/esp32/main.c index 81e4a64346180..6f9ab82d0512a 100644 --- a/ports/esp32/main.c +++ b/ports/esp32/main.c @@ -65,7 +65,6 @@ // MicroPython runs as a task under FreeRTOS #define MP_TASK_PRIORITY (ESP_TASK_PRIO_MIN + 1) #define MP_TASK_STACK_SIZE (16 * 1024) -#define MP_TASK_STACK_LEN (MP_TASK_STACK_SIZE / sizeof(StackType_t)) int vprintf_null(const char *format, va_list ap) { // do nothing: this is used as a log target during raw repl mode @@ -75,7 +74,7 @@ int vprintf_null(const char *format, va_list ap) { void mp_task(void *pvParameter) { volatile uint32_t sp = (uint32_t)get_sp(); #if MICROPY_PY_THREAD - mp_thread_init(pxTaskGetStackStart(NULL), MP_TASK_STACK_LEN); + mp_thread_init(pxTaskGetStackStart(NULL), MP_TASK_STACK_SIZE / sizeof(uintptr_t)); #endif uart_init(); @@ -169,7 +168,7 @@ void app_main(void) { nvs_flash_erase(); nvs_flash_init(); } - xTaskCreatePinnedToCore(mp_task, "mp_task", MP_TASK_STACK_LEN, NULL, MP_TASK_PRIORITY, &mp_main_task_handle, MP_TASK_COREID); + xTaskCreatePinnedToCore(mp_task, "mp_task", MP_TASK_STACK_SIZE / sizeof(StackType_t), NULL, MP_TASK_PRIORITY, &mp_main_task_handle, MP_TASK_COREID); } void nlr_jump_fail(void *val) { diff --git a/ports/esp32/modesp32.c b/ports/esp32/modesp32.c index 8e248370ffb6d..28d1762d240de 100644 --- a/ports/esp32/modesp32.c +++ b/ports/esp32/modesp32.c @@ -41,7 +41,7 @@ #include "py/obj.h" #include "py/runtime.h" #include "py/mphal.h" -#include "timeutils.h" +#include "lib/timeutils/timeutils.h" #include "modmachine.h" #include "machine_rtc.h" #include "modesp32.h" diff --git a/ports/esp32/modmachine.c b/ports/esp32/modmachine.c index 8d8df995c294b..303d25ee8b2d1 100644 --- a/ports/esp32/modmachine.c +++ b/ports/esp32/modmachine.c @@ -146,36 +146,30 @@ STATIC mp_obj_t machine_deepsleep(size_t n_args, const mp_obj_t *pos_args, mp_ma STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_deepsleep_obj, 0, machine_deepsleep); STATIC mp_obj_t machine_reset_cause(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - switch (rtc_get_reset_reason(0)) { - case POWERON_RESET: + switch (esp_reset_reason()) { + case ESP_RST_POWERON: + case ESP_RST_BROWNOUT: return MP_OBJ_NEW_SMALL_INT(MP_PWRON_RESET); break; - case SW_RESET: - case SW_CPU_RESET: - return MP_OBJ_NEW_SMALL_INT(MP_SOFT_RESET); - break; - case OWDT_RESET: - case TG0WDT_SYS_RESET: - case TG1WDT_SYS_RESET: - case RTCWDT_SYS_RESET: - case RTCWDT_BROWN_OUT_RESET: - case RTCWDT_CPU_RESET: - case RTCWDT_RTC_RESET: - case TGWDT_CPU_RESET: + + case ESP_RST_INT_WDT: + case ESP_RST_TASK_WDT: + case ESP_RST_WDT: return MP_OBJ_NEW_SMALL_INT(MP_WDT_RESET); break; - case DEEPSLEEP_RESET: + case ESP_RST_DEEPSLEEP: return MP_OBJ_NEW_SMALL_INT(MP_DEEPSLEEP_RESET); break; - case EXT_CPU_RESET: + case ESP_RST_SW: + case ESP_RST_PANIC: + case ESP_RST_EXT: // Comment in ESP-IDF: "For ESP32, ESP_RST_EXT is never returned" return MP_OBJ_NEW_SMALL_INT(MP_HARD_RESET); break; - case NO_MEAN: - case SDIO_RESET: - case INTRUSION_RESET: + case ESP_RST_SDIO: + case ESP_RST_UNKNOWN: default: return MP_OBJ_NEW_SMALL_INT(0); break; @@ -261,10 +255,12 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_TouchPad), MP_ROM_PTR(&machine_touchpad_type) }, { MP_ROM_QSTR(MP_QSTR_ADC), MP_ROM_PTR(&machine_adc_type) }, { MP_ROM_QSTR(MP_QSTR_DAC), MP_ROM_PTR(&machine_dac_type) }, - { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_i2c_type) }, + { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_hw_i2c_type) }, + { MP_ROM_QSTR(MP_QSTR_SoftI2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, { MP_ROM_QSTR(MP_QSTR_PWM), MP_ROM_PTR(&machine_pwm_type) }, { MP_ROM_QSTR(MP_QSTR_RTC), MP_ROM_PTR(&machine_rtc_type) }, - { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&mp_machine_soft_spi_type) }, + { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_hw_spi_type) }, + { MP_ROM_QSTR(MP_QSTR_SoftSPI), MP_ROM_PTR(&mp_machine_soft_spi_type) }, { MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&machine_uart_type) }, // Reset reasons diff --git a/ports/esp32/modmachine.h b/ports/esp32/modmachine.h index fbb43250ff42e..98b36e32b0231 100644 --- a/ports/esp32/modmachine.h +++ b/ports/esp32/modmachine.h @@ -16,6 +16,7 @@ extern const mp_obj_type_t machine_touchpad_type; extern const mp_obj_type_t machine_adc_type; extern const mp_obj_type_t machine_dac_type; extern const mp_obj_type_t machine_pwm_type; +extern const mp_obj_type_t machine_hw_i2c_type; extern const mp_obj_type_t machine_hw_spi_type; extern const mp_obj_type_t machine_uart_type; extern const mp_obj_type_t machine_rtc_type; diff --git a/ports/esp32/modnetwork.c b/ports/esp32/modnetwork.c index 975029121ed0b..44263350409fc 100644 --- a/ports/esp32/modnetwork.c +++ b/ports/esp32/modnetwork.c @@ -40,7 +40,7 @@ #include "py/runtime.h" #include "py/mphal.h" #include "py/mperrno.h" -#include "netutils.h" +#include "lib/netutils/netutils.h" #include "esp_eth.h" #include "esp_wifi.h" #include "esp_log.h" @@ -755,8 +755,8 @@ STATIC const mp_rom_map_elem_t mp_module_network_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_WLAN), MP_ROM_PTR(&get_wlan_obj) }, #if !MICROPY_ESP_IDF_4 { MP_ROM_QSTR(MP_QSTR_LAN), MP_ROM_PTR(&get_lan_obj) }, - { MP_ROM_QSTR(MP_QSTR_PPP), MP_ROM_PTR(&ppp_make_new_obj) }, #endif + { MP_ROM_QSTR(MP_QSTR_PPP), MP_ROM_PTR(&ppp_make_new_obj) }, { MP_ROM_QSTR(MP_QSTR_phy_mode), MP_ROM_PTR(&esp_phy_mode_obj) }, #if MODNETWORK_INCLUDE_CONSTANTS @@ -776,6 +776,7 @@ STATIC const mp_rom_map_elem_t mp_module_network_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_PHY_LAN8720), MP_ROM_INT(PHY_LAN8720) }, { MP_ROM_QSTR(MP_QSTR_PHY_TLK110), MP_ROM_INT(PHY_TLK110) }, + { MP_ROM_QSTR(MP_QSTR_PHY_IP101), MP_ROM_INT(PHY_IP101) }, // ETH Clock modes from ESP-IDF #if !MICROPY_ESP_IDF_4 diff --git a/ports/esp32/modnetwork.h b/ports/esp32/modnetwork.h index 64d2da018c056..db1b3d1300932 100644 --- a/ports/esp32/modnetwork.h +++ b/ports/esp32/modnetwork.h @@ -26,7 +26,7 @@ #ifndef MICROPY_INCLUDED_ESP32_MODNETWORK_H #define MICROPY_INCLUDED_ESP32_MODNETWORK_H -enum { PHY_LAN8720, PHY_TLK110 }; +enum { PHY_LAN8720, PHY_TLK110, PHY_IP101 }; MP_DECLARE_CONST_FUN_OBJ_KW(get_lan_obj); MP_DECLARE_CONST_FUN_OBJ_1(ppp_make_new_obj); diff --git a/ports/esp32/modutime.c b/ports/esp32/modutime.c index 0325bd469391d..cf7178e0b1c2d 100644 --- a/ports/esp32/modutime.c +++ b/ports/esp32/modutime.c @@ -44,7 +44,7 @@ STATIC mp_obj_t time_localtime(size_t n_args, const mp_obj_t *args) { } else { seconds = mp_obj_get_int(args[0]); } - timeutils_seconds_since_2000_to_struct_time(seconds, &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), @@ -85,6 +85,7 @@ MP_DEFINE_CONST_FUN_OBJ_0(time_time_obj, time_time); STATIC const mp_rom_map_elem_t time_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_utime) }, + { MP_ROM_QSTR(MP_QSTR_gmtime), MP_ROM_PTR(&time_localtime_obj) }, { MP_ROM_QSTR(MP_QSTR_localtime), MP_ROM_PTR(&time_localtime_obj) }, { MP_ROM_QSTR(MP_QSTR_mktime), MP_ROM_PTR(&time_mktime_obj) }, { MP_ROM_QSTR(MP_QSTR_time), MP_ROM_PTR(&time_time_obj) }, @@ -96,6 +97,7 @@ STATIC const mp_rom_map_elem_t time_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_ticks_cpu), MP_ROM_PTR(&mp_utime_ticks_cpu_obj) }, { MP_ROM_QSTR(MP_QSTR_ticks_add), MP_ROM_PTR(&mp_utime_ticks_add_obj) }, { MP_ROM_QSTR(MP_QSTR_ticks_diff), MP_ROM_PTR(&mp_utime_ticks_diff_obj) }, + { MP_ROM_QSTR(MP_QSTR_time_ns), MP_ROM_PTR(&mp_utime_time_ns_obj) }, }; STATIC MP_DEFINE_CONST_DICT(time_module_globals, time_module_globals_table); diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index 7c09bdbd9218c..b63d1f89558c0 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -63,6 +63,7 @@ // control over Python builtins #define MICROPY_PY_FUNCTION_ATTRS (1) #define MICROPY_PY_DESCRIPTORS (1) +#define MICROPY_PY_DELATTR_SETATTR (1) #define MICROPY_PY_STR_BYTES_CMP_WARN (1) #define MICROPY_PY_BUILTINS_STR_UNICODE (1) #define MICROPY_PY_BUILTINS_STR_CENTER (1) @@ -145,11 +146,9 @@ #define MICROPY_PY_MACHINE_PIN_MAKE_NEW mp_pin_make_new #define MICROPY_PY_MACHINE_PULSE (1) #define MICROPY_PY_MACHINE_I2C (1) -#define MICROPY_PY_MACHINE_I2C_MAKE_NEW machine_hw_i2c_make_new #define MICROPY_PY_MACHINE_SPI (1) #define MICROPY_PY_MACHINE_SPI_MSB (0) #define MICROPY_PY_MACHINE_SPI_LSB (1) -#define MICROPY_PY_MACHINE_SPI_MAKE_NEW machine_hw_spi_make_new #define MICROPY_HW_ENABLE_SDCARD (1) #define MICROPY_HW_SOFTSPI_MIN_DELAY (0) #define MICROPY_HW_SOFTSPI_MAX_BAUDRATE (ets_get_cpu_frequency() * 1000000 / 200) // roughly @@ -200,7 +199,6 @@ extern const struct _mp_obj_module_t mp_module_onewire; { MP_OBJ_NEW_QSTR(MP_QSTR_machine), (mp_obj_t)&mp_module_machine }, \ { MP_OBJ_NEW_QSTR(MP_QSTR_network), (mp_obj_t)&mp_module_network }, \ { MP_OBJ_NEW_QSTR(MP_QSTR__onewire), (mp_obj_t)&mp_module_onewire }, \ - { MP_OBJ_NEW_QSTR(MP_QSTR_uhashlib), (mp_obj_t)&mp_module_uhashlib }, \ #define MP_STATE_PORT MP_STATE_VM diff --git a/ports/esp32/mphalport.c b/ports/esp32/mphalport.c index 99548ad627be5..ad571bf961886 100644 --- a/ports/esp32/mphalport.c +++ b/ports/esp32/mphalport.c @@ -28,6 +28,7 @@ #include #include +#include #include "freertos/FreeRTOS.h" #include "freertos/task.h" @@ -44,6 +45,7 @@ #include "py/mpstate.h" #include "py/mphal.h" #include "extmod/misc.h" +#include "lib/timeutils/timeutils.h" #include "lib/utils/pyexec.h" #include "mphalport.h" @@ -195,6 +197,14 @@ void mp_hal_delay_us(uint32_t us) { } } +uint64_t mp_hal_time_ns(void) { + struct timeval tv; + gettimeofday(&tv, NULL); + uint64_t ns = tv.tv_sec * 1000000000ULL; + ns += (uint64_t)tv.tv_usec * 1000ULL; + return ns; +} + // Wake up the main task if it is sleeping void mp_hal_wake_main_task_from_isr(void) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; diff --git a/ports/esp32/mphalport.h b/ports/esp32/mphalport.h index 1f78d820a3f17..60cc308d68275 100644 --- a/ports/esp32/mphalport.h +++ b/ports/esp32/mphalport.h @@ -35,8 +35,11 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" -// The core that the MicroPython task(s) are pinned to -#define MP_TASK_COREID (1) +// The core that the MicroPython task(s) are pinned to. +// Until we move to IDF 4.2+, we need NimBLE on core 0, and for synchronisation +// with the ringbuffer and scheduler MP needs to be on the same core. +// See https://github.com/micropython/micropython/issues/5489 +#define MP_TASK_COREID (0) extern TaskHandle_t mp_main_task_handle; diff --git a/ports/esp32/nimble.c b/ports/esp32/mpnimbleport.c similarity index 66% rename from ports/esp32/nimble.c rename to ports/esp32/mpnimbleport.c index e60e08badfbe1..a58fcbdbf49f4 100644 --- a/ports/esp32/nimble.c +++ b/ports/esp32/mpnimbleport.c @@ -30,28 +30,48 @@ #if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE +#define DEBUG_printf(...) // printf("nimble (esp32): " __VA_ARGS__) + #include "esp_nimble_hci.h" #include "nimble/nimble_port.h" #include "nimble/nimble_port_freertos.h" +#include "extmod/nimble/modbluetooth_nimble.h" + STATIC void ble_host_task(void *param) { + DEBUG_printf("ble_host_task\n"); nimble_port_run(); // This function will return only when nimble_port_stop() is executed. nimble_port_freertos_deinit(); } -void mp_bluetooth_nimble_port_preinit(void) { +void mp_bluetooth_nimble_port_hci_init(void) { + DEBUG_printf("mp_bluetooth_nimble_port_hci_init\n"); esp_nimble_hci_and_controller_init(); } -void mp_bluetooth_nimble_port_postinit(void) { +void mp_bluetooth_nimble_port_hci_deinit(void) { + DEBUG_printf("mp_bluetooth_nimble_port_hci_deinit\n"); + + esp_nimble_hci_and_controller_deinit(); +} + +void mp_bluetooth_nimble_port_start(void) { + DEBUG_printf("mp_bluetooth_nimble_port_start\n"); nimble_port_freertos_init(ble_host_task); } -void mp_bluetooth_nimble_port_deinit(void) { +void mp_bluetooth_nimble_port_shutdown(void) { + DEBUG_printf("mp_bluetooth_nimble_port_shutdown\n"); + + // Despite the name, these is an ESP32-specific (no other NimBLE ports have these functions). + // Calls ble_hs_stop() and waits for stack shutdown. nimble_port_stop(); -} -void mp_bluetooth_nimble_port_start(void) { + // Shuts down the event queue. + nimble_port_deinit(); + + // Mark stack as shutdown. + mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF; } #endif diff --git a/ports/esp32/mpthreadport.c b/ports/esp32/mpthreadport.c index f39c99b68d41a..d294c92727bf9 100644 --- a/ports/esp32/mpthreadport.c +++ b/ports/esp32/mpthreadport.c @@ -83,7 +83,7 @@ void mp_thread_gc_others(void) { if (!th->ready) { continue; } - gc_collect_root(th->stack, th->stack_len); // probably not needed + gc_collect_root(th->stack, th->stack_len); } mp_thread_mutex_unlock(&thread_mutex); } @@ -140,17 +140,17 @@ void mp_thread_create_ex(void *(*entry)(void *), void *arg, size_t *stack_size, mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("can't create thread")); } - // adjust the stack_size to provide room to recover from hitting the limit - *stack_size -= 1024; - // add thread to linked list of all threads th->ready = 0; th->arg = arg; th->stack = pxTaskGetStackStart(th->id); - th->stack_len = *stack_size / sizeof(StackType_t); + th->stack_len = *stack_size / sizeof(uintptr_t); th->next = thread; thread = th; + // adjust the stack_size to provide room to recover from hitting the limit + *stack_size -= 1024; + mp_thread_mutex_unlock(&thread_mutex); } diff --git a/ports/esp32/network_lan.c b/ports/esp32/network_lan.c index 1f9a733a7aef1..7a4ad49e01ceb 100644 --- a/ports/esp32/network_lan.c +++ b/ports/esp32/network_lan.c @@ -33,6 +33,7 @@ #include "eth_phy/phy.h" #include "eth_phy/phy_tlk110.h" #include "eth_phy/phy_lan8720.h" +#include "eth_phy/phy_ip101.h" #include "tcpip_adapter.h" #include "modnetwork.h" @@ -123,7 +124,9 @@ STATIC mp_obj_t get_lan(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar mp_raise_ValueError(MP_ERROR_TEXT("invalid phy address")); } - if (args[ARG_phy_type].u_int != PHY_LAN8720 && args[ARG_phy_type].u_int != PHY_TLK110) { + if (args[ARG_phy_type].u_int != PHY_LAN8720 && + args[ARG_phy_type].u_int != PHY_TLK110 && + args[ARG_phy_type].u_int != PHY_IP101) { mp_raise_ValueError(MP_ERROR_TEXT("invalid phy type")); } @@ -145,6 +148,9 @@ STATIC mp_obj_t get_lan(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar case PHY_LAN8720: config = phy_lan8720_default_ethernet_config; break; + case PHY_IP101: + config = phy_ip101_default_ethernet_config; + break; } self->link_func = config.phy_check_link; diff --git a/ports/esp32/network_ppp.c b/ports/esp32/network_ppp.c index c3046c9fc77b9..db2292ca03f7e 100644 --- a/ports/esp32/network_ppp.c +++ b/ports/esp32/network_ppp.c @@ -26,12 +26,11 @@ * THE SOFTWARE. */ -#if !MICROPY_ESP_IDF_4 #include "py/runtime.h" #include "py/mphal.h" #include "py/objtype.h" #include "py/stream.h" -#include "netutils.h" +#include "lib/netutils/netutils.h" #include "modmachine.h" #include "netif/ppp/ppp.h" @@ -284,5 +283,3 @@ const mp_obj_type_t ppp_if_type = { .name = MP_QSTR_PPP, .locals_dict = (mp_obj_dict_t *)&ppp_if_locals_dict, }; - -#endif // !MICROPY_ESP_IDF_4 diff --git a/ports/esp8266/Makefile b/ports/esp8266/Makefile index 38b91a545dea3..02ea05f76e064 100644 --- a/ports/esp8266/Makefile +++ b/ports/esp8266/Makefile @@ -50,7 +50,7 @@ INC += -I$(ESP_SDK)/include # UART for "os" messages. 0 is normal UART as used by MicroPython REPL, # 1 is debug UART (tx only), -1 to disable. -UART_OS = 0 +UART_OS = -1 CFLAGS_XTENSA = -fsingle-precision-constant -Wdouble-promotion \ -D__ets__ -DICACHE_FLASH \ diff --git a/ports/esp8266/boards/GENERIC/manifest.py b/ports/esp8266/boards/GENERIC/manifest.py index 4e65b256f92c8..46f3b837be07f 100644 --- a/ports/esp8266/boards/GENERIC/manifest.py +++ b/ports/esp8266/boards/GENERIC/manifest.py @@ -1,2 +1,21 @@ +# base modules include("$(PORT_DIR)/boards/manifest.py") + +# uasyncio include("$(MPY_DIR)/extmod/uasyncio/manifest.py") + +# drivers +freeze("$(MPY_DIR)/drivers/display", "ssd1306.py") + +# Libraries from micropython-lib, include only if the library directory exists +if os.path.isdir(convert_path("$(MPY_LIB_DIR)")): + # file utilities + freeze("$(MPY_LIB_DIR)/upysh", "upysh.py") + + # requests + freeze("$(MPY_LIB_DIR)/urequests", "urequests.py") + freeze("$(MPY_LIB_DIR)/urllib.urequest", "urllib/urequest.py") + + # umqtt + freeze("$(MPY_LIB_DIR)/umqtt.simple", "umqtt/simple.py") + freeze("$(MPY_LIB_DIR)/umqtt.robust", "umqtt/robust.py") diff --git a/ports/esp8266/boards/manifest_release.py b/ports/esp8266/boards/manifest_release.py deleted file mode 100644 index 5a3194ae9bd45..0000000000000 --- a/ports/esp8266/boards/manifest_release.py +++ /dev/null @@ -1,23 +0,0 @@ -include("manifest.py") - -# drivers -freeze("$(MPY_DIR)/drivers/display", "ssd1306.py") - -# file utilities -freeze("$(MPY_LIB_DIR)/upysh", "upysh.py") - -# requests -freeze("$(MPY_LIB_DIR)/urequests", "urequests.py") -freeze("$(MPY_LIB_DIR)/urllib.urequest", "urllib/urequest.py") - -# umqtt with examples -freeze("$(MPY_LIB_DIR)/umqtt.simple", "umqtt/simple.py") -freeze("$(MPY_LIB_DIR)/umqtt.robust", "umqtt/robust.py") -freeze("$(MPY_LIB_DIR)/umqtt.simple", "example_pub_button.py") -freeze("$(MPY_LIB_DIR)/umqtt.simple", "example_sub_led.py") - -# HTTP examples -freeze("$(MPY_DIR)/examples/network", "http_client.py") -freeze("$(MPY_DIR)/examples/network", "http_client_ssl.py") -freeze("$(MPY_DIR)/examples/network", "http_server.py") -freeze("$(MPY_DIR)/examples/network", "http_server_ssl.py") diff --git a/ports/esp8266/esp_mphal.c b/ports/esp8266/esp_mphal.c index c467ab72db7ec..54f9611e56233 100644 --- a/ports/esp8266/esp_mphal.c +++ b/ports/esp8266/esp_mphal.c @@ -138,6 +138,10 @@ void MP_FASTCODE(mp_hal_delay_ms)(uint32_t delay) { mp_hal_delay_us(delay * 1000); } +uint64_t mp_hal_time_ns(void) { + return pyb_rtc_get_us_since_epoch() * 1000ULL; +} + void ets_event_poll(void) { ets_loop_iter(); mp_handle_pending(true); diff --git a/ports/esp8266/esppwm.c b/ports/esp8266/esppwm.c index 6116a468957fa..d5bcea9acd8cc 100644 --- a/ports/esp8266/esppwm.c +++ b/ports/esp8266/esppwm.c @@ -57,6 +57,7 @@ STATIC uint8_t pwm_timer_down = 1; STATIC uint8_t pwm_current_channel = 0; STATIC uint16_t pwm_gpio = 0; STATIC uint8_t pwm_channel_num = 0; +STATIC volatile uint8_t pwm_toggle_request = 0; // XXX: 0xffffffff/(80000000/16)=35A #define US_TO_RTC_TIMER_TICKS(t) \ @@ -126,6 +127,9 @@ pwm_start(void) { LOCK_PWM(critical); // enter critical + // if a toggle is pending, we reset it since we're changing the settings again + pwm_toggle_request = 0; + struct pwm_single_param *local_single = pwm_single_toggle[pwm_toggle ^ 0x01]; uint8 *local_channel = &pwm_channel_toggle[pwm_toggle ^ 0x01]; @@ -188,14 +192,14 @@ pwm_start(void) { // start gpio_output_set(local_single[0].gpio_set, local_single[0].gpio_clear, pwm_gpio, 0); + // do the first toggle because timer has to have a valid set to do it's job + pwm_toggle ^= 0x01; + pwm_timer_down = 0; RTC_REG_WRITE(FRC1_LOAD_ADDRESS, local_single[0].h_time); - } - - if (pwm_toggle == 1) { - pwm_toggle = 0; } else { - pwm_toggle = 1; + // request pwm_tim1_intr_handler to swap the timing buffers + pwm_toggle_request = 1; } UNLOCK_PWM(critical); // leave critical @@ -297,12 +301,18 @@ pwm_get_freq(uint8 channel) { STATIC void ICACHE_RAM_ATTR pwm_tim1_intr_handler(void *dummy) { (void)dummy; - uint8 local_toggle = pwm_toggle; // pwm_toggle may change outside + RTC_CLR_REG_MASK(FRC1_INT_ADDRESS, FRC1_INT_CLR_MASK); if (pwm_current_channel >= (*pwm_channel - 1)) { // *pwm_channel may change outside - pwm_single = pwm_single_toggle[local_toggle]; - pwm_channel = &pwm_channel_toggle[local_toggle]; + + if (pwm_toggle_request != 0) { + pwm_toggle ^= 1; + pwm_toggle_request = 0; + } + + pwm_single = pwm_single_toggle[pwm_toggle]; + pwm_channel = &pwm_channel_toggle[pwm_toggle]; gpio_output_set(pwm_single[*pwm_channel - 1].gpio_set, pwm_single[*pwm_channel - 1].gpio_clear, diff --git a/ports/esp8266/fatfs_port.c b/ports/esp8266/fatfs_port.c index 8cef0acec3c62..bbd10519359a0 100644 --- a/ports/esp8266/fatfs_port.c +++ b/ports/esp8266/fatfs_port.c @@ -33,10 +33,10 @@ DWORD get_fattime(void) { // TODO: Optimize division (there's no HW division support on ESP8266, // so it's expensive). - uint32_t secs = (uint32_t)(pyb_rtc_get_us_since_2000() / 1000000); + uint32_t secs = (uint32_t)(pyb_rtc_get_us_since_epoch() / 1000000); timeutils_struct_time_t tm; - timeutils_seconds_since_2000_to_struct_time(secs, &tm); + timeutils_seconds_since_epoch_to_struct_time(secs, &tm); return ((DWORD)(tm.tm_year - 1980) << 25) | ((DWORD)tm.tm_mon << 21) | ((DWORD)tm.tm_mday << 16) | ((DWORD)tm.tm_hour << 11) | ((DWORD)tm.tm_min << 5) | ((DWORD)tm.tm_sec >> 1); diff --git a/ports/esp8266/machine_hspi.c b/ports/esp8266/machine_hspi.c index 7319194d763d3..ff3ba17255358 100644 --- a/ports/esp8266/machine_hspi.c +++ b/ports/esp8266/machine_hspi.c @@ -151,6 +151,8 @@ STATIC void machine_hspi_init(mp_obj_base_t *self_in, size_t n_args, const mp_ob } mp_obj_t machine_hspi_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + MP_MACHINE_SPI_CHECK_FOR_LEGACY_SOFTSPI_CONSTRUCTION(n_args, n_kw, args); + // args[0] holds the id of the peripheral if (args[0] != MP_OBJ_NEW_SMALL_INT(1)) { // FlashROM is on SPI0, so far we don't support its usage @@ -178,7 +180,7 @@ const mp_obj_type_t machine_hspi_type = { { &mp_type_type }, .name = MP_QSTR_HSPI, .print = machine_hspi_print, - .make_new = mp_machine_spi_make_new, // delegate to master constructor + .make_new = machine_hspi_make_new, .protocol = &machine_hspi_p, .locals_dict = (mp_obj_dict_t *)&mp_machine_spi_locals_dict, }; diff --git a/ports/esp8266/machine_rtc.c b/ports/esp8266/machine_rtc.c index 1aa73f9b317f8..e7b750fd92c8d 100644 --- a/ports/esp8266/machine_rtc.c +++ b/ports/esp8266/machine_rtc.c @@ -84,7 +84,7 @@ STATIC mp_obj_t pyb_rtc_make_new(const mp_obj_type_t *type, size_t n_args, size_ return (mp_obj_t)&pyb_rtc_obj; } -void pyb_rtc_set_us_since_2000(uint64_t nowus) { +void pyb_rtc_set_us_since_epoch(uint64_t nowus) { uint32_t cal = system_rtc_clock_cali_proc(); // Save RTC ticks for overflow detection. rtc_last_ticks = system_get_rtc_time(); @@ -96,7 +96,7 @@ void pyb_rtc_set_us_since_2000(uint64_t nowus) { system_rtc_mem_write(MEM_DELTA_ADDR, &delta, sizeof(delta)); }; -uint64_t pyb_rtc_get_us_since_2000() { +uint64_t pyb_rtc_get_us_since_epoch() { uint32_t cal; int64_t delta; uint32_t rtc_ticks; @@ -120,17 +120,17 @@ uint64_t pyb_rtc_get_us_since_2000() { void rtc_prepare_deepsleep(uint64_t sleep_us) { // RTC time will reset at wake up. Let's be preared for this. - int64_t delta = pyb_rtc_get_us_since_2000() + sleep_us; + int64_t delta = pyb_rtc_get_us_since_epoch() + sleep_us; system_rtc_mem_write(MEM_DELTA_ADDR, &delta, sizeof(delta)); } STATIC mp_obj_t pyb_rtc_datetime(size_t n_args, const mp_obj_t *args) { if (n_args == 1) { // Get time - uint64_t msecs = pyb_rtc_get_us_since_2000() / 1000; + uint64_t msecs = pyb_rtc_get_us_since_epoch() / 1000; timeutils_struct_time_t tm; - timeutils_seconds_since_2000_to_struct_time(msecs / 1000, &tm); + timeutils_seconds_since_epoch_to_struct_time(msecs / 1000, &tm); mp_obj_t tuple[8] = { mp_obj_new_int(tm.tm_year), @@ -149,8 +149,8 @@ STATIC mp_obj_t pyb_rtc_datetime(size_t n_args, const mp_obj_t *args) { mp_obj_t *items; mp_obj_get_array_fixed_n(args[1], 8, &items); - pyb_rtc_set_us_since_2000( - ((uint64_t)timeutils_seconds_since_2000( + pyb_rtc_set_us_since_epoch( + ((uint64_t)timeutils_seconds_since_epoch( mp_obj_get_int(items[0]), mp_obj_get_int(items[1]), mp_obj_get_int(items[2]), @@ -209,7 +209,7 @@ STATIC mp_obj_t pyb_rtc_alarm(mp_obj_t self_in, mp_obj_t alarm_id, mp_obj_t time } // set expiry time (in microseconds) - pyb_rtc_alarm0_expiry = pyb_rtc_get_us_since_2000() + (uint64_t)mp_obj_get_int(time_in) * 1000; + pyb_rtc_alarm0_expiry = pyb_rtc_get_us_since_epoch() + (uint64_t)mp_obj_get_int(time_in) * 1000; return mp_const_none; @@ -222,7 +222,7 @@ STATIC mp_obj_t pyb_rtc_alarm_left(size_t n_args, const mp_obj_t *args) { mp_raise_ValueError(MP_ERROR_TEXT("invalid alarm")); } - uint64_t now = pyb_rtc_get_us_since_2000(); + uint64_t now = pyb_rtc_get_us_since_epoch(); if (pyb_rtc_alarm0_expiry <= now) { return MP_OBJ_NEW_SMALL_INT(0); } else { diff --git a/ports/esp8266/modmachine.c b/ports/esp8266/modmachine.c index bc838b420645d..86c1d728d5369 100644 --- a/ports/esp8266/modmachine.c +++ b/ports/esp8266/modmachine.c @@ -39,6 +39,7 @@ #include "extmod/machine_signal.h" #include "extmod/machine_pulse.h" #include "extmod/machine_i2c.h" +#include "extmod/machine_spi.h" #include "modmachine.h" #include "xtirq.h" @@ -132,7 +133,7 @@ STATIC mp_obj_t machine_deepsleep(size_t n_args, const mp_obj_t *args) { // see if RTC.ALARM0 should wake the device if (pyb_rtc_alarm0_wake & MACHINE_WAKE_DEEPSLEEP) { - uint64_t t = pyb_rtc_get_us_since_2000(); + uint64_t t = pyb_rtc_get_us_since_epoch(); if (pyb_rtc_alarm0_expiry <= t) { sleep_us = 1; // alarm already expired so wake immediately } else { @@ -421,10 +422,12 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_ADC), MP_ROM_PTR(&machine_adc_type) }, { MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&pyb_uart_type) }, #if MICROPY_PY_MACHINE_I2C - { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_i2c_type) }, + { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, + { MP_ROM_QSTR(MP_QSTR_SoftI2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, #endif #if MICROPY_PY_MACHINE_SPI { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_hspi_type) }, + { MP_ROM_QSTR(MP_QSTR_SoftSPI), MP_ROM_PTR(&mp_machine_soft_spi_type) }, #endif // wake abilities diff --git a/ports/esp8266/modmachine.h b/ports/esp8266/modmachine.h index f5cfc0fa2cd52..be4debd335a64 100644 --- a/ports/esp8266/modmachine.h +++ b/ports/esp8266/modmachine.h @@ -32,8 +32,8 @@ void pin_set(uint pin, int value); extern uint32_t pyb_rtc_alarm0_wake; extern uint64_t pyb_rtc_alarm0_expiry; -void pyb_rtc_set_us_since_2000(uint64_t nowus); -uint64_t pyb_rtc_get_us_since_2000(); +void pyb_rtc_set_us_since_epoch(uint64_t nowus); +uint64_t pyb_rtc_get_us_since_epoch(); void rtc_prepare_deepsleep(uint64_t sleep_us); #endif // MICROPY_INCLUDED_ESP8266_MODMACHINE_H diff --git a/ports/esp8266/modules/ntptime.py b/ports/esp8266/modules/ntptime.py index 92ae6ab077cfb..dd07e46f1d3b4 100644 --- a/ports/esp8266/modules/ntptime.py +++ b/ports/esp8266/modules/ntptime.py @@ -29,12 +29,11 @@ def time(): return val - NTP_DELTA -# There's currently no timezone support in MicroPython, so -# utime.localtime() will return UTC time (as if it was .gmtime()) +# There's currently no timezone support in MicroPython, and the RTC is set in UTC time. def settime(): t = time() import machine import utime - tm = utime.localtime(t) + tm = utime.gmtime(t) machine.RTC().datetime((tm[0], tm[1], tm[2], tm[6] + 1, tm[3], tm[4], tm[5], 0)) diff --git a/ports/esp8266/modutime.c b/ports/esp8266/modutime.c index 9e924121bbc78..bcfbf7baf2c66 100644 --- a/ports/esp8266/modutime.c +++ b/ports/esp8266/modutime.c @@ -58,11 +58,11 @@ STATIC mp_obj_t time_localtime(size_t n_args, const mp_obj_t *args) { timeutils_struct_time_t tm; mp_int_t seconds; if (n_args == 0 || args[0] == mp_const_none) { - seconds = pyb_rtc_get_us_since_2000() / 1000 / 1000; + seconds = pyb_rtc_get_us_since_epoch() / 1000 / 1000; } else { seconds = mp_obj_get_int(args[0]); } - timeutils_seconds_since_2000_to_struct_time(seconds, &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), @@ -98,16 +98,17 @@ STATIC mp_obj_t time_mktime(mp_obj_t tuple) { MP_DEFINE_CONST_FUN_OBJ_1(time_mktime_obj, time_mktime); /// \function time() -/// Returns the number of seconds, as an integer, since 1/1/2000. +/// Returns the number of seconds, as an integer, since the Epoch. STATIC mp_obj_t time_time(void) { // get date and time - return mp_obj_new_int(pyb_rtc_get_us_since_2000() / 1000 / 1000); + return mp_obj_new_int(pyb_rtc_get_us_since_epoch() / 1000 / 1000); } MP_DEFINE_CONST_FUN_OBJ_0(time_time_obj, time_time); STATIC const mp_rom_map_elem_t time_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_utime) }, + { MP_ROM_QSTR(MP_QSTR_gmtime), MP_ROM_PTR(&time_localtime_obj) }, { MP_ROM_QSTR(MP_QSTR_localtime), MP_ROM_PTR(&time_localtime_obj) }, { MP_ROM_QSTR(MP_QSTR_mktime), MP_ROM_PTR(&time_mktime_obj) }, { MP_ROM_QSTR(MP_QSTR_sleep), MP_ROM_PTR(&mp_utime_sleep_obj) }, @@ -119,6 +120,7 @@ STATIC const mp_rom_map_elem_t time_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_ticks_add), MP_ROM_PTR(&mp_utime_ticks_add_obj) }, { MP_ROM_QSTR(MP_QSTR_ticks_diff), MP_ROM_PTR(&mp_utime_ticks_diff_obj) }, { MP_ROM_QSTR(MP_QSTR_time), MP_ROM_PTR(&time_time_obj) }, + { MP_ROM_QSTR(MP_QSTR_time_ns), MP_ROM_PTR(&mp_utime_time_ns_obj) }, }; STATIC MP_DEFINE_CONST_DICT(time_module_globals, time_module_globals_table); diff --git a/ports/esp8266/mpconfigport.h b/ports/esp8266/mpconfigport.h index 5344a98d6a548..974310f84da6e 100644 --- a/ports/esp8266/mpconfigport.h +++ b/ports/esp8266/mpconfigport.h @@ -80,7 +80,6 @@ #define MICROPY_PY_MACHINE_PULSE (1) #define MICROPY_PY_MACHINE_I2C (1) #define MICROPY_PY_MACHINE_SPI (1) -#define MICROPY_PY_MACHINE_SPI_MAKE_NEW machine_hspi_make_new #define MICROPY_PY_UWEBSOCKET (1) #define MICROPY_PY_WEBREPL (1) #define MICROPY_PY_WEBREPL_DELAY (20) diff --git a/ports/gprs_a9/Makefile b/ports/gprs_a9/Makefile index 72110df05be70..06f8e49b87eea 100644 --- a/ports/gprs_a9/Makefile +++ b/ports/gprs_a9/Makefile @@ -28,15 +28,20 @@ QSTR_DEFS = qstrdefsport.h ############################################# # MicroPython feature configurations + MICROPY_PY_USSL = 1 -MICROPY_SSL_AXTLS = 1 -AXTLS_DEFS_EXTRA = -Dabort=abort_ -include time.h -DRT_MAX_PLAIN_LENGTH=1024 -DRT_EXTRA=4096 +MICROPY_SSL_MBEDTLS = 1 +# MICROPY_SSL_AXTLS = 1 +# AXTLS_DEFS_EXTRA = -Dabort=abort_ -include time.h -DRT_MAX_PLAIN_LENGTH=1024 -DRT_EXTRA=4096 FROZEN_MANIFEST ?= boards/manifest.py # include py core make definitions include $(TOP)/py/py.mk +# GIT_SUBMODULES = lib/mbedtls +GIT_SUBMODULES = lib/mbedtls + ############################################# CROSS_COMPILE = $(CSDTK_PATH)/bin/mips-elf- @@ -164,6 +169,16 @@ DRIVERS_SRC_C = $(addprefix drivers/,\ SRC_S = \ gchelper.s + +ifeq ($(MICROPY_SSL_MBEDTLS),1) +CFLAGS_MOD += -DMBEDTLS_CONFIG_FILE='"mbedtls/mbedtls_config.h"' +SRC_MOD += mbedtls/mbedtls_port.c +# replace mbedtls' error.c by ours +SRC_MOD := $(filter-out %/mbedtls/library/error.c, $(SRC_MOD)) +LIB_SRC_C += lib/mbedtls_errors/mp_mbedtls_errors.c +# LIB_SRC_C += lib/GPRS_C_SDK/include/api_inc/api +endif + OBJ_MP = OBJ_MP += $(PY_O) OBJ_MP += $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) diff --git a/ports/gprs_a9/main.c b/ports/gprs_a9/main.c index bec8b8b79eb55..3d9f86ef9dc8b 100644 --- a/ports/gprs_a9/main.c +++ b/ports/gprs_a9/main.c @@ -29,6 +29,7 @@ #include #include #include +#include #include "py/compile.h" #include "py/runtime.h" @@ -59,6 +60,8 @@ #include "modgps.h" #include "modmachine.h" +#define SIZE_MAX std::numeric_limits::max() + #define AppMain_TASK_STACK_SIZE (2048 * 2) #define AppMain_TASK_PRIORITY 0 #define MICROPYTHON_TASK_STACK_SIZE (2048 * 4) diff --git a/ports/gprs_a9/mbedtls/mbedtls_config.h b/ports/gprs_a9/mbedtls/mbedtls_config.h new file mode 100644 index 0000000000000..eeed1a566a2c2 --- /dev/null +++ b/ports/gprs_a9/mbedtls/mbedtls_config.h @@ -0,0 +1,100 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018-2019 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef MICROPY_INCLUDED_MBEDTLS_CONFIG_H +#define MICROPY_INCLUDED_MBEDTLS_CONFIG_H + +#define SIZE_MAX UINT32_MAX +// Set mbedtls configuration +#define MBEDTLS_PLATFORM_MEMORY +#define MBEDTLS_PLATFORM_NO_STD_FUNCTIONS +#define MBEDTLS_DEPRECATED_REMOVED +#define MBEDTLS_ENTROPY_HARDWARE_ALT +#define MBEDTLS_AES_ROM_TABLES +#define MBEDTLS_CIPHER_MODE_CBC +#define MBEDTLS_ECP_DP_SECP192R1_ENABLED +#define MBEDTLS_ECP_DP_SECP224R1_ENABLED +#define MBEDTLS_ECP_DP_SECP256R1_ENABLED +#define MBEDTLS_ECP_DP_SECP384R1_ENABLED +#define MBEDTLS_ECP_DP_SECP521R1_ENABLED +#define MBEDTLS_ECP_DP_SECP192K1_ENABLED +#define MBEDTLS_ECP_DP_SECP224K1_ENABLED +#define MBEDTLS_ECP_DP_SECP256K1_ENABLED +#define MBEDTLS_ECP_DP_BP256R1_ENABLED +#define MBEDTLS_ECP_DP_BP384R1_ENABLED +#define MBEDTLS_ECP_DP_BP512R1_ENABLED +#define MBEDTLS_ECP_DP_CURVE25519_ENABLED +#define MBEDTLS_KEY_EXCHANGE_RSA_ENABLED +#define MBEDTLS_NO_PLATFORM_ENTROPY +#define MBEDTLS_PKCS1_V15 +#define MBEDTLS_SHA256_SMALLER +#define MBEDTLS_SSL_PROTO_TLS1 +#define MBEDTLS_SSL_PROTO_TLS1_1 +#define MBEDTLS_SSL_PROTO_TLS1_2 +#define MBEDTLS_SSL_SERVER_NAME_INDICATION + +// Use a smaller output buffer to reduce size of SSL context +#define MBEDTLS_SSL_MAX_CONTENT_LEN (16384) +#define MBEDTLS_SSL_IN_CONTENT_LEN (MBEDTLS_SSL_MAX_CONTENT_LEN) +#define MBEDTLS_SSL_OUT_CONTENT_LEN (4096) + +// Enable mbedtls modules +#define MBEDTLS_AES_C +#define MBEDTLS_ASN1_PARSE_C +#define MBEDTLS_BIGNUM_C +#define MBEDTLS_CIPHER_C +#define MBEDTLS_CTR_DRBG_C +//#define MBEDTLS_ECP_C +#define MBEDTLS_ENTROPY_C +#define MBEDTLS_ERROR_C +#define MBEDTLS_MD_C +#define MBEDTLS_MD5_C +#define MBEDTLS_OID_C +#define MBEDTLS_PKCS5_C +#define MBEDTLS_PK_C +#define MBEDTLS_PK_PARSE_C +#define MBEDTLS_PLATFORM_C +#define MBEDTLS_RSA_C +#define MBEDTLS_SHA1_C +#define MBEDTLS_SHA256_C +#define MBEDTLS_SHA512_C +#define MBEDTLS_SSL_CLI_C +#define MBEDTLS_SSL_SRV_C +#define MBEDTLS_SSL_TLS_C +#define MBEDTLS_X509_CRT_PARSE_C +#define MBEDTLS_X509_USE_C + +// Memory allocation hooks +#include +#include +void *m_calloc_mbedtls(size_t nmemb, size_t size); +void m_free_mbedtls(void *ptr); +#define MBEDTLS_PLATFORM_STD_CALLOC m_calloc_mbedtls +#define MBEDTLS_PLATFORM_STD_FREE m_free_mbedtls +#define MBEDTLS_PLATFORM_SNPRINTF_MACRO snprintf + +#include "mbedtls/check_config.h" + +#endif /* MICROPY_INCLUDED_MBEDTLS_CONFIG_H */ \ No newline at end of file diff --git a/ports/gprs_a9/mbedtls/mbedtls_port.c b/ports/gprs_a9/mbedtls/mbedtls_port.c new file mode 100644 index 0000000000000..bc628b6fe75e8 --- /dev/null +++ b/ports/gprs_a9/mbedtls/mbedtls_port.c @@ -0,0 +1,96 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/runtime.h" +#include "py/gc.h" +#include "rng.h" +#include "mbedtls_config.h" + +#define DEBUG (0) + +#if DEBUG +static size_t count_links(uint32_t *nb) { + void **p = MP_STATE_PORT(mbedtls_memory); + size_t n = 0; + *nb = 0; + while (p != NULL) { + ++n; + *nb += gc_nbytes(p); + p = (void**)p[1]; + } + return n; +} +#endif + +void *m_calloc_mbedtls(size_t nmemb, size_t size) { + void **ptr = m_malloc0(nmemb * size + 2 * sizeof(uintptr_t)); + #if DEBUG + uint32_t nb; + size_t n = count_links(&nb); + printf("mbed_alloc(%u, %u) -> (%u;%u) %p\n", nmemb, size, n, (uint)nb, ptr); + #endif + if (MP_STATE_PORT(mbedtls_memory) != NULL) { + MP_STATE_PORT(mbedtls_memory)[0] = ptr; + } + ptr[0] = NULL; + ptr[1] = MP_STATE_PORT(mbedtls_memory); + MP_STATE_PORT(mbedtls_memory) = ptr; + return &ptr[2]; +} + +void m_free_mbedtls(void *ptr_in) { + void **ptr = &((void**)ptr_in)[-2]; + #if DEBUG + uint32_t nb; + size_t n = count_links(&nb); + printf("mbed_free(%p, [%p, %p], nbytes=%u, links=%u;%u)\n", ptr, ptr[0], ptr[1], gc_nbytes(ptr), n, (uint)nb); + #endif + if (ptr[1] != NULL) { + ((void**)ptr[1])[0] = ptr[0]; + } + if (ptr[0] != NULL) { + ((void**)ptr[0])[1] = ptr[1]; + } else { + MP_STATE_PORT(mbedtls_memory) = ptr[1]; + } + m_free(ptr); +} + +int mbedtls_hardware_poll(void *data, unsigned char *output, size_t len, size_t *olen) { + uint32_t val; + int n = 0; + *olen = len; + while (len--) { + if (!n) { + val = rng_get(); + n = 4; + } + *output++ = val; + val >>= 8; + --n; + } + return 0; +} \ No newline at end of file diff --git a/ports/gprs_a9/mpconfigport.h b/ports/gprs_a9/mpconfigport.h index d3d0cbb24b324..c0b2e7f59d2bb 100644 --- a/ports/gprs_a9/mpconfigport.h +++ b/ports/gprs_a9/mpconfigport.h @@ -8,8 +8,6 @@ #include #include -#include "api_sys.h" - // options to control how MicroPython is built @@ -65,8 +63,6 @@ #define MICROPY_ENABLE_SCHEDULER (1) #define MICROPY_SCHEDULER_DEPTH (8) #define MICROPY_COMP_CONST (1) -#define MICROPY_BEGIN_ATOMIC_SECTION() SYS_EnterCriticalSection() -#define MICROPY_END_ATOMIC_SECTION(state) SYS_ExitCriticalSection(state) // MCU definition #define MP_ENDIANNESS_LITTLE (1) @@ -247,8 +243,15 @@ typedef long mp_off_t; #define MP_STATE_PORT MP_STATE_VM +#if MICROPY_SSL_MBEDTLS +#define MICROPY_PORT_ROOT_POINTER_MBEDTLS void **mbedtls_memory; +#else +#define MICROPY_PORT_ROOT_POINTER_MBEDTLS +#endif + #define MICROPY_PORT_ROOT_POINTERS \ const char *readline_hist[8]; \ + MICROPY_PORT_ROOT_POINTER_MBEDTLS \ byte *uart_rxbuf[2]; #include "csdk_config.h" diff --git a/ports/mimxrt/Makefile b/ports/mimxrt/Makefile index cbd2b8595791a..85e77b978e9f0 100644 --- a/ports/mimxrt/Makefile +++ b/ports/mimxrt/Makefile @@ -40,7 +40,7 @@ INC += -I$(TOP)/lib/tinyusb/hw INC += -I$(TOP)/lib/tinyusb/hw/bsp/teensy_40 CFLAGS_MCU = -mtune=cortex-m7 -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-d16 -CFLAGS = $(INC) -Wall -Werror -Wdouble-promotion -Wfloat-conversion -std=c99 -nostdlib -mthumb $(CFLAGS_MCU) +CFLAGS += $(INC) -Wall -Werror -Wdouble-promotion -Wfloat-conversion -std=c99 -nostdlib -mthumb $(CFLAGS_MCU) CFLAGS += -DCPU_$(MCU_SERIES) -DCPU_$(MCU_VARIANT) CFLAGS += -DXIP_EXTERNAL_FLASH=1 \ -DXIP_BOOT_HEADER_ENABLE=1 \ @@ -88,9 +88,13 @@ SRC_TINYUSB_IMX_C += \ SRC_C = \ main.c \ + led.c \ + pin.c \ tusb_port.c \ board_init.c \ $(BOARD_DIR)/flash_config.c \ + $(BOARD_DIR)/pins.c \ + machine_led.c \ modutime.c \ modmachine.c \ mphalport.c \ @@ -108,7 +112,12 @@ SRC_SS = $(MCU_DIR)/gcc/startup_$(MCU_SERIES).S SRC_S = lib/utils/gchelper_m3.s \ # List of sources for qstr extraction -SRC_QSTR += modutime.c modmachine.c +SRC_QSTR += \ + machine_led.c \ + modutime.c \ + modmachine.c \ + pin.c \ + $(BOARD_DIR)/pins.c \ OBJ += $(PY_O) OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) diff --git a/ports/mimxrt/board_init.c b/ports/mimxrt/board_init.c index a3c27b045bba5..6f5235d3b635b 100644 --- a/ports/mimxrt/board_init.c +++ b/ports/mimxrt/board_init.c @@ -45,8 +45,6 @@ volatile uint32_t systick_ms = 0; const uint8_t dcd_data[] = { 0x00 }; -void board_led_write(bool state); - void board_init(void) { // Init clock BOARD_BootClockRUN(); @@ -58,14 +56,6 @@ void board_init(void) { // 1ms tick timer SysTick_Config(SystemCoreClock / 1000); - // LED - IOMUXC_SetPinMux(MICROPY_HW_LED_PINMUX, 0U); - IOMUXC_SetPinConfig(MICROPY_HW_LED_PINMUX, 0x10B0U); - - gpio_pin_config_t led_config = { kGPIO_DigitalOutput, 0, kGPIO_NoIntmode }; - GPIO_PinInit(MICROPY_HW_LED_PORT, MICROPY_HW_LED_PIN, &led_config); - board_led_write(true); - // ------------- USB0 ------------- // // Clock @@ -95,10 +85,6 @@ void board_init(void) { // CLOCK_EnableUsbhs1Clock(kCLOCK_Usb480M, 480000000U); } -void board_led_write(bool state) { - GPIO_PinWrite(MICROPY_HW_LED_PORT, MICROPY_HW_LED_PIN, state ? LED_STATE_ON : (1 - LED_STATE_ON)); -} - void SysTick_Handler(void) { systick_ms++; } diff --git a/ports/mimxrt/boards/MIMXRT1010_EVK/mpconfigboard.h b/ports/mimxrt/boards/MIMXRT1010_EVK/mpconfigboard.h index 02d34fc42f052..eeddd0e0213fb 100644 --- a/ports/mimxrt/boards/MIMXRT1010_EVK/mpconfigboard.h +++ b/ports/mimxrt/boards/MIMXRT1010_EVK/mpconfigboard.h @@ -3,6 +3,7 @@ #define BOARD_FLASH_SIZE (16 * 1024 * 1024) -#define MICROPY_HW_LED_PINMUX IOMUXC_GPIO_11_GPIOMUX_IO11 -#define MICROPY_HW_LED_PORT GPIO1 -#define MICROPY_HW_LED_PIN 11 +// i.MX RT1010 EVK has 1 board LED +#define MICROPY_HW_LED1_PIN (GPIO_11) +#define MICROPY_HW_LED_ON(pin) (mp_hal_pin_high(pin)) +#define MICROPY_HW_LED_OFF(pin) (mp_hal_pin_low(pin)) diff --git a/ports/mimxrt/boards/MIMXRT1010_EVK/pins.c b/ports/mimxrt/boards/MIMXRT1010_EVK/pins.c new file mode 100644 index 0000000000000..e0397e09edb21 --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1010_EVK/pins.c @@ -0,0 +1,33 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Philipp Ebensberger + * + * 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 "pin.h" + +static pin_af_obj_t GPIO_11_af[] = { + PIN_AF(GPIOMUX_IO11, PIN_AF_MODE_ALT5, GPIO1, 0x10B0U), +}; + +pin_obj_t GPIO_11 = PIN(GPIO_11, GPIO1, 5, GPIO_11_af); diff --git a/ports/mimxrt/boards/MIMXRT1010_EVK/pins.h b/ports/mimxrt/boards/MIMXRT1010_EVK/pins.h new file mode 100644 index 0000000000000..99534932a19d5 --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1010_EVK/pins.h @@ -0,0 +1,30 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Philipp Ebensberger + * + * 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. + */ + +// NOTE: pins.h shall only be included in in pin.h +// hence no include guards are needed since they will be provided by pin.h + +extern pin_obj_t GPIO_11; diff --git a/ports/mimxrt/boards/MIMXRT1011.ld b/ports/mimxrt/boards/MIMXRT1011.ld index 6b59649fcaf7e..0512c48a7f2d9 100644 --- a/ports/mimxrt/boards/MIMXRT1011.ld +++ b/ports/mimxrt/boards/MIMXRT1011.ld @@ -3,6 +3,9 @@ __stack_size__ = 0x6000; _estack = __StackTop; _sstack = __StackLimit; +/* Do not use the traditional C heap. */ +__heap_size__ = 0; + /* Use second OCRAM bank for GC heap. */ _gc_heap_start = ORIGIN(m_data2); _gc_heap_end = ORIGIN(m_data2) + LENGTH(m_data2); diff --git a/ports/mimxrt/boards/MIMXRT1020_EVK/evkmimxrt1020_flexspi_nor_config.h b/ports/mimxrt/boards/MIMXRT1020_EVK/evkmimxrt1020_flexspi_nor_config.h new file mode 100644 index 0000000000000..c0c6ea243672c --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1020_EVK/evkmimxrt1020_flexspi_nor_config.h @@ -0,0 +1,270 @@ +/* + * Copyright 2019 NXP. + * All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +// Based on tinyusb/hw/bsp/teensy_40/evkmimxrt1020_flexspi_nor_config.h + +#ifndef __EVKMIMXRT1020_FLEXSPI_NOR_CONFIG__ +#define __EVKMIMXRT1020_FLEXSPI_NOR_CONFIG__ + +#include +#include +#include "fsl_common.h" + +/*! @name Driver version */ +/*@{*/ +/*! @brief XIP_BOARD driver version 2.0.0. */ +#define FSL_XIP_BOARD_DRIVER_VERSION (MAKE_VERSION(2, 0, 0)) +/*@}*/ + +/* FLEXSPI memory config block related defintions */ +#define FLEXSPI_CFG_BLK_TAG (0x42464346UL) // ascii "FCFB" Big Endian +#define FLEXSPI_CFG_BLK_VERSION (0x56010400UL) // V1.4.0 +#define FLEXSPI_CFG_BLK_SIZE (512) + +/* FLEXSPI Feature related definitions */ +#define FLEXSPI_FEATURE_HAS_PARALLEL_MODE 1 + +/* Lookup table related defintions */ +#define CMD_INDEX_READ 0 +#define CMD_INDEX_READSTATUS 1 +#define CMD_INDEX_WRITEENABLE 2 +#define CMD_INDEX_WRITE 4 + +#define CMD_LUT_SEQ_IDX_READ 0 +#define CMD_LUT_SEQ_IDX_READSTATUS 1 +#define CMD_LUT_SEQ_IDX_WRITEENABLE 3 +#define CMD_LUT_SEQ_IDX_WRITE 9 + +#define CMD_SDR 0x01 +#define CMD_DDR 0x21 +#define RADDR_SDR 0x02 +#define RADDR_DDR 0x22 +#define CADDR_SDR 0x03 +#define CADDR_DDR 0x23 +#define MODE1_SDR 0x04 +#define MODE1_DDR 0x24 +#define MODE2_SDR 0x05 +#define MODE2_DDR 0x25 +#define MODE4_SDR 0x06 +#define MODE4_DDR 0x26 +#define MODE8_SDR 0x07 +#define MODE8_DDR 0x27 +#define WRITE_SDR 0x08 +#define WRITE_DDR 0x28 +#define READ_SDR 0x09 +#define READ_DDR 0x29 +#define LEARN_SDR 0x0A +#define LEARN_DDR 0x2A +#define DATSZ_SDR 0x0B +#define DATSZ_DDR 0x2B +#define DUMMY_SDR 0x0C +#define DUMMY_DDR 0x2C +#define DUMMY_RWDS_SDR 0x0D +#define DUMMY_RWDS_DDR 0x2D +#define JMP_ON_CS 0x1F +#define STOP 0 + +#define FLEXSPI_1PAD 0 +#define FLEXSPI_2PAD 1 +#define FLEXSPI_4PAD 2 +#define FLEXSPI_8PAD 3 + +#define FLEXSPI_LUT_SEQ(cmd0, pad0, op0, cmd1, pad1, op1) \ + (FLEXSPI_LUT_OPERAND0(op0) | FLEXSPI_LUT_NUM_PADS0(pad0) | FLEXSPI_LUT_OPCODE0(cmd0) | FLEXSPI_LUT_OPERAND1(op1) | \ + FLEXSPI_LUT_NUM_PADS1(pad1) | FLEXSPI_LUT_OPCODE1(cmd1)) + +//!@brief Definitions for FlexSPI Serial Clock Frequency +typedef enum _FlexSpiSerialClockFreq +{ + kFlexSpiSerialClk_30MHz = 1, + kFlexSpiSerialClk_50MHz = 2, + kFlexSpiSerialClk_60MHz = 3, + kFlexSpiSerialClk_75MHz = 4, + kFlexSpiSerialClk_80MHz = 5, + kFlexSpiSerialClk_100MHz = 6, + kFlexSpiSerialClk_133MHz = 7, + kFlexSpiSerialClk_166MHz = 8, + kFlexSpiSerialClk_200MHz = 9, +} flexspi_serial_clk_freq_t; + +//!@brief FlexSPI clock configuration type +enum +{ + kFlexSpiClk_SDR, //!< Clock configure for SDR mode + kFlexSpiClk_DDR, //!< Clock configurat for DDR mode +}; + +//!@brief FlexSPI Read Sample Clock Source definition +typedef enum _FlashReadSampleClkSource +{ + kFlexSPIReadSampleClk_LoopbackInternally = 0, + kFlexSPIReadSampleClk_LoopbackFromDqsPad = 1, + kFlexSPIReadSampleClk_LoopbackFromSckPad = 2, + kFlexSPIReadSampleClk_ExternalInputFromDqsPad = 3, +} flexspi_read_sample_clk_t; + +//!@brief Misc feature bit definitions +enum +{ + kFlexSpiMiscOffset_DiffClkEnable = 0, //!< Bit for Differential clock enable + kFlexSpiMiscOffset_Ck2Enable = 1, //!< Bit for CK2 enable + kFlexSpiMiscOffset_ParallelEnable = 2, //!< Bit for Parallel mode enable + kFlexSpiMiscOffset_WordAddressableEnable = 3, //!< Bit for Word Addressable enable + kFlexSpiMiscOffset_SafeConfigFreqEnable = 4, //!< Bit for Safe Configuration Frequency enable + kFlexSpiMiscOffset_PadSettingOverrideEnable = 5, //!< Bit for Pad setting override enable + kFlexSpiMiscOffset_DdrModeEnable = 6, //!< Bit for DDR clock confiuration indication. +}; + +//!@brief Flash Type Definition +enum +{ + kFlexSpiDeviceType_SerialNOR = 1, //!< Flash devices are Serial NOR + kFlexSpiDeviceType_SerialNAND = 2, //!< Flash devices are Serial NAND + kFlexSpiDeviceType_SerialRAM = 3, //!< Flash devices are Serial RAM/HyperFLASH + kFlexSpiDeviceType_MCP_NOR_NAND = 0x12, //!< Flash device is MCP device, A1 is Serial NOR, A2 is Serial NAND + kFlexSpiDeviceType_MCP_NOR_RAM = 0x13, //!< Flash deivce is MCP device, A1 is Serial NOR, A2 is Serial RAMs +}; + +//!@brief Flash Pad Definitions +enum +{ + kSerialFlash_1Pad = 1, + kSerialFlash_2Pads = 2, + kSerialFlash_4Pads = 4, + kSerialFlash_8Pads = 8, +}; + +//!@brief FlexSPI LUT Sequence structure +typedef struct _lut_sequence +{ + uint8_t seqNum; //!< Sequence Number, valid number: 1-16 + uint8_t seqId; //!< Sequence Index, valid number: 0-15 + uint16_t reserved; +} flexspi_lut_seq_t; + +//!@brief Flash Configuration Command Type +enum +{ + kDeviceConfigCmdType_Generic, //!< Generic command, for example: configure dummy cycles, drive strength, etc + kDeviceConfigCmdType_QuadEnable, //!< Quad Enable command + kDeviceConfigCmdType_Spi2Xpi, //!< Switch from SPI to DPI/QPI/OPI mode + kDeviceConfigCmdType_Xpi2Spi, //!< Switch from DPI/QPI/OPI to SPI mode + kDeviceConfigCmdType_Spi2NoCmd, //!< Switch to 0-4-4/0-8-8 mode + kDeviceConfigCmdType_Reset, //!< Reset device command +}; + +//!@brief FlexSPI Memory Configuration Block +typedef struct _FlexSPIConfig +{ + uint32_t tag; //!< [0x000-0x003] Tag, fixed value 0x42464346UL + uint32_t version; //!< [0x004-0x007] Version,[31:24] -'V', [23:16] - Major, [15:8] - Minor, [7:0] - bugfix + uint32_t reserved0; //!< [0x008-0x00b] Reserved for future use + uint8_t readSampleClkSrc; //!< [0x00c-0x00c] Read Sample Clock Source, valid value: 0/1/3 + uint8_t csHoldTime; //!< [0x00d-0x00d] CS hold time, default value: 3 + uint8_t csSetupTime; //!< [0x00e-0x00e] CS setup time, default value: 3 + uint8_t columnAddressWidth; //!< [0x00f-0x00f] Column Address with, for HyperBus protocol, it is fixed to 3, For + //! Serial NAND, need to refer to datasheet + uint8_t deviceModeCfgEnable; //!< [0x010-0x010] Device Mode Configure enable flag, 1 - Enable, 0 - Disable + uint8_t deviceModeType; //!< [0x011-0x011] Specify the configuration command type:Quad Enable, DPI/QPI/OPI switch, + //! Generic configuration, etc. + uint16_t waitTimeCfgCommands; //!< [0x012-0x013] Wait time for all configuration commands, unit: 100us, Used for + //! DPI/QPI/OPI switch or reset command + flexspi_lut_seq_t deviceModeSeq; //!< [0x014-0x017] Device mode sequence info, [7:0] - LUT sequence id, [15:8] - LUt + //! sequence number, [31:16] Reserved + uint32_t deviceModeArg; //!< [0x018-0x01b] Argument/Parameter for device configuration + uint8_t configCmdEnable; //!< [0x01c-0x01c] Configure command Enable Flag, 1 - Enable, 0 - Disable + uint8_t configModeType[3]; //!< [0x01d-0x01f] Configure Mode Type, similar as deviceModeTpe + flexspi_lut_seq_t + configCmdSeqs[3]; //!< [0x020-0x02b] Sequence info for Device Configuration command, similar as deviceModeSeq + uint32_t reserved1; //!< [0x02c-0x02f] Reserved for future use + uint32_t configCmdArgs[3]; //!< [0x030-0x03b] Arguments/Parameters for device Configuration commands + uint32_t reserved2; //!< [0x03c-0x03f] Reserved for future use + uint32_t controllerMiscOption; //!< [0x040-0x043] Controller Misc Options, see Misc feature bit definitions for more + //! details + uint8_t deviceType; //!< [0x044-0x044] Device Type: See Flash Type Definition for more details + uint8_t sflashPadType; //!< [0x045-0x045] Serial Flash Pad Type: 1 - Single, 2 - Dual, 4 - Quad, 8 - Octal + uint8_t serialClkFreq; //!< [0x046-0x046] Serial Flash Frequencey, device specific definitions, See System Boot + //! Chapter for more details + uint8_t lutCustomSeqEnable; //!< [0x047-0x047] LUT customization Enable, it is required if the program/erase cannot + //! be done using 1 LUT sequence, currently, only applicable to HyperFLASH + uint32_t reserved3[2]; //!< [0x048-0x04f] Reserved for future use + uint32_t sflashA1Size; //!< [0x050-0x053] Size of Flash connected to A1 + uint32_t sflashA2Size; //!< [0x054-0x057] Size of Flash connected to A2 + uint32_t sflashB1Size; //!< [0x058-0x05b] Size of Flash connected to B1 + uint32_t sflashB2Size; //!< [0x05c-0x05f] Size of Flash connected to B2 + uint32_t csPadSettingOverride; //!< [0x060-0x063] CS pad setting override value + uint32_t sclkPadSettingOverride; //!< [0x064-0x067] SCK pad setting override value + uint32_t dataPadSettingOverride; //!< [0x068-0x06b] data pad setting override value + uint32_t dqsPadSettingOverride; //!< [0x06c-0x06f] DQS pad setting override value + uint32_t timeoutInMs; //!< [0x070-0x073] Timeout threshold for read status command + uint32_t commandInterval; //!< [0x074-0x077] CS deselect interval between two commands + uint16_t dataValidTime[2]; //!< [0x078-0x07b] CLK edge to data valid time for PORT A and PORT B, in terms of 0.1ns + uint16_t busyOffset; //!< [0x07c-0x07d] Busy offset, valid value: 0-31 + uint16_t busyBitPolarity; //!< [0x07e-0x07f] Busy flag polarity, 0 - busy flag is 1 when flash device is busy, 1 - + //! busy flag is 0 when flash device is busy + uint32_t lookupTable[64]; //!< [0x080-0x17f] Lookup table holds Flash command sequences + flexspi_lut_seq_t lutCustomSeq[12]; //!< [0x180-0x1af] Customizable LUT Sequences + uint32_t reserved4[4]; //!< [0x1b0-0x1bf] Reserved for future use +} flexspi_mem_config_t; + +/* */ +#define NOR_CMD_INDEX_READ CMD_INDEX_READ //!< 0 +#define NOR_CMD_INDEX_READSTATUS CMD_INDEX_READSTATUS //!< 1 +#define NOR_CMD_INDEX_WRITEENABLE CMD_INDEX_WRITEENABLE //!< 2 +#define NOR_CMD_INDEX_ERASESECTOR 3 //!< 3 +#define NOR_CMD_INDEX_PAGEPROGRAM CMD_INDEX_WRITE //!< 4 +#define NOR_CMD_INDEX_CHIPERASE 5 //!< 5 +#define NOR_CMD_INDEX_DUMMY 6 //!< 6 +#define NOR_CMD_INDEX_ERASEBLOCK 7 //!< 7 + +#define NOR_CMD_LUT_SEQ_IDX_READ CMD_LUT_SEQ_IDX_READ //!< 0 READ LUT sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_READSTATUS \ + CMD_LUT_SEQ_IDX_READSTATUS //!< 1 Read Status LUT sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_READSTATUS_XPI \ + 2 //!< 2 Read status DPI/QPI/OPI sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_WRITEENABLE \ + CMD_LUT_SEQ_IDX_WRITEENABLE //!< 3 Write Enable sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_WRITEENABLE_XPI \ + 4 //!< 4 Write Enable DPI/QPI/OPI sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_ERASESECTOR 5 //!< 5 Erase Sector sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_ERASEBLOCK 8 //!< 8 Erase Block sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM \ + CMD_LUT_SEQ_IDX_WRITE //!< 9 Program sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_CHIPERASE 11 //!< 11 Chip Erase sequence in lookupTable id stored in config block +#define NOR_CMD_LUT_SEQ_IDX_READ_SFDP 13 //!< 13 Read SFDP sequence in lookupTable id stored in config block +#define NOR_CMD_LUT_SEQ_IDX_RESTORE_NOCMD \ + 14 //!< 14 Restore 0-4-4/0-8-8 mode sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_EXIT_NOCMD \ + 15 //!< 15 Exit 0-4-4/0-8-8 mode sequence id in lookupTable stored in config blobk + +/* + * Serial NOR configuration block + */ +typedef struct _flexspi_nor_config +{ + flexspi_mem_config_t memConfig; //!< Common memory configuration info via FlexSPI + uint32_t pageSize; //!< Page size of Serial NOR + uint32_t sectorSize; //!< Sector size of Serial NOR + uint8_t ipcmdSerialClkFreq; //!< Clock frequency for IP command + uint8_t isUniformBlockSize; //!< Sector/Block size is the same + uint8_t reserved0[2]; //!< Reserved for future use + uint8_t serialNorType; //!< Serial NOR Flash type: 0/1/2/3 + uint8_t needExitNoCmdMode; //!< Need to exit NoCmd mode before other IP command + uint8_t halfClkForNonReadCmd; //!< Half the Serial Clock for non-read command: true/false + uint8_t needRestoreNoCmdMode; //!< Need to Restore NoCmd mode after IP commmand execution + uint32_t blockSize; //!< Block size + uint32_t reserve2[11]; //!< Reserved for future use +} flexspi_nor_config_t; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif +#endif /* __EVKMIMXRT1020_FLEXSPI_NOR_CONFIG__ */ diff --git a/ports/mimxrt/boards/MIMXRT1020_EVK/flash_config.c b/ports/mimxrt/boards/MIMXRT1020_EVK/flash_config.c new file mode 100644 index 0000000000000..56007931a90d7 --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1020_EVK/flash_config.c @@ -0,0 +1,51 @@ +/* + * Copyright 2019 NXP. + * All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +// Based on tinyusb/hw/bsp/teensy_40/evkmimxrt1010_flexspi_nor_config.c + +#include "evkmimxrt1020_flexspi_nor_config.h" + +/* Component ID definition, used by tools. */ +#ifndef FSL_COMPONENT_ID +#define FSL_COMPONENT_ID "platform.drivers.xip_board" +#endif + +/******************************************************************************* + * Code + ******************************************************************************/ +#if defined(XIP_BOOT_HEADER_ENABLE) && (XIP_BOOT_HEADER_ENABLE == 1) +#if defined(__ARMCC_VERSION) || defined(__GNUC__) +__attribute__((section(".boot_hdr.conf"))) +#elif defined(__ICCARM__) +#pragma location = ".boot_hdr.conf" +#endif + +const flexspi_nor_config_t qspiflash_config = { + .memConfig = + { + .tag = FLEXSPI_CFG_BLK_TAG, + .version = FLEXSPI_CFG_BLK_VERSION, + .readSampleClkSrc = kFlexSPIReadSampleClk_LoopbackFromDqsPad, + .csHoldTime = 3u, + .csSetupTime = 3u, + // Enable DDR mode, Wordaddassable, Safe configuration, Differential clock + .sflashPadType = kSerialFlash_4Pads, + .serialClkFreq = kFlexSpiSerialClk_100MHz, + .sflashA1Size = 8u * 1024u * 1024u, + .lookupTable = + { + // Read LUTs + FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0xEB, RADDR_SDR, FLEXSPI_4PAD, 0x18), + FLEXSPI_LUT_SEQ(DUMMY_SDR, FLEXSPI_4PAD, 0x06, READ_SDR, FLEXSPI_4PAD, 0x04), + }, + }, + .pageSize = 256u, + .sectorSize = 4u * 1024u, + .blockSize = 256u * 1024u, + .isUniformBlockSize = false, +}; +#endif /* XIP_BOOT_HEADER_ENABLE */ diff --git a/ports/mimxrt/boards/MIMXRT1020_EVK/mpconfigboard.h b/ports/mimxrt/boards/MIMXRT1020_EVK/mpconfigboard.h new file mode 100644 index 0000000000000..8598f76bf1833 --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1020_EVK/mpconfigboard.h @@ -0,0 +1,9 @@ +#define MICROPY_HW_BOARD_NAME "i.MX RT1020 EVK" +#define MICROPY_HW_MCU_NAME "MIMXRT1021DAG5A" + +#define BOARD_FLASH_SIZE (8 * 1024 * 1024) + +// i.MX RT1020 EVK has 1 board LED +#define MICROPY_HW_LED1_PIN (GPIO_AD_B0_05) +#define MICROPY_HW_LED_ON(pin) (mp_hal_pin_low(pin)) +#define MICROPY_HW_LED_OFF(pin) (mp_hal_pin_high(pin)) diff --git a/ports/mimxrt/boards/MIMXRT1020_EVK/mpconfigboard.mk b/ports/mimxrt/boards/MIMXRT1020_EVK/mpconfigboard.mk new file mode 100644 index 0000000000000..f3b1689524e29 --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1020_EVK/mpconfigboard.mk @@ -0,0 +1,7 @@ +MCU_SERIES = MIMXRT1021 +MCU_VARIANT = MIMXRT1021DAG5A + +JLINK_PATH ?= /media/RT1020-EVK/ + +deploy: $(BUILD)/firmware.bin + cp $< $(JLINK_PATH) diff --git a/ports/mimxrt/boards/MIMXRT1020_EVK/pins.c b/ports/mimxrt/boards/MIMXRT1020_EVK/pins.c new file mode 100644 index 0000000000000..946b6efca8b27 --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1020_EVK/pins.c @@ -0,0 +1,33 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Philipp Ebensberger + * + * 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 "pin.h" + +static pin_af_obj_t GPIO_AD_B0_05_af[] = { + PIN_AF(GPIO1_IO05, PIN_AF_MODE_ALT5, GPIO1, 0x10B0U), +}; + +pin_obj_t GPIO_AD_B0_05 = PIN(GPIO_AD_B0_05, GPIO1, 5, GPIO_AD_B0_05_af); diff --git a/ports/mimxrt/boards/MIMXRT1020_EVK/pins.h b/ports/mimxrt/boards/MIMXRT1020_EVK/pins.h new file mode 100644 index 0000000000000..158929911c7a5 --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1020_EVK/pins.h @@ -0,0 +1,30 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Philipp Ebensberger + * + * 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. + */ + +// NOTE: pins.h shall only be included in in pin.h +// hence no include guards are needed since they will be provided by pin.h + +extern pin_obj_t GPIO_AD_B0_05; diff --git a/ports/mimxrt/boards/MIMXRT1021.ld b/ports/mimxrt/boards/MIMXRT1021.ld new file mode 100644 index 0000000000000..6b59649fcaf7e --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1021.ld @@ -0,0 +1,8 @@ +/* 24kiB stack. */ +__stack_size__ = 0x6000; +_estack = __StackTop; +_sstack = __StackLimit; + +/* Use second OCRAM bank for GC heap. */ +_gc_heap_start = ORIGIN(m_data2); +_gc_heap_end = ORIGIN(m_data2) + LENGTH(m_data2); diff --git a/ports/mimxrt/boards/MIMXRT1060_EVK/evkmimxrt1060_flexspi_nor_config.h b/ports/mimxrt/boards/MIMXRT1060_EVK/evkmimxrt1060_flexspi_nor_config.h new file mode 100644 index 0000000000000..8bb771fd8ad44 --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1060_EVK/evkmimxrt1060_flexspi_nor_config.h @@ -0,0 +1,268 @@ +/* + * Copyright 2018 NXP + * All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef __EVKMIMXRT1060_FLEXSPI_NOR_CONFIG__ +#define __EVKMIMXRT1060_FLEXSPI_NOR_CONFIG__ + +#include +#include +#include "fsl_common.h" + +/*! @name Driver version */ +/*@{*/ +/*! @brief XIP_BOARD driver version 2.0.0. */ +#define FSL_XIP_BOARD_DRIVER_VERSION (MAKE_VERSION(2, 0, 0)) +/*@}*/ + +/* FLEXSPI memory config block related defintions */ +#define FLEXSPI_CFG_BLK_TAG (0x42464346UL) // ascii "FCFB" Big Endian +#define FLEXSPI_CFG_BLK_VERSION (0x56010400UL) // V1.4.0 +#define FLEXSPI_CFG_BLK_SIZE (512) + +/* FLEXSPI Feature related definitions */ +#define FLEXSPI_FEATURE_HAS_PARALLEL_MODE 1 + +/* Lookup table related defintions */ +#define CMD_INDEX_READ 0 +#define CMD_INDEX_READSTATUS 1 +#define CMD_INDEX_WRITEENABLE 2 +#define CMD_INDEX_WRITE 4 + +#define CMD_LUT_SEQ_IDX_READ 0 +#define CMD_LUT_SEQ_IDX_READSTATUS 1 +#define CMD_LUT_SEQ_IDX_WRITEENABLE 3 +#define CMD_LUT_SEQ_IDX_WRITE 9 + +#define CMD_SDR 0x01 +#define CMD_DDR 0x21 +#define RADDR_SDR 0x02 +#define RADDR_DDR 0x22 +#define CADDR_SDR 0x03 +#define CADDR_DDR 0x23 +#define MODE1_SDR 0x04 +#define MODE1_DDR 0x24 +#define MODE2_SDR 0x05 +#define MODE2_DDR 0x25 +#define MODE4_SDR 0x06 +#define MODE4_DDR 0x26 +#define MODE8_SDR 0x07 +#define MODE8_DDR 0x27 +#define WRITE_SDR 0x08 +#define WRITE_DDR 0x28 +#define READ_SDR 0x09 +#define READ_DDR 0x29 +#define LEARN_SDR 0x0A +#define LEARN_DDR 0x2A +#define DATSZ_SDR 0x0B +#define DATSZ_DDR 0x2B +#define DUMMY_SDR 0x0C +#define DUMMY_DDR 0x2C +#define DUMMY_RWDS_SDR 0x0D +#define DUMMY_RWDS_DDR 0x2D +#define JMP_ON_CS 0x1F +#define STOP 0 + +#define FLEXSPI_1PAD 0 +#define FLEXSPI_2PAD 1 +#define FLEXSPI_4PAD 2 +#define FLEXSPI_8PAD 3 + +#define FLEXSPI_LUT_SEQ(cmd0, pad0, op0, cmd1, pad1, op1) \ + (FLEXSPI_LUT_OPERAND0(op0) | FLEXSPI_LUT_NUM_PADS0(pad0) | FLEXSPI_LUT_OPCODE0(cmd0) | FLEXSPI_LUT_OPERAND1(op1) | \ + FLEXSPI_LUT_NUM_PADS1(pad1) | FLEXSPI_LUT_OPCODE1(cmd1)) + +//!@brief Definitions for FlexSPI Serial Clock Frequency +typedef enum _FlexSpiSerialClockFreq +{ + kFlexSpiSerialClk_30MHz = 1, + kFlexSpiSerialClk_50MHz = 2, + kFlexSpiSerialClk_60MHz = 3, + kFlexSpiSerialClk_75MHz = 4, + kFlexSpiSerialClk_80MHz = 5, + kFlexSpiSerialClk_100MHz = 6, + kFlexSpiSerialClk_120MHz = 7, + kFlexSpiSerialClk_133MHz = 8, + kFlexSpiSerialClk_166MHz = 9, +} flexspi_serial_clk_freq_t; + +//!@brief FlexSPI clock configuration type +enum +{ + kFlexSpiClk_SDR, //!< Clock configure for SDR mode + kFlexSpiClk_DDR, //!< Clock configurat for DDR mode +}; + +//!@brief FlexSPI Read Sample Clock Source definition +typedef enum _FlashReadSampleClkSource +{ + kFlexSPIReadSampleClk_LoopbackInternally = 0, + kFlexSPIReadSampleClk_LoopbackFromDqsPad = 1, + kFlexSPIReadSampleClk_LoopbackFromSckPad = 2, + kFlexSPIReadSampleClk_ExternalInputFromDqsPad = 3, +} flexspi_read_sample_clk_t; + +//!@brief Misc feature bit definitions +enum +{ + kFlexSpiMiscOffset_DiffClkEnable = 0, //!< Bit for Differential clock enable + kFlexSpiMiscOffset_Ck2Enable = 1, //!< Bit for CK2 enable + kFlexSpiMiscOffset_ParallelEnable = 2, //!< Bit for Parallel mode enable + kFlexSpiMiscOffset_WordAddressableEnable = 3, //!< Bit for Word Addressable enable + kFlexSpiMiscOffset_SafeConfigFreqEnable = 4, //!< Bit for Safe Configuration Frequency enable + kFlexSpiMiscOffset_PadSettingOverrideEnable = 5, //!< Bit for Pad setting override enable + kFlexSpiMiscOffset_DdrModeEnable = 6, //!< Bit for DDR clock confiuration indication. +}; + +//!@brief Flash Type Definition +enum +{ + kFlexSpiDeviceType_SerialNOR = 1, //!< Flash devices are Serial NOR + kFlexSpiDeviceType_SerialNAND = 2, //!< Flash devices are Serial NAND + kFlexSpiDeviceType_SerialRAM = 3, //!< Flash devices are Serial RAM/HyperFLASH + kFlexSpiDeviceType_MCP_NOR_NAND = 0x12, //!< Flash device is MCP device, A1 is Serial NOR, A2 is Serial NAND + kFlexSpiDeviceType_MCP_NOR_RAM = 0x13, //!< Flash deivce is MCP device, A1 is Serial NOR, A2 is Serial RAMs +}; + +//!@brief Flash Pad Definitions +enum +{ + kSerialFlash_1Pad = 1, + kSerialFlash_2Pads = 2, + kSerialFlash_4Pads = 4, + kSerialFlash_8Pads = 8, +}; + +//!@brief FlexSPI LUT Sequence structure +typedef struct _lut_sequence +{ + uint8_t seqNum; //!< Sequence Number, valid number: 1-16 + uint8_t seqId; //!< Sequence Index, valid number: 0-15 + uint16_t reserved; +} flexspi_lut_seq_t; + +//!@brief Flash Configuration Command Type +enum +{ + kDeviceConfigCmdType_Generic, //!< Generic command, for example: configure dummy cycles, drive strength, etc + kDeviceConfigCmdType_QuadEnable, //!< Quad Enable command + kDeviceConfigCmdType_Spi2Xpi, //!< Switch from SPI to DPI/QPI/OPI mode + kDeviceConfigCmdType_Xpi2Spi, //!< Switch from DPI/QPI/OPI to SPI mode + kDeviceConfigCmdType_Spi2NoCmd, //!< Switch to 0-4-4/0-8-8 mode + kDeviceConfigCmdType_Reset, //!< Reset device command +}; + +//!@brief FlexSPI Memory Configuration Block +typedef struct _FlexSPIConfig +{ + uint32_t tag; //!< [0x000-0x003] Tag, fixed value 0x42464346UL + uint32_t version; //!< [0x004-0x007] Version,[31:24] -'V', [23:16] - Major, [15:8] - Minor, [7:0] - bugfix + uint32_t reserved0; //!< [0x008-0x00b] Reserved for future use + uint8_t readSampleClkSrc; //!< [0x00c-0x00c] Read Sample Clock Source, valid value: 0/1/3 + uint8_t csHoldTime; //!< [0x00d-0x00d] CS hold time, default value: 3 + uint8_t csSetupTime; //!< [0x00e-0x00e] CS setup time, default value: 3 + uint8_t columnAddressWidth; //!< [0x00f-0x00f] Column Address with, for HyperBus protocol, it is fixed to 3, For + //! Serial NAND, need to refer to datasheet + uint8_t deviceModeCfgEnable; //!< [0x010-0x010] Device Mode Configure enable flag, 1 - Enable, 0 - Disable + uint8_t deviceModeType; //!< [0x011-0x011] Specify the configuration command type:Quad Enable, DPI/QPI/OPI switch, + //! Generic configuration, etc. + uint16_t waitTimeCfgCommands; //!< [0x012-0x013] Wait time for all configuration commands, unit: 100us, Used for + //! DPI/QPI/OPI switch or reset command + flexspi_lut_seq_t deviceModeSeq; //!< [0x014-0x017] Device mode sequence info, [7:0] - LUT sequence id, [15:8] - LUt + //! sequence number, [31:16] Reserved + uint32_t deviceModeArg; //!< [0x018-0x01b] Argument/Parameter for device configuration + uint8_t configCmdEnable; //!< [0x01c-0x01c] Configure command Enable Flag, 1 - Enable, 0 - Disable + uint8_t configModeType[3]; //!< [0x01d-0x01f] Configure Mode Type, similar as deviceModeTpe + flexspi_lut_seq_t + configCmdSeqs[3]; //!< [0x020-0x02b] Sequence info for Device Configuration command, similar as deviceModeSeq + uint32_t reserved1; //!< [0x02c-0x02f] Reserved for future use + uint32_t configCmdArgs[3]; //!< [0x030-0x03b] Arguments/Parameters for device Configuration commands + uint32_t reserved2; //!< [0x03c-0x03f] Reserved for future use + uint32_t controllerMiscOption; //!< [0x040-0x043] Controller Misc Options, see Misc feature bit definitions for more + //! details + uint8_t deviceType; //!< [0x044-0x044] Device Type: See Flash Type Definition for more details + uint8_t sflashPadType; //!< [0x045-0x045] Serial Flash Pad Type: 1 - Single, 2 - Dual, 4 - Quad, 8 - Octal + uint8_t serialClkFreq; //!< [0x046-0x046] Serial Flash Frequencey, device specific definitions, See System Boot + //! Chapter for more details + uint8_t lutCustomSeqEnable; //!< [0x047-0x047] LUT customization Enable, it is required if the program/erase cannot + //! be done using 1 LUT sequence, currently, only applicable to HyperFLASH + uint32_t reserved3[2]; //!< [0x048-0x04f] Reserved for future use + uint32_t sflashA1Size; //!< [0x050-0x053] Size of Flash connected to A1 + uint32_t sflashA2Size; //!< [0x054-0x057] Size of Flash connected to A2 + uint32_t sflashB1Size; //!< [0x058-0x05b] Size of Flash connected to B1 + uint32_t sflashB2Size; //!< [0x05c-0x05f] Size of Flash connected to B2 + uint32_t csPadSettingOverride; //!< [0x060-0x063] CS pad setting override value + uint32_t sclkPadSettingOverride; //!< [0x064-0x067] SCK pad setting override value + uint32_t dataPadSettingOverride; //!< [0x068-0x06b] data pad setting override value + uint32_t dqsPadSettingOverride; //!< [0x06c-0x06f] DQS pad setting override value + uint32_t timeoutInMs; //!< [0x070-0x073] Timeout threshold for read status command + uint32_t commandInterval; //!< [0x074-0x077] CS deselect interval between two commands + uint16_t dataValidTime[2]; //!< [0x078-0x07b] CLK edge to data valid time for PORT A and PORT B, in terms of 0.1ns + uint16_t busyOffset; //!< [0x07c-0x07d] Busy offset, valid value: 0-31 + uint16_t busyBitPolarity; //!< [0x07e-0x07f] Busy flag polarity, 0 - busy flag is 1 when flash device is busy, 1 - + //! busy flag is 0 when flash device is busy + uint32_t lookupTable[64]; //!< [0x080-0x17f] Lookup table holds Flash command sequences + flexspi_lut_seq_t lutCustomSeq[12]; //!< [0x180-0x1af] Customizable LUT Sequences + uint32_t reserved4[4]; //!< [0x1b0-0x1bf] Reserved for future use +} flexspi_mem_config_t; + +/* */ +#define NOR_CMD_INDEX_READ CMD_INDEX_READ //!< 0 +#define NOR_CMD_INDEX_READSTATUS CMD_INDEX_READSTATUS //!< 1 +#define NOR_CMD_INDEX_WRITEENABLE CMD_INDEX_WRITEENABLE //!< 2 +#define NOR_CMD_INDEX_ERASESECTOR 3 //!< 3 +#define NOR_CMD_INDEX_PAGEPROGRAM CMD_INDEX_WRITE //!< 4 +#define NOR_CMD_INDEX_CHIPERASE 5 //!< 5 +#define NOR_CMD_INDEX_DUMMY 6 //!< 6 +#define NOR_CMD_INDEX_ERASEBLOCK 7 //!< 7 + +#define NOR_CMD_LUT_SEQ_IDX_READ CMD_LUT_SEQ_IDX_READ //!< 0 READ LUT sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_READSTATUS \ + CMD_LUT_SEQ_IDX_READSTATUS //!< 1 Read Status LUT sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_READSTATUS_XPI \ + 2 //!< 2 Read status DPI/QPI/OPI sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_WRITEENABLE \ + CMD_LUT_SEQ_IDX_WRITEENABLE //!< 3 Write Enable sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_WRITEENABLE_XPI \ + 4 //!< 4 Write Enable DPI/QPI/OPI sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_ERASESECTOR 5 //!< 5 Erase Sector sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_ERASEBLOCK 8 //!< 8 Erase Block sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM \ + CMD_LUT_SEQ_IDX_WRITE //!< 9 Program sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_CHIPERASE 11 //!< 11 Chip Erase sequence in lookupTable id stored in config block +#define NOR_CMD_LUT_SEQ_IDX_READ_SFDP 13 //!< 13 Read SFDP sequence in lookupTable id stored in config block +#define NOR_CMD_LUT_SEQ_IDX_RESTORE_NOCMD \ + 14 //!< 14 Restore 0-4-4/0-8-8 mode sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_EXIT_NOCMD \ + 15 //!< 15 Exit 0-4-4/0-8-8 mode sequence id in lookupTable stored in config blobk + +/* + * Serial NOR configuration block + */ +typedef struct _flexspi_nor_config +{ + flexspi_mem_config_t memConfig; //!< Common memory configuration info via FlexSPI + uint32_t pageSize; //!< Page size of Serial NOR + uint32_t sectorSize; //!< Sector size of Serial NOR + uint8_t ipcmdSerialClkFreq; //!< Clock frequency for IP command + uint8_t isUniformBlockSize; //!< Sector/Block size is the same + uint8_t reserved0[2]; //!< Reserved for future use + uint8_t serialNorType; //!< Serial NOR Flash type: 0/1/2/3 + uint8_t needExitNoCmdMode; //!< Need to exit NoCmd mode before other IP command + uint8_t halfClkForNonReadCmd; //!< Half the Serial Clock for non-read command: true/false + uint8_t needRestoreNoCmdMode; //!< Need to Restore NoCmd mode after IP commmand execution + uint32_t blockSize; //!< Block size + uint32_t reserve2[11]; //!< Reserved for future use +} flexspi_nor_config_t; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif +#endif /* __EVKMIMXRT1060_FLEXSPI_NOR_CONFIG__ */ diff --git a/ports/mimxrt/boards/MIMXRT1060_EVK/flash_config.c b/ports/mimxrt/boards/MIMXRT1060_EVK/flash_config.c new file mode 100644 index 0000000000000..095a922e9efe1 --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1060_EVK/flash_config.c @@ -0,0 +1,48 @@ +/* + * Copyright 2018 NXP + * All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "evkmimxrt1060_flexspi_nor_config.h" + +/* Component ID definition, used by tools. */ +#ifndef FSL_COMPONENT_ID +#define FSL_COMPONENT_ID "platform.drivers.xip_board" +#endif + +/******************************************************************************* + * Code + ******************************************************************************/ +#if defined(XIP_BOOT_HEADER_ENABLE) && (XIP_BOOT_HEADER_ENABLE == 1) +#if defined(__CC_ARM) || defined(__ARMCC_VERSION) || defined(__GNUC__) +__attribute__((section(".boot_hdr.conf"))) +#elif defined(__ICCARM__) +#pragma location = ".boot_hdr.conf" +#endif + +const flexspi_nor_config_t qspiflash_config = { + .memConfig = + { + .tag = FLEXSPI_CFG_BLK_TAG, + .version = FLEXSPI_CFG_BLK_VERSION, + .readSampleClkSrc = kFlexSPIReadSampleClk_LoopbackFromDqsPad, + .csHoldTime = 3u, + .csSetupTime = 3u, + .sflashPadType = kSerialFlash_4Pads, + .serialClkFreq = kFlexSpiSerialClk_100MHz, + .sflashA1Size = 8u * 1024u * 1024u, + .lookupTable = + { + // Read LUTs + FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0xEB, RADDR_SDR, FLEXSPI_4PAD, 0x18), + FLEXSPI_LUT_SEQ(DUMMY_SDR, FLEXSPI_4PAD, 0x06, READ_SDR, FLEXSPI_4PAD, 0x04), + }, + }, + .pageSize = 256u, + .sectorSize = 4u * 1024u, + .blockSize = 64u * 1024u, + .isUniformBlockSize = false, +}; +#endif /* XIP_BOOT_HEADER_ENABLE */ diff --git a/ports/mimxrt/boards/MIMXRT1060_EVK/mpconfigboard.h b/ports/mimxrt/boards/MIMXRT1060_EVK/mpconfigboard.h new file mode 100644 index 0000000000000..8bd2a579174aa --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1060_EVK/mpconfigboard.h @@ -0,0 +1,9 @@ +#define MICROPY_HW_BOARD_NAME "i.MX RT1060 EVK" +#define MICROPY_HW_MCU_NAME "MIMXRT1062DVJ6A" + +#define BOARD_FLASH_SIZE (8 * 1024 * 1024) + +// MIMXRT1060_EVK has 1 user LED +#define MICROPY_HW_LED1_PIN (GPIO_AD_B0_09) +#define MICROPY_HW_LED_ON(pin) (mp_hal_pin_low(pin)) +#define MICROPY_HW_LED_OFF(pin) (mp_hal_pin_high(pin)) diff --git a/ports/mimxrt/boards/MIMXRT1060_EVK/mpconfigboard.mk b/ports/mimxrt/boards/MIMXRT1060_EVK/mpconfigboard.mk new file mode 100644 index 0000000000000..b34169801b900 --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1060_EVK/mpconfigboard.mk @@ -0,0 +1,7 @@ +MCU_SERIES = MIMXRT1062 +MCU_VARIANT = MIMXRT1062DVJ6A + +JLINK_PATH ?= /media/RT1060-EVK/ + +deploy: $(BUILD)/firmware.bin + cp $< $(JLINK_PATH) diff --git a/ports/mimxrt/boards/MIMXRT1060_EVK/pins.c b/ports/mimxrt/boards/MIMXRT1060_EVK/pins.c new file mode 100644 index 0000000000000..d5da9c6f99275 --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1060_EVK/pins.c @@ -0,0 +1,33 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 NXP + * + * 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 "pin.h" + +static pin_af_obj_t GPIO_AD_B0_09_af[] = { + PIN_AF(GPIO1_IO09, PIN_AF_MODE_ALT5, GPIO1, 0x10B0U), +}; + +pin_obj_t GPIO_AD_B0_09 = PIN(GPIO_AD_B0_09, GPIO1, 9, GPIO_AD_B0_09_af); diff --git a/ports/mimxrt/boards/MIMXRT1060_EVK/pins.h b/ports/mimxrt/boards/MIMXRT1060_EVK/pins.h new file mode 100644 index 0000000000000..baef51c6c8612 --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1060_EVK/pins.h @@ -0,0 +1,30 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 NXP + * + * 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. + */ + +// NOTE: pins.h shall only be included in in pin.h +// hence no include guards are needed since they will be provided by pin.h + +extern pin_obj_t GPIO_AD_B0_09; diff --git a/ports/mimxrt/boards/MIMXRT1064.ld b/ports/mimxrt/boards/MIMXRT1064.ld new file mode 100644 index 0000000000000..bf37c21802581 --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1064.ld @@ -0,0 +1,8 @@ +/* 24kiB stack. */ +__stack_size__ = 0x6000; +_estack = __StackTop; +_sstack = __StackLimit; + +/* Use second OCRAM bank for GC heap. */ +_gc_heap_start = ORIGIN(m_data2); +_gc_heap_end = ORIGIN(m_data2) + LENGTH(m_data2); \ No newline at end of file diff --git a/ports/mimxrt/boards/MIMXRT1064_EVK/evkmimxrt1064_flexspi_nor_config.h b/ports/mimxrt/boards/MIMXRT1064_EVK/evkmimxrt1064_flexspi_nor_config.h new file mode 100644 index 0000000000000..efdfe583fb82c --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1064_EVK/evkmimxrt1064_flexspi_nor_config.h @@ -0,0 +1,268 @@ +/* + * Copyright 2018 NXP + * All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef __EVKMIMXRT1064_FLEXSPI_NOR_CONFIG__ +#define __EVKMIMXRT1064_FLEXSPI_NOR_CONFIG__ + +#include +#include +#include "fsl_common.h" + +/*! @name Driver version */ +/*@{*/ +/*! @brief XIP_BOARD driver version 2.0.0. */ +#define FSL_XIP_BOARD_DRIVER_VERSION (MAKE_VERSION(2, 0, 0)) +/*@}*/ + +/* FLEXSPI memory config block related defintions */ +#define FLEXSPI_CFG_BLK_TAG (0x42464346UL) // ascii "FCFB" Big Endian +#define FLEXSPI_CFG_BLK_VERSION (0x56010400UL) // V1.4.0 +#define FLEXSPI_CFG_BLK_SIZE (512) + +/* FLEXSPI Feature related definitions */ +#define FLEXSPI_FEATURE_HAS_PARALLEL_MODE 1 + +/* Lookup table related defintions */ +#define CMD_INDEX_READ 0 +#define CMD_INDEX_READSTATUS 1 +#define CMD_INDEX_WRITEENABLE 2 +#define CMD_INDEX_WRITE 4 + +#define CMD_LUT_SEQ_IDX_READ 0 +#define CMD_LUT_SEQ_IDX_READSTATUS 1 +#define CMD_LUT_SEQ_IDX_WRITEENABLE 3 +#define CMD_LUT_SEQ_IDX_WRITE 9 + +#define CMD_SDR 0x01 +#define CMD_DDR 0x21 +#define RADDR_SDR 0x02 +#define RADDR_DDR 0x22 +#define CADDR_SDR 0x03 +#define CADDR_DDR 0x23 +#define MODE1_SDR 0x04 +#define MODE1_DDR 0x24 +#define MODE2_SDR 0x05 +#define MODE2_DDR 0x25 +#define MODE4_SDR 0x06 +#define MODE4_DDR 0x26 +#define MODE8_SDR 0x07 +#define MODE8_DDR 0x27 +#define WRITE_SDR 0x08 +#define WRITE_DDR 0x28 +#define READ_SDR 0x09 +#define READ_DDR 0x29 +#define LEARN_SDR 0x0A +#define LEARN_DDR 0x2A +#define DATSZ_SDR 0x0B +#define DATSZ_DDR 0x2B +#define DUMMY_SDR 0x0C +#define DUMMY_DDR 0x2C +#define DUMMY_RWDS_SDR 0x0D +#define DUMMY_RWDS_DDR 0x2D +#define JMP_ON_CS 0x1F +#define STOP 0 + +#define FLEXSPI_1PAD 0 +#define FLEXSPI_2PAD 1 +#define FLEXSPI_4PAD 2 +#define FLEXSPI_8PAD 3 + +#define FLEXSPI_LUT_SEQ(cmd0, pad0, op0, cmd1, pad1, op1) \ + (FLEXSPI_LUT_OPERAND0(op0) | FLEXSPI_LUT_NUM_PADS0(pad0) | FLEXSPI_LUT_OPCODE0(cmd0) | FLEXSPI_LUT_OPERAND1(op1) | \ + FLEXSPI_LUT_NUM_PADS1(pad1) | FLEXSPI_LUT_OPCODE1(cmd1)) + +//!@brief Definitions for FlexSPI Serial Clock Frequency +typedef enum _FlexSpiSerialClockFreq +{ + kFlexSpiSerialClk_30MHz = 1, + kFlexSpiSerialClk_50MHz = 2, + kFlexSpiSerialClk_60MHz = 3, + kFlexSpiSerialClk_75MHz = 4, + kFlexSpiSerialClk_80MHz = 5, + kFlexSpiSerialClk_100MHz = 6, + kFlexSpiSerialClk_120MHz = 7, + kFlexSpiSerialClk_133MHz = 8, + kFlexSpiSerialClk_166MHz = 9, +} flexspi_serial_clk_freq_t; + +//!@brief FlexSPI clock configuration type +enum +{ + kFlexSpiClk_SDR, //!< Clock configure for SDR mode + kFlexSpiClk_DDR, //!< Clock configurat for DDR mode +}; + +//!@brief FlexSPI Read Sample Clock Source definition +typedef enum _FlashReadSampleClkSource +{ + kFlexSPIReadSampleClk_LoopbackInternally = 0, + kFlexSPIReadSampleClk_LoopbackFromDqsPad = 1, + kFlexSPIReadSampleClk_LoopbackFromSckPad = 2, + kFlexSPIReadSampleClk_ExternalInputFromDqsPad = 3, +} flexspi_read_sample_clk_t; + +//!@brief Misc feature bit definitions +enum +{ + kFlexSpiMiscOffset_DiffClkEnable = 0, //!< Bit for Differential clock enable + kFlexSpiMiscOffset_Ck2Enable = 1, //!< Bit for CK2 enable + kFlexSpiMiscOffset_ParallelEnable = 2, //!< Bit for Parallel mode enable + kFlexSpiMiscOffset_WordAddressableEnable = 3, //!< Bit for Word Addressable enable + kFlexSpiMiscOffset_SafeConfigFreqEnable = 4, //!< Bit for Safe Configuration Frequency enable + kFlexSpiMiscOffset_PadSettingOverrideEnable = 5, //!< Bit for Pad setting override enable + kFlexSpiMiscOffset_DdrModeEnable = 6, //!< Bit for DDR clock confiuration indication. +}; + +//!@brief Flash Type Definition +enum +{ + kFlexSpiDeviceType_SerialNOR = 1, //!< Flash devices are Serial NOR + kFlexSpiDeviceType_SerialNAND = 2, //!< Flash devices are Serial NAND + kFlexSpiDeviceType_SerialRAM = 3, //!< Flash devices are Serial RAM/HyperFLASH + kFlexSpiDeviceType_MCP_NOR_NAND = 0x12, //!< Flash device is MCP device, A1 is Serial NOR, A2 is Serial NAND + kFlexSpiDeviceType_MCP_NOR_RAM = 0x13, //!< Flash deivce is MCP device, A1 is Serial NOR, A2 is Serial RAMs +}; + +//!@brief Flash Pad Definitions +enum +{ + kSerialFlash_1Pad = 1, + kSerialFlash_2Pads = 2, + kSerialFlash_4Pads = 4, + kSerialFlash_8Pads = 8, +}; + +//!@brief FlexSPI LUT Sequence structure +typedef struct _lut_sequence +{ + uint8_t seqNum; //!< Sequence Number, valid number: 1-16 + uint8_t seqId; //!< Sequence Index, valid number: 0-15 + uint16_t reserved; +} flexspi_lut_seq_t; + +//!@brief Flash Configuration Command Type +enum +{ + kDeviceConfigCmdType_Generic, //!< Generic command, for example: configure dummy cycles, drive strength, etc + kDeviceConfigCmdType_QuadEnable, //!< Quad Enable command + kDeviceConfigCmdType_Spi2Xpi, //!< Switch from SPI to DPI/QPI/OPI mode + kDeviceConfigCmdType_Xpi2Spi, //!< Switch from DPI/QPI/OPI to SPI mode + kDeviceConfigCmdType_Spi2NoCmd, //!< Switch to 0-4-4/0-8-8 mode + kDeviceConfigCmdType_Reset, //!< Reset device command +}; + +//!@brief FlexSPI Memory Configuration Block +typedef struct _FlexSPIConfig +{ + uint32_t tag; //!< [0x000-0x003] Tag, fixed value 0x42464346UL + uint32_t version; //!< [0x004-0x007] Version,[31:24] -'V', [23:16] - Major, [15:8] - Minor, [7:0] - bugfix + uint32_t reserved0; //!< [0x008-0x00b] Reserved for future use + uint8_t readSampleClkSrc; //!< [0x00c-0x00c] Read Sample Clock Source, valid value: 0/1/3 + uint8_t csHoldTime; //!< [0x00d-0x00d] CS hold time, default value: 3 + uint8_t csSetupTime; //!< [0x00e-0x00e] CS setup time, default value: 3 + uint8_t columnAddressWidth; //!< [0x00f-0x00f] Column Address with, for HyperBus protocol, it is fixed to 3, For + //! Serial NAND, need to refer to datasheet + uint8_t deviceModeCfgEnable; //!< [0x010-0x010] Device Mode Configure enable flag, 1 - Enable, 0 - Disable + uint8_t deviceModeType; //!< [0x011-0x011] Specify the configuration command type:Quad Enable, DPI/QPI/OPI switch, + //! Generic configuration, etc. + uint16_t waitTimeCfgCommands; //!< [0x012-0x013] Wait time for all configuration commands, unit: 100us, Used for + //! DPI/QPI/OPI switch or reset command + flexspi_lut_seq_t deviceModeSeq; //!< [0x014-0x017] Device mode sequence info, [7:0] - LUT sequence id, [15:8] - LUt + //! sequence number, [31:16] Reserved + uint32_t deviceModeArg; //!< [0x018-0x01b] Argument/Parameter for device configuration + uint8_t configCmdEnable; //!< [0x01c-0x01c] Configure command Enable Flag, 1 - Enable, 0 - Disable + uint8_t configModeType[3]; //!< [0x01d-0x01f] Configure Mode Type, similar as deviceModeTpe + flexspi_lut_seq_t + configCmdSeqs[3]; //!< [0x020-0x02b] Sequence info for Device Configuration command, similar as deviceModeSeq + uint32_t reserved1; //!< [0x02c-0x02f] Reserved for future use + uint32_t configCmdArgs[3]; //!< [0x030-0x03b] Arguments/Parameters for device Configuration commands + uint32_t reserved2; //!< [0x03c-0x03f] Reserved for future use + uint32_t controllerMiscOption; //!< [0x040-0x043] Controller Misc Options, see Misc feature bit definitions for more + //! details + uint8_t deviceType; //!< [0x044-0x044] Device Type: See Flash Type Definition for more details + uint8_t sflashPadType; //!< [0x045-0x045] Serial Flash Pad Type: 1 - Single, 2 - Dual, 4 - Quad, 8 - Octal + uint8_t serialClkFreq; //!< [0x046-0x046] Serial Flash Frequencey, device specific definitions, See System Boot + //! Chapter for more details + uint8_t lutCustomSeqEnable; //!< [0x047-0x047] LUT customization Enable, it is required if the program/erase cannot + //! be done using 1 LUT sequence, currently, only applicable to HyperFLASH + uint32_t reserved3[2]; //!< [0x048-0x04f] Reserved for future use + uint32_t sflashA1Size; //!< [0x050-0x053] Size of Flash connected to A1 + uint32_t sflashA2Size; //!< [0x054-0x057] Size of Flash connected to A2 + uint32_t sflashB1Size; //!< [0x058-0x05b] Size of Flash connected to B1 + uint32_t sflashB2Size; //!< [0x05c-0x05f] Size of Flash connected to B2 + uint32_t csPadSettingOverride; //!< [0x060-0x063] CS pad setting override value + uint32_t sclkPadSettingOverride; //!< [0x064-0x067] SCK pad setting override value + uint32_t dataPadSettingOverride; //!< [0x068-0x06b] data pad setting override value + uint32_t dqsPadSettingOverride; //!< [0x06c-0x06f] DQS pad setting override value + uint32_t timeoutInMs; //!< [0x070-0x073] Timeout threshold for read status command + uint32_t commandInterval; //!< [0x074-0x077] CS deselect interval between two commands + uint16_t dataValidTime[2]; //!< [0x078-0x07b] CLK edge to data valid time for PORT A and PORT B, in terms of 0.1ns + uint16_t busyOffset; //!< [0x07c-0x07d] Busy offset, valid value: 0-31 + uint16_t busyBitPolarity; //!< [0x07e-0x07f] Busy flag polarity, 0 - busy flag is 1 when flash device is busy, 1 - + //! busy flag is 0 when flash device is busy + uint32_t lookupTable[64]; //!< [0x080-0x17f] Lookup table holds Flash command sequences + flexspi_lut_seq_t lutCustomSeq[12]; //!< [0x180-0x1af] Customizable LUT Sequences + uint32_t reserved4[4]; //!< [0x1b0-0x1bf] Reserved for future use +} flexspi_mem_config_t; + +/* */ +#define NOR_CMD_INDEX_READ CMD_INDEX_READ //!< 0 +#define NOR_CMD_INDEX_READSTATUS CMD_INDEX_READSTATUS //!< 1 +#define NOR_CMD_INDEX_WRITEENABLE CMD_INDEX_WRITEENABLE //!< 2 +#define NOR_CMD_INDEX_ERASESECTOR 3 //!< 3 +#define NOR_CMD_INDEX_PAGEPROGRAM CMD_INDEX_WRITE //!< 4 +#define NOR_CMD_INDEX_CHIPERASE 5 //!< 5 +#define NOR_CMD_INDEX_DUMMY 6 //!< 6 +#define NOR_CMD_INDEX_ERASEBLOCK 7 //!< 7 + +#define NOR_CMD_LUT_SEQ_IDX_READ CMD_LUT_SEQ_IDX_READ //!< 0 READ LUT sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_READSTATUS \ + CMD_LUT_SEQ_IDX_READSTATUS //!< 1 Read Status LUT sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_READSTATUS_XPI \ + 2 //!< 2 Read status DPI/QPI/OPI sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_WRITEENABLE \ + CMD_LUT_SEQ_IDX_WRITEENABLE //!< 3 Write Enable sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_WRITEENABLE_XPI \ + 4 //!< 4 Write Enable DPI/QPI/OPI sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_ERASESECTOR 5 //!< 5 Erase Sector sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_ERASEBLOCK 8 //!< 8 Erase Block sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM \ + CMD_LUT_SEQ_IDX_WRITE //!< 9 Program sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_CHIPERASE 11 //!< 11 Chip Erase sequence in lookupTable id stored in config block +#define NOR_CMD_LUT_SEQ_IDX_READ_SFDP 13 //!< 13 Read SFDP sequence in lookupTable id stored in config block +#define NOR_CMD_LUT_SEQ_IDX_RESTORE_NOCMD \ + 14 //!< 14 Restore 0-4-4/0-8-8 mode sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_EXIT_NOCMD \ + 15 //!< 15 Exit 0-4-4/0-8-8 mode sequence id in lookupTable stored in config blobk + +/* + * Serial NOR configuration block + */ +typedef struct _flexspi_nor_config +{ + flexspi_mem_config_t memConfig; //!< Common memory configuration info via FlexSPI + uint32_t pageSize; //!< Page size of Serial NOR + uint32_t sectorSize; //!< Sector size of Serial NOR + uint8_t ipcmdSerialClkFreq; //!< Clock frequency for IP command + uint8_t isUniformBlockSize; //!< Sector/Block size is the same + uint8_t reserved0[2]; //!< Reserved for future use + uint8_t serialNorType; //!< Serial NOR Flash type: 0/1/2/3 + uint8_t needExitNoCmdMode; //!< Need to exit NoCmd mode before other IP command + uint8_t halfClkForNonReadCmd; //!< Half the Serial Clock for non-read command: true/false + uint8_t needRestoreNoCmdMode; //!< Need to Restore NoCmd mode after IP commmand execution + uint32_t blockSize; //!< Block size + uint32_t reserve2[11]; //!< Reserved for future use +} flexspi_nor_config_t; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif +#endif /* __EVKMIMXRT1064_FLEXSPI_NOR_CONFIG__ */ diff --git a/ports/mimxrt/boards/MIMXRT1064_EVK/flash_config.c b/ports/mimxrt/boards/MIMXRT1064_EVK/flash_config.c new file mode 100644 index 0000000000000..bfb1c2d59aa25 --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1064_EVK/flash_config.c @@ -0,0 +1,49 @@ +/* + * Copyright 2018 NXP + * All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "evkmimxrt1064_flexspi_nor_config.h" + +/* Component ID definition, used by tools. */ +#ifndef FSL_COMPONENT_ID +#define FSL_COMPONENT_ID "platform.drivers.xip_board" +#endif + +/******************************************************************************* + * Code + ******************************************************************************/ +#if defined(XIP_BOOT_HEADER_ENABLE) && (XIP_BOOT_HEADER_ENABLE == 1) +#if defined(__CC_ARM) || defined(__ARMCC_VERSION) || defined(__GNUC__) +__attribute__((section(".boot_hdr.conf"))) +#elif defined(__ICCARM__) +#pragma location = ".boot_hdr.conf" +#endif + +const flexspi_nor_config_t qspiflash_config = { + .memConfig = + { + .tag = FLEXSPI_CFG_BLK_TAG, + .version = FLEXSPI_CFG_BLK_VERSION, + .readSampleClkSrc = kFlexSPIReadSampleClk_LoopbackFromDqsPad, + .csHoldTime = 3u, + .csSetupTime = 3u, + // Enable DDR mode, Wordaddassable, Safe configuration, Differential clock + .sflashPadType = kSerialFlash_4Pads, + .serialClkFreq = kFlexSpiSerialClk_100MHz, + .sflashA1Size = 8u * 1024u * 1024u, + .lookupTable = + { + // Read LUTs + FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0xEB, RADDR_SDR, FLEXSPI_4PAD, 0x18), + FLEXSPI_LUT_SEQ(DUMMY_SDR, FLEXSPI_4PAD, 0x06, READ_SDR, FLEXSPI_4PAD, 0x04), + }, + }, + .pageSize = 256u, + .sectorSize = 4u * 1024u, + .blockSize = 256u * 1024u, + .isUniformBlockSize = false, +}; +#endif /* XIP_BOOT_HEADER_ENABLE */ diff --git a/ports/mimxrt/boards/MIMXRT1064_EVK/mpconfigboard.h b/ports/mimxrt/boards/MIMXRT1064_EVK/mpconfigboard.h new file mode 100644 index 0000000000000..e05c8824d26ce --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1064_EVK/mpconfigboard.h @@ -0,0 +1,8 @@ +#define MICROPY_HW_BOARD_NAME "i.MX RT1064 EVK" +#define MICROPY_HW_MCU_NAME "MIMXRT1064DVL6A" + + +// MIMXRT1064_EVK has 1 user LED +#define MICROPY_HW_LED1_PIN (GPIO_AD_B0_09) +#define MICROPY_HW_LED_ON(pin) (mp_hal_pin_low(pin)) +#define MICROPY_HW_LED_OFF(pin) (mp_hal_pin_high(pin)) diff --git a/ports/mimxrt/boards/MIMXRT1064_EVK/mpconfigboard.mk b/ports/mimxrt/boards/MIMXRT1064_EVK/mpconfigboard.mk new file mode 100644 index 0000000000000..700988919146d --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1064_EVK/mpconfigboard.mk @@ -0,0 +1,9 @@ +MCU_SERIES = MIMXRT1064 +MCU_VARIANT = MIMXRT1064DVL6A + +JLINK_PATH ?= /media/RT1064-EVK/ + +CFLAGS += -DBOARD_FLASH_SIZE=0x400000 + +deploy: $(BUILD)/firmware.bin + cp $< $(JLINK_PATH) diff --git a/ports/mimxrt/boards/MIMXRT1064_EVK/pins.c b/ports/mimxrt/boards/MIMXRT1064_EVK/pins.c new file mode 100644 index 0000000000000..d5da9c6f99275 --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1064_EVK/pins.c @@ -0,0 +1,33 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 NXP + * + * 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 "pin.h" + +static pin_af_obj_t GPIO_AD_B0_09_af[] = { + PIN_AF(GPIO1_IO09, PIN_AF_MODE_ALT5, GPIO1, 0x10B0U), +}; + +pin_obj_t GPIO_AD_B0_09 = PIN(GPIO_AD_B0_09, GPIO1, 9, GPIO_AD_B0_09_af); diff --git a/ports/mimxrt/boards/MIMXRT1064_EVK/pins.h b/ports/mimxrt/boards/MIMXRT1064_EVK/pins.h new file mode 100644 index 0000000000000..baef51c6c8612 --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1064_EVK/pins.h @@ -0,0 +1,30 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 NXP + * + * 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. + */ + +// NOTE: pins.h shall only be included in in pin.h +// hence no include guards are needed since they will be provided by pin.h + +extern pin_obj_t GPIO_AD_B0_09; diff --git a/ports/mimxrt/boards/TEENSY40/mpconfigboard.h b/ports/mimxrt/boards/TEENSY40/mpconfigboard.h index 10c8b7a6d1ffa..7d564785797ad 100644 --- a/ports/mimxrt/boards/TEENSY40/mpconfigboard.h +++ b/ports/mimxrt/boards/TEENSY40/mpconfigboard.h @@ -3,6 +3,7 @@ #define BOARD_FLASH_SIZE (2 * 1024 * 1024) -#define MICROPY_HW_LED_PINMUX IOMUXC_GPIO_B0_03_GPIO2_IO03 // D13 -#define MICROPY_HW_LED_PORT GPIO2 -#define MICROPY_HW_LED_PIN 3 +// Teensy 4.0 has 1 board LED +#define MICROPY_HW_LED1_PIN (GPIO_B0_03) +#define MICROPY_HW_LED_ON(pin) (mp_hal_pin_high(pin)) +#define MICROPY_HW_LED_OFF(pin) (mp_hal_pin_low(pin)) diff --git a/ports/mimxrt/boards/TEENSY40/pins.c b/ports/mimxrt/boards/TEENSY40/pins.c new file mode 100644 index 0000000000000..c7bfc102d4350 --- /dev/null +++ b/ports/mimxrt/boards/TEENSY40/pins.c @@ -0,0 +1,33 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Philipp Ebensberger + * + * 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 "pin.h" + +static pin_af_obj_t GPIO_B0_03_af[] = { + PIN_AF(GPIO2_IO03, PIN_AF_MODE_ALT5, GPIO2, 0x10B0U), +}; + +pin_obj_t GPIO_B0_03 = PIN(GPIO_B0_03, GPIO2, 3, GPIO_B0_03_af); diff --git a/ports/mimxrt/boards/TEENSY40/pins.h b/ports/mimxrt/boards/TEENSY40/pins.h new file mode 100644 index 0000000000000..c0b6dcdfa9576 --- /dev/null +++ b/ports/mimxrt/boards/TEENSY40/pins.h @@ -0,0 +1,30 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Philipp Ebensberger + * + * 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. + */ + +// NOTE: pins.h shall only be included in in pin.h +// hence no include guards are needed since they will be provided by pin.h + +extern pin_obj_t GPIO_B0_03; diff --git a/ports/mimxrt/led.c b/ports/mimxrt/led.c new file mode 100644 index 0000000000000..bec97dd8f7cc4 --- /dev/null +++ b/ports/mimxrt/led.c @@ -0,0 +1,102 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Philipp Ebensberger + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "fsl_gpio.h" +#include "fsl_iomuxc.h" + +#include "py/runtime.h" +#include "py/mphal.h" +#include "led.h" + +#if NUM_LEDS + +const machine_led_obj_t machine_led_obj[NUM_LEDS] = { + { + .base = {&machine_led_type}, + .led_id = 1U, + .led_pin = &MICROPY_HW_LED1_PIN, + } +}; + +void led_init(void) { + // Turn off LEDs and initialize + for (mp_int_t led = 0; led < NUM_LEDS; led++) { + const pin_obj_t *led_pin = machine_led_obj[led].led_pin; + + gpio_pin_config_t pin_config = { + .outputLogic = 1U, + .direction = kGPIO_DigitalOutput, + .interruptMode = kGPIO_NoIntmode, + }; + + GPIO_PinInit(led_pin->gpio, led_pin->pin, &pin_config); + + // ALT mode for GPIO is always 5 + IOMUXC_SetPinMux(led_pin->muxRegister, 5U, 0, 0, led_pin->configRegister, + 1U); // Software Input On Field: Input Path is determined by functionality + IOMUXC_SetPinConfig(led_pin->muxRegister, 5U, 0, 0, led_pin->configRegister, 0x10B0U); + MICROPY_HW_LED_OFF(led_pin); + } +} + +void led_state(machine_led_t led, int state) { + if (led < 1 || led > NUM_LEDS) { + return; + } + + const pin_obj_t *led_pin = machine_led_obj[led - 1].led_pin; + + if (state == 0) { + // turn LED off + MICROPY_HW_LED_OFF(led_pin); + } else { + // turn LED on + MICROPY_HW_LED_ON(led_pin); + } +} + +void led_toggle(machine_led_t led) { + if (led < 1 || led > NUM_LEDS) { + return; + } + + const pin_obj_t *led_pin = machine_led_obj[led - 1].led_pin; + mp_hal_pin_toggle(led_pin); +} + +void led_debug(int value, int delay) { + for (mp_int_t i = 0; i < NUM_LEDS; i++) { + led_state(i + 1, (value & (1 << i))); + } + mp_hal_delay_ms(delay); +} + +#else + +void led_init(void) { +} + +#endif diff --git a/ports/powerpc/uart_core.c b/ports/mimxrt/led.h similarity index 54% rename from ports/powerpc/uart_core.c rename to ports/mimxrt/led.h index b0dcd031a5ad2..35418321d173d 100644 --- a/ports/powerpc/uart_core.c +++ b/ports/mimxrt/led.h @@ -3,7 +3,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2019, Michael Neuling, IBM Corporation. + * Copyright (c) 2020 Philipp Ebensberger * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -24,50 +24,33 @@ * THE SOFTWARE. */ -#include -#include +#ifndef MICROPY_INCLUDED_MIMXRT_LED_H +#define MICROPY_INCLUDED_MIMXRT_LED_H -#include "py/mpconfig.h" -#include "uart_potato.h" -#include "uart_lpc_serial.h" +#include "pin.h" -static int lpc_console; -static int potato_console; +#if defined(MICROPY_HW_LED1_PIN) +#define NUM_LEDS (1) +#else +#define NUM_LEDS (0) +#endif -void uart_init_ppc(int lpc) { - lpc_console = lpc; +typedef enum { + MACHINE_BOARD_LED = 1, +} machine_led_t; - if (!lpc_console) { - potato_console = 1; +typedef struct _machine_led_obj_t { + mp_obj_base_t base; + mp_uint_t led_id; + const pin_obj_t *led_pin; +} machine_led_obj_t; - potato_uart_init(); - } else { - lpc_uart_init(); - } -} +void led_init(void); +void led_state(machine_led_t led, int state); +void led_toggle(machine_led_t led); +void led_debug(int value, int delay); -// Receive single character -int mp_hal_stdin_rx_chr(void) { - unsigned char c = 0; - if (lpc_console) { - c = lpc_uart_read(); - } else if (potato_console) { - c = potato_uart_read(); - } - return c; -} +extern const mp_obj_type_t machine_led_type; +extern const machine_led_obj_t machine_led_obj[NUM_LEDS]; -// Send string of given length -void mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) { - if (lpc_console) { - int i; - for (i = 0; i < len; i++) { - lpc_uart_write(str[i]); - } - } else if (potato_console) { - int i; - for (i = 0; i < len; i++) { - potato_uart_write(str[i]); - } - } -} +#endif // MICROPY_INCLUDED_MIMXRT_LED_H diff --git a/ports/mimxrt/machine_led.c b/ports/mimxrt/machine_led.c new file mode 100644 index 0000000000000..07c7b180a10e6 --- /dev/null +++ b/ports/mimxrt/machine_led.c @@ -0,0 +1,91 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Philipp Ebensberger + * + * 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 "led.h" + +#if NUM_LEDS + +STATIC void led_obj_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + machine_led_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "LED(%u)", self->led_id); +} + +STATIC mp_obj_t led_obj_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); + + // Extract arguments + mp_int_t led_id = mp_obj_get_int(args[0]); + + // Check led id is in range + if (!(1 <= led_id && led_id <= NUM_LEDS)) { + nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "LED(%d) doesn't exist", led_id)); + } + + // Return reference to static object + return MP_OBJ_FROM_PTR(&machine_led_obj[led_id - 1]); +} + +STATIC mp_obj_t led_obj_on(mp_obj_t self_in) { + machine_led_obj_t *self = MP_OBJ_TO_PTR(self_in); + MICROPY_HW_LED_ON(self->led_pin); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(led_obj_on_obj, led_obj_on); + +STATIC mp_obj_t led_obj_off(mp_obj_t self_in) { + machine_led_obj_t *self = MP_OBJ_TO_PTR(self_in); + MICROPY_HW_LED_OFF(self->led_pin); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(led_obj_off_obj, led_obj_off); + +STATIC mp_obj_t led_obj_toggle(mp_obj_t self_in) { + machine_led_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_hal_pin_toggle(self->led_pin); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(led_obj_toggle_obj, led_obj_toggle); + +STATIC const mp_rom_map_elem_t led_locals_dict_table[] = { + {MP_ROM_QSTR(MP_QSTR_on), MP_ROM_PTR(&led_obj_on_obj)}, + {MP_ROM_QSTR(MP_QSTR_off), MP_ROM_PTR(&led_obj_off_obj)}, + {MP_ROM_QSTR(MP_QSTR_toggle), MP_ROM_PTR(&led_obj_toggle_obj)}, +}; + +STATIC MP_DEFINE_CONST_DICT(led_locals_dict, led_locals_dict_table); + +const mp_obj_type_t machine_led_type = { + {&mp_type_type}, + .name = MP_QSTR_LED, + .print = led_obj_print, + .make_new = led_obj_make_new, + .locals_dict = (mp_obj_dict_t *)&led_locals_dict, +}; + +#endif diff --git a/ports/mimxrt/main.c b/ports/mimxrt/main.c index d94bf24412452..e06283a8ab37b 100644 --- a/ports/mimxrt/main.c +++ b/ports/mimxrt/main.c @@ -33,6 +33,7 @@ #include "lib/utils/gchelper.h" #include "lib/utils/pyexec.h" #include "tusb.h" +#include "led.h" extern uint8_t _sstack, _estack, _gc_heap_start, _gc_heap_end; @@ -41,6 +42,7 @@ void board_init(void); int main(void) { board_init(); tusb_init(); + led_init(); mp_stack_set_top(&_estack); mp_stack_set_limit(&_estack - &_sstack - 1024); diff --git a/ports/mimxrt/modmachine.c b/ports/mimxrt/modmachine.c index 747b7bc27f56d..a6aab7b4aba53 100644 --- a/ports/mimxrt/modmachine.c +++ b/ports/mimxrt/modmachine.c @@ -27,6 +27,7 @@ #include "py/runtime.h" #include "extmod/machine_mem.h" +#include "led.h" #include CPU_HEADER_H @@ -48,6 +49,9 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_mem8), MP_ROM_PTR(&machine_mem8_obj) }, { MP_ROM_QSTR(MP_QSTR_mem16), MP_ROM_PTR(&machine_mem16_obj) }, { MP_ROM_QSTR(MP_QSTR_mem32), MP_ROM_PTR(&machine_mem32_obj) }, + #if NUM_LEDS + { MP_ROM_QSTR(MP_QSTR_LED), MP_ROM_PTR(&machine_led_type) }, + #endif }; STATIC MP_DEFINE_CONST_DICT(machine_module_globals, machine_module_globals_table); diff --git a/ports/mimxrt/mphalport.h b/ports/mimxrt/mphalport.h index e1cf84f6c2cc7..2b9e49432cc0f 100644 --- a/ports/mimxrt/mphalport.h +++ b/ports/mimxrt/mphalport.h @@ -29,6 +29,10 @@ #include +#define mp_hal_pin_high(p) (GPIO_PinWrite(p->gpio, p->pin, 1U)) +#define mp_hal_pin_low(p) (GPIO_PinWrite(p->gpio, p->pin, 0U)) +#define mp_hal_pin_toggle(p) (GPIO_PortToggle(p->gpio, (1 << p->pin))) + extern volatile uint32_t systick_ms; void mp_hal_set_interrupt_char(int c); diff --git a/ports/mimxrt/pin.c b/ports/mimxrt/pin.c new file mode 100644 index 0000000000000..b30f4be41ceae --- /dev/null +++ b/ports/mimxrt/pin.c @@ -0,0 +1,37 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Philipp Ebensberger + * + * 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 "pin.h" + +const mp_obj_type_t pin_type = { + .base = {&mp_type_type}, + .name = MP_QSTR_Pin, +}; + +const mp_obj_type_t pin_af_type = { + {&mp_type_type}, + .name = MP_QSTR_PinAF, +}; diff --git a/ports/mimxrt/pin.h b/ports/mimxrt/pin.h new file mode 100644 index 0000000000000..cfb1e6b572502 --- /dev/null +++ b/ports/mimxrt/pin.h @@ -0,0 +1,100 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Philipp Ebensberger + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MICROPY_INCLUDED_MIMXRT_PIN_H +#define MICROPY_INCLUDED_MIMXRT_PIN_H + +#include "fsl_gpio.h" +#include "py/obj.h" + +enum { + PIN_MODE_IN = 0, + PIN_MODE_OUT, + PIN_MODE_ALT, +}; + +enum { + PIN_AF_MODE_ALT1 = 1, + PIN_AF_MODE_ALT2, + PIN_AF_MODE_ALT3, + PIN_AF_MODE_ALT4, + PIN_AF_MODE_ALT5, + PIN_AF_MODE_ALT6, + PIN_AF_MODE_ALT7, + PIN_AF_MODE_ALT8, +}; + +typedef struct { + mp_obj_base_t base; + qstr name; // port name + uint32_t af_mode; // alternate function + void *instance; // pointer to peripheral instance for alternate function + uint32_t pad_config; // pad configuration for alternate function +} pin_af_obj_t; + +typedef struct { + mp_obj_base_t base; + qstr name; // pad name + GPIO_Type *gpio; // gpio instance for pin + uint32_t pin; // pin number + uint32_t muxRegister; + uint32_t configRegister; + uint32_t mode; // current pin mode + uint32_t af_mode; // current alternate function mode + size_t af_list_len; // length of available alternate functions list + const pin_af_obj_t *af_list; // pointer tolist with alternate functions +} pin_obj_t; + +extern const mp_obj_type_t pin_type; +extern const mp_obj_type_t pin_af_type; + +#define PIN_AF(_name, _af_mode, _instance, _pad_config) \ + { \ + .base = { &pin_af_type }, \ + .name = MP_QSTR_##_name, \ + .af_mode = (uint32_t)(_af_mode), \ + .instance = (void *)(_instance), \ + .pad_config = (uint32_t)(_pad_config), \ + } \ + +#define PIN(_name, _gpio, _pin, _af_list) \ + { \ + .base = { &pin_type }, \ + .name = MP_QSTR_##_name, \ + .gpio = (_gpio), \ + .pin = (uint32_t)(_pin), \ + .muxRegister = (uint32_t)&(IOMUXC->SW_MUX_CTL_PAD[kIOMUXC_SW_MUX_CTL_PAD_##_name]), \ + .configRegister = (uint32_t)&(IOMUXC->SW_PAD_CTL_PAD[kIOMUXC_SW_PAD_CTL_PAD_##_name]), \ + .mode = PIN_MODE_IN, \ + .af_mode = PIN_AF_MODE_ALT5, \ + .af_list_len = (size_t)(sizeof((_af_list)) / sizeof(pin_af_obj_t)), \ + .af_list = (_af_list), \ + } \ + +// Include board specific pins +#include "pins.h" + +#endif // MICROPY_INCLUDED_MIMXRT_PIN_H diff --git a/ports/mimxrt/tusb_config.h b/ports/mimxrt/tusb_config.h index f2367c2044384..c7ec05e632323 100644 --- a/ports/mimxrt/tusb_config.h +++ b/ports/mimxrt/tusb_config.h @@ -30,7 +30,8 @@ #define CFG_TUSB_OS (OPT_OS_NONE) #define CFG_TUD_CDC (1) -#define CFG_TUD_CDC_RX_BUFSIZE (256) -#define CFG_TUD_CDC_TX_BUFSIZE (256) +#define CFG_TUD_CDC_RX_BUFSIZE (512) +#define CFG_TUD_CDC_TX_BUFSIZE (512) +#define CFG_TUD_CDC_EPSIZE (512) #endif // MICROPY_INCLUDED_MIMXRT_TUSB_CONFIG_H diff --git a/ports/mimxrt/tusb_port.c b/ports/mimxrt/tusb_port.c index 70f8ef527cd0d..f6b09a362d18a 100644 --- a/ports/mimxrt/tusb_port.c +++ b/ports/mimxrt/tusb_port.c @@ -39,6 +39,7 @@ #define USBD_CDC_EP_OUT (0x02) #define USBD_CDC_EP_IN (0x82) #define USBD_CDC_CMD_MAX_SIZE (8) +#define USBD_CDC_IN_OUT_MAX_SIZE (512) #define USBD_STR_0 (0x00) #define USBD_STR_MANUF (0x01) @@ -70,7 +71,7 @@ static const uint8_t usbd_desc_cfg[USBD_DESC_LEN] = { TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, USBD_MAX_POWER_MA), TUD_CDC_DESCRIPTOR(USBD_ITF_CDC, USBD_STR_CDC, USBD_CDC_EP_CMD, - USBD_CDC_CMD_MAX_SIZE, USBD_CDC_EP_OUT, USBD_CDC_EP_IN, CFG_TUD_CDC_RX_BUFSIZE), + USBD_CDC_CMD_MAX_SIZE, USBD_CDC_EP_OUT, USBD_CDC_EP_IN, USBD_CDC_IN_OUT_MAX_SIZE), }; static const char *const usbd_desc_str[] = { diff --git a/ports/minimal/Makefile b/ports/minimal/Makefile index 5801d64087b21..bce544ec176b5 100644 --- a/ports/minimal/Makefile +++ b/ports/minimal/Makefile @@ -49,10 +49,13 @@ SRC_C = \ lib/utils/printf.c \ lib/utils/stdout_helpers.c \ lib/utils/pyexec.c \ - lib/libc/string0.c \ lib/mp-readline/readline.c \ $(BUILD)/_frozen_mpy.c \ +ifeq ($(CROSS), 1) +SRC_C += lib/libc/string0.c +endif + OBJ = $(PY_CORE_O) $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) ifeq ($(CROSS), 1) diff --git a/ports/minimal/main.c b/ports/minimal/main.c index 6943ee7589f39..aa6d10ee3750c 100644 --- a/ports/minimal/main.c +++ b/ports/minimal/main.c @@ -60,6 +60,7 @@ int main(int argc, char **argv) { return 0; } +#if MICROPY_ENABLE_GC void gc_collect(void) { // WARNING: This gc_collect implementation doesn't try to get root // pointers from CPU registers, and thus may function incorrectly. @@ -69,6 +70,7 @@ void gc_collect(void) { gc_collect_end(); gc_dump_info(); } +#endif mp_lexer_t *mp_lexer_new_from_file(const char *filename) { mp_raise_OSError(MP_ENOENT); diff --git a/ports/minimal/mpconfigport.h b/ports/minimal/mpconfigport.h index 2507d0124d1ca..b34217f68019a 100644 --- a/ports/minimal/mpconfigport.h +++ b/ports/minimal/mpconfigport.h @@ -11,31 +11,19 @@ #define MICROPY_QSTR_EXTRA_POOL mp_qstr_frozen_const_pool #define MICROPY_ALLOC_PATH_MAX (256) #define MICROPY_ALLOC_PARSE_CHUNK_INIT (16) -#define MICROPY_EMIT_X64 (0) -#define MICROPY_EMIT_THUMB (0) -#define MICROPY_EMIT_INLINE_THUMB (0) -#define MICROPY_COMP_MODULE_CONST (0) #define MICROPY_COMP_CONST (0) #define MICROPY_COMP_DOUBLE_TUPLE_ASSIGN (0) -#define MICROPY_COMP_TRIPLE_TUPLE_ASSIGN (0) -#define MICROPY_MEM_STATS (0) -#define MICROPY_DEBUG_PRINTERS (0) #define MICROPY_ENABLE_GC (1) #define MICROPY_GC_ALLOC_THRESHOLD (0) -#define MICROPY_REPL_EVENT_DRIVEN (0) #define MICROPY_HELPER_REPL (1) -#define MICROPY_HELPER_LEXER_UNIX (0) -#define MICROPY_ENABLE_SOURCE_LINE (0) -#define MICROPY_ENABLE_DOC_STRING (0) #define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_TERSE) #define MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG (0) #define MICROPY_PY_ASYNC_AWAIT (0) +#define MICROPY_PY_ASSIGN_EXPR (0) #define MICROPY_PY_BUILTINS_BYTEARRAY (0) #define MICROPY_PY_BUILTINS_DICT_FROMKEYS (0) -#define MICROPY_PY_BUILTINS_MEMORYVIEW (0) #define MICROPY_PY_BUILTINS_ENUMERATE (0) #define MICROPY_PY_BUILTINS_FILTER (0) -#define MICROPY_PY_BUILTINS_FROZENSET (0) #define MICROPY_PY_BUILTINS_REVERSED (0) #define MICROPY_PY_BUILTINS_SET (0) #define MICROPY_PY_BUILTINS_SLICE (0) @@ -48,34 +36,19 @@ #define MICROPY_PY_ARRAY (0) #define MICROPY_PY_ATTRTUPLE (0) #define MICROPY_PY_COLLECTIONS (0) -#define MICROPY_PY_MATH (0) -#define MICROPY_PY_CMATH (0) #define MICROPY_PY_IO (0) #define MICROPY_PY_STRUCT (0) #define MICROPY_PY_SYS (0) #define MICROPY_MODULE_FROZEN_MPY (1) #define MICROPY_CPYTHON_COMPAT (0) #define MICROPY_MODULE_GETATTR (0) -#define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_NONE) -#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_NONE) // type definitions for the specific machine -#define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)((mp_uint_t)(p) | 1)) - -// This port is intended to be 32-bit, but unfortunately, int32_t for -// different targets may be defined in different ways - either as int -// or as long. This requires different printf formatting specifiers -// to print such value. So, we avoid int32_t and use int directly. -#define UINT_FMT "%u" -#define INT_FMT "%d" -typedef int mp_int_t; // must be pointer size -typedef unsigned mp_uint_t; // must be pointer size - +typedef intptr_t mp_int_t; // must be pointer size +typedef uintptr_t mp_uint_t; // must be pointer size typedef long mp_off_t; -#define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn_cooked(str, len) - // extra built in names to add to the global namespace #define MICROPY_PORT_BUILTINS \ { MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&mp_builtin_open_obj) }, diff --git a/ports/nrf/Makefile b/ports/nrf/Makefile index 23d5cd20d0d00..e5a1b471a35d7 100644 --- a/ports/nrf/Makefile +++ b/ports/nrf/Makefile @@ -39,7 +39,9 @@ endif QSTR_DEFS = qstrdefsport.h $(BUILD)/pins_qstr.h # MicroPython feature configurations +ifeq ($(DEBUG), 0) MICROPY_ROM_TEXT_COMPRESSION ?= 1 +endif # include py core make definitions include ../../py/py.mk @@ -111,7 +113,7 @@ endif CFLAGS += $(CFLAGS_MCU_$(MCU_SERIES)) -CFLAGS += $(INC) -Wall -Werror -g -ansi -std=c11 -nostdlib $(COPT) $(NRF_DEFINES) $(CFLAGS_MOD) +CFLAGS += $(INC) -Wall -Werror -g -ansi -std=c11 -nostdlib $(COPT) $(NRF_DEFINES) $(CFLAGS_MOD) $(CFLAGS_EXTRA) CFLAGS += -fno-strict-aliasing CFLAGS += -Iboards/$(BOARD) CFLAGS += -DNRF5_HAL_H='<$(MCU_VARIANT)_hal.h>' @@ -443,11 +445,11 @@ flash: deploy $(BUILD)/$(OUTPUT_FILENAME).elf: $(OBJ) $(ECHO) "LINK $@" - $(Q)$(CC) $(LDFLAGS) -o $@ $(OBJ) $(LIBS) + $(Q)$(CC) $(LDFLAGS) -o $@ $(OBJ) $(LDFLAGS_MOD) $(LIBS) $(Q)$(SIZE) $@ # List of sources for qstr extraction -SRC_QSTR += $(SRC_C) $(SRC_LIB) $(DRIVERS_SRC_C) $(SRC_BOARD_MODULES) +SRC_QSTR += $(SRC_C) $(SRC_LIB) $(DRIVERS_SRC_C) $(SRC_BOARD_MODULES) $(SRC_MOD) # Append any auto-generated sources that are needed by sources listed in # SRC_QSTR diff --git a/ports/nrf/README.md b/ports/nrf/README.md index b5f39267ebf2f..b08a034561e0c 100644 --- a/ports/nrf/README.md +++ b/ports/nrf/README.md @@ -41,8 +41,10 @@ This is a port of MicroPython to the Nordic Semiconductor nRF series of chips. * [PCA10056](http://www.nordicsemi.com/eng/Products/nRF52840-Preview-DK) * [PCA10059](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-Dongle) * [Particle Xenon](https://docs.particle.io/xenon/) + * [nRF52840 MDK USB Dongle](boards/nrf52840-mdk-usb-dongle/README.md) * nRF9160 * [PCA10090](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF9160-DK) + * [Actinius Icarus](https://www.actinius.com/icarus) ## Compile and Flash @@ -135,6 +137,7 @@ pca10056 | s140 | Peripheral and Central | [Segge pca10059 | s140 | Peripheral and Central | Manual, SWDIO and SWCLK solder points on the sides. particle_xenon | s140 | Peripheral and Central | [Black Magic Probe](#black-magic-probe-targets) pca10090 | None (bsdlib.a) | None (LTE/GNSS) | [Segger](#segger-targets) +actinius_icarus | None (bsdlib.a) | None (LTE/GNSS) | [Segger](#segger-targets) ## IDAP-M/IDAP-Link Targets diff --git a/ports/nrf/boards/actinius_icarus/mpconfigboard.h b/ports/nrf/boards/actinius_icarus/mpconfigboard.h new file mode 100644 index 0000000000000..e99d731df941d --- /dev/null +++ b/ports/nrf/boards/actinius_icarus/mpconfigboard.h @@ -0,0 +1,81 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Actinius + * + * 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 ACTINIUS_ICARUS + +#define MICROPY_HW_BOARD_NAME "Actinius Icarus" +#define MICROPY_HW_MCU_NAME "NRF9160" +#define MICROPY_PY_SYS_PLATFORM "nrf9160" + +#define MICROPY_PY_MACHINE_UART (1) +#define MICROPY_PY_MACHINE_HW_PWM (0) +#define MICROPY_PY_MACHINE_HW_SPI (1) +#define MICROPY_PY_MACHINE_TIMER (1) +#define MICROPY_PY_MACHINE_RTCOUNTER (1) +#define MICROPY_PY_MACHINE_I2C (1) +#define MICROPY_PY_MACHINE_ADC (0) +#define MICROPY_PY_MACHINE_TEMP (0) +#define MICROPY_PY_RANDOM_HW_RNG (0) + +#define MICROPY_MBFS (0) +#define MICROPY_VFS (1) + +#define MICROPY_HW_HAS_LED (1) +#define MICROPY_HW_HAS_SWITCH (1) +#define MICROPY_HW_HAS_FLASH (0) +#define MICROPY_HW_HAS_SDCARD (0) +#define MICROPY_HW_HAS_MMA7660 (0) +#define MICROPY_HW_HAS_LIS3DSH (0) +#define MICROPY_HW_HAS_LCD (0) +#define MICROPY_HW_ENABLE_RNG (0) +#define MICROPY_HW_ENABLE_RTC (0) +#define MICROPY_HW_ENABLE_TIMER (0) +#define MICROPY_HW_ENABLE_SERVO (0) +#define MICROPY_HW_ENABLE_DAC (0) +#define MICROPY_HW_ENABLE_CAN (0) + +#define MICROPY_HW_LED_TRICOLOR (1) +#define MICROPY_HW_LED_PULLUP (1) + +#define MICROPY_HW_LED_RED (10) // LED1 / RED +#define MICROPY_HW_LED_GREEN (11) // LED2 / GREEN +#define MICROPY_HW_LED_BLUE (12) // LED3 / BLUE + +// UART config +#define MICROPY_HW_UART1_RX (6) +#define MICROPY_HW_UART1_TX (9) +#define MICROPY_HW_UART1_CTS (25) +#define MICROPY_HW_UART1_RTS (7) +#define MICROPY_HW_UART1_HWFC (1) + +// SPI0 config +#define MICROPY_HW_SPI0_NAME "SPI0" + +#define MICROPY_HW_SPI0_SCK (20) +#define MICROPY_HW_SPI0_MOSI (21) +#define MICROPY_HW_SPI0_MISO (22) + +#define HELP_TEXT_BOARD_LED "1,2,3" diff --git a/ports/nrf/boards/actinius_icarus/mpconfigboard.mk b/ports/nrf/boards/actinius_icarus/mpconfigboard.mk new file mode 100644 index 0000000000000..c1e05fd306272 --- /dev/null +++ b/ports/nrf/boards/actinius_icarus/mpconfigboard.mk @@ -0,0 +1,6 @@ +MCU_SERIES = m33 +MCU_VARIANT = nrf91 +MCU_SUB_VARIANT = nrf9160 +LD_FILES += boards/nrf9160_1M_256k.ld + +NRF_DEFINES += -DNRF9160_XXAA -DNRF_TRUSTZONE_NONSECURE diff --git a/ports/nrf/boards/actinius_icarus/pins.csv b/ports/nrf/boards/actinius_icarus/pins.csv new file mode 100644 index 0000000000000..50c284dfdc5cc --- /dev/null +++ b/ports/nrf/boards/actinius_icarus/pins.csv @@ -0,0 +1,32 @@ +P0,P0 +P1,P1 +P2,P2 +P3,P3 +P4,P4 +BUTTON,P5 +UART_RX,P6 +UART_RTS,P7 +SIM_SELECT,P8 +UART_TX,P9 +LED1,P10 +LED2,P11 +LED3,P12 +BAT_SENSE,P13 +A1,P14 +A2,P15 +A3,P16 +A4,P17 +A5,P18 +A6,P19 +SPI_SCK,P20 +SPI_MOSI,P21 +SPI_MISO,P22 +P23,P23 +P24,P24 +UART_CTS,P25 +I2C_SDA,P26 +I2C_SCL,P27 +ACCEL_INT1,P28 +ACCEL_INT2,P29 +P30,P30 +P31,P31 diff --git a/ports/nrf/boards/make-pins.py b/ports/nrf/boards/make-pins.py index 56c56ae5f42f3..347ed15b21b31 100644 --- a/ports/nrf/boards/make-pins.py +++ b/ports/nrf/boards/make-pins.py @@ -381,7 +381,10 @@ def main(): default="build/pins_af.py", ) parser.add_argument( - "-b", "--board", dest="board_filename", help="Specifies the board file", + "-b", + "--board", + dest="board_filename", + help="Specifies the board file", ) parser.add_argument( "-p", diff --git a/ports/nrf/boards/memory.ld b/ports/nrf/boards/memory.ld index c95daf3d94fa0..f1f9a2a4c48b8 100644 --- a/ports/nrf/boards/memory.ld +++ b/ports/nrf/boards/memory.ld @@ -1,14 +1,17 @@ /* Flash layout: softdevice | application | filesystem */ /* RAM layout: softdevice RAM | application RAM */ -_sd_size = DEFINED(_sd_size) ? _sd_size : 0; + +_ram_start = DEFINED(_ram_start) ? _ram_start : 0x20000000; +_flash_start = DEFINED(_flash_start) ? _flash_start : 0; +_sd_size = DEFINED(_sd_size) ? _sd_size : _flash_start; _sd_ram = DEFINED(_sd_ram) ? _sd_ram : 0; _fs_size = DEFINED(_fs_size) ? _fs_size : 64K; /* TODO: set to 0 if not using the filesystem */ _app_size = _flash_size - _sd_size - _fs_size; _app_start = _sd_size; _fs_start = _sd_size + _app_size; _fs_end = _fs_start + _fs_size; -_app_ram_start = 0x20000000 + _sd_ram; +_app_ram_start = _ram_start + _sd_ram; _app_ram_size = _ram_size - _sd_ram; _heap_start = _ebss; _heap_end = _ram_end - _stack_size; diff --git a/ports/nrf/boards/nrf52840-mdk-usb-dongle/README.md b/ports/nrf/boards/nrf52840-mdk-usb-dongle/README.md new file mode 100644 index 0000000000000..c39a800d776cf --- /dev/null +++ b/ports/nrf/boards/nrf52840-mdk-usb-dongle/README.md @@ -0,0 +1,49 @@ +nRF52840 MDK USB Dongle +======================= + +The *[nRF52840 MDK USB +Dongle](https://wiki.makerdiary.com/nrf52840-mdk-usb-dongle)* is a small, +low-cost development board in a USB dongle form-factor powered by an nRF52840 +with 1MB flash and 256KB RAM. + +This device is pre-installed with [Open +Bootloader](https://wiki.makerdiary.com/nrf52840-mdk-usb-dongle/programming/), +allowing DFU upgrades over USB using Nordic [nRF +Connect](https://www.nordicsemi.com/Software-and-tools/Development-Tools/nRF-Connect-for-desktop) +or [nrfutil](https://github.com/NordicSemiconductor/pc-nrfutil/). To support +Open Bootloader, the flash and memory layout must be adjusted slightly (details +[here](https://devzone.nordicsemi.com/nordic/short-range-guides/b/getting-started/posts/nrf52840-dongle-programming-tutorial)) +from the typical nRF build; this board definition ensure the appropriate build +configuration is used for MicroPython. + + +Pinout +------ + +The [pinout +diagram](https://wiki.makerdiary.com/nrf52840-mdk-usb-dongle/#pinout-diagram) +provides an overview of the available pins and their capabilities. All pins are +available in MicroPython, using the pin numbers labelled in the diagram +(excluding the leading port number, *P0*). + +The three LEDs are available either through the usual `Pin` mechanism - pins +22-24 - or by `board.LED(n)` where n can be 1, 2 or 3. + + +Build instructions +------------------ + +Follow the standard [nRF Port build instructions](../../README.md); but use +`nrf52840-mdk-usb-dongle` as the value for `BOARD`: + + make BOARD=nrf52840-mdk-usb-dongle + +The build artifacts will be created in `build-nrf52840-mdk-usb-dongle`. Once +built, the easiest way to deploy to the device is to open `firmware.hex` using +*nRF Connect* and select *Write*. Detailed instructions can be found on the +[developer +wiki](https://wiki.makerdiary.com/nrf52840-mdk-usb-dongle/programming/). + +**Note** that the regular method of deployment for the MicroPython nRF port +(using `make deploy`) will *not* operate correctly and will overwrite the +bootloader. diff --git a/ports/nrf/boards/nrf52840-mdk-usb-dongle/mpconfigboard.h b/ports/nrf/boards/nrf52840-mdk-usb-dongle/mpconfigboard.h new file mode 100644 index 0000000000000..f048c39e0efdd --- /dev/null +++ b/ports/nrf/boards/nrf52840-mdk-usb-dongle/mpconfigboard.h @@ -0,0 +1,71 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Matt Trentini + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#define MICROPY_HW_BOARD_NAME "MDK-USB-DONGLE" +#define MICROPY_HW_MCU_NAME "NRF52840" +#define MICROPY_PY_SYS_PLATFORM "nrf52840-MDK-USB-Dongle" + +#define MICROPY_PY_MACHINE_UART (1) +#define MICROPY_PY_MACHINE_HW_PWM (1) +#define MICROPY_PY_MACHINE_HW_SPI (1) +#define MICROPY_PY_MACHINE_TIMER (1) +#define MICROPY_PY_MACHINE_RTCOUNTER (1) +#define MICROPY_PY_MACHINE_I2C (1) +#define MICROPY_PY_MACHINE_ADC (1) +#define MICROPY_PY_MACHINE_TEMP (1) + +#define MICROPY_HW_ENABLE_RNG (1) + +#define MICROPY_HW_USB_CDC (1) + +#define MICROPY_HW_HAS_LED (1) +#define MICROPY_HW_LED_COUNT (3) +#define MICROPY_HW_LED_PULLUP (1) + +#define MICROPY_HW_LED1 (22) // LED1 GREEN +#define MICROPY_HW_LED2 (23) // LED2 RED +#define MICROPY_HW_LED3 (24) // LED3 BLUE + +// UART config +#define MICROPY_HW_UART1_RX (7) +#define MICROPY_HW_UART1_TX (8) +#define MICROPY_HW_UART1_CTS (9) +#define MICROPY_HW_UART1_RTS (10) +#define MICROPY_HW_UART1_HWFC (1) + +// SPI0 config +#define MICROPY_HW_SPI0_NAME "SPI0" + +#define MICROPY_HW_SPI0_SCK (19) +#define MICROPY_HW_SPI0_MOSI (20) +#define MICROPY_HW_SPI0_MISO (21) + +#define MICROPY_HW_PWM0_NAME "PWM0" +#define MICROPY_HW_PWM1_NAME "PWM1" +#define MICROPY_HW_PWM2_NAME "PWM2" +#define MICROPY_HW_PWM3_NAME "PWM3" + +#define HELP_TEXT_BOARD_LED "1,2,3" diff --git a/ports/nrf/boards/nrf52840-mdk-usb-dongle/mpconfigboard.mk b/ports/nrf/boards/nrf52840-mdk-usb-dongle/mpconfigboard.mk new file mode 100644 index 0000000000000..f98d5c88a9076 --- /dev/null +++ b/ports/nrf/boards/nrf52840-mdk-usb-dongle/mpconfigboard.mk @@ -0,0 +1,7 @@ +MCU_SERIES = m4 +MCU_VARIANT = nrf52 +MCU_SUB_VARIANT = nrf52840 +SOFTDEV_VERSION = 6.1.1 +LD_FILES += boards/nrf52840-mdk-usb-dongle/nrf52840_open_bootloader.ld boards/nrf52840_1M_256k.ld + +NRF_DEFINES += -DNRF52840_XXAA diff --git a/ports/nrf/boards/nrf52840-mdk-usb-dongle/nrf52840_open_bootloader.ld b/ports/nrf/boards/nrf52840-mdk-usb-dongle/nrf52840_open_bootloader.ld new file mode 100644 index 0000000000000..d6c6e743a4daf --- /dev/null +++ b/ports/nrf/boards/nrf52840-mdk-usb-dongle/nrf52840_open_bootloader.ld @@ -0,0 +1,2 @@ +_ram_start = 0x20000008; +_flash_start = 0x1000; diff --git a/ports/nrf/boards/nrf52840-mdk-usb-dongle/pins.csv b/ports/nrf/boards/nrf52840-mdk-usb-dongle/pins.csv new file mode 100644 index 0000000000000..57162863778eb --- /dev/null +++ b/ports/nrf/boards/nrf52840-mdk-usb-dongle/pins.csv @@ -0,0 +1,17 @@ +P2,P2,ADC1_CH0 +P3,P3,ADC1_CH1 +P4,P4,ADC1_CH2 +P5,P5,ADC1_CH3 +P6,P6 +P7,P7 +P8,P8 +P9,P9 +P10,P10 +USER,P18,P18 +P19,P19 +P20,P20 +P21,P21 +LED_G,P22,P22 +LED_R,P23,P23 +LED_B,P24,P24 +P25,P25 diff --git a/ports/nrf/boards/pca10090/mpconfigboard.h b/ports/nrf/boards/pca10090/mpconfigboard.h index e4e514290949b..28381c40d1e6f 100644 --- a/ports/nrf/boards/pca10090/mpconfigboard.h +++ b/ports/nrf/boards/pca10090/mpconfigboard.h @@ -34,7 +34,7 @@ #define MICROPY_PY_MACHINE_HW_PWM (0) #define MICROPY_PY_MACHINE_HW_SPI (1) #define MICROPY_PY_MACHINE_TIMER (0) -#define MICROPY_PY_MACHINE_RTCOUNTER (0) +#define MICROPY_PY_MACHINE_RTCOUNTER (1) #define MICROPY_PY_MACHINE_I2C (1) #define MICROPY_PY_MACHINE_ADC (0) #define MICROPY_PY_MACHINE_TEMP (0) diff --git a/ports/nrf/drivers/bluetooth/ble_drv.c b/ports/nrf/drivers/bluetooth/ble_drv.c index 7619dc0399455..3a15025cb4978 100644 --- a/ports/nrf/drivers/bluetooth/ble_drv.c +++ b/ports/nrf/drivers/bluetooth/ble_drv.c @@ -32,7 +32,6 @@ #include "py/runtime.h" #include "ble_drv.h" -#include "mpconfigport.h" #include "nrf_sdm.h" #include "ble_gap.h" #include "ble.h" // sd_ble_uuid_encode @@ -607,12 +606,12 @@ bool ble_drv_advertise_data(ubluepy_advertise_data_t * p_adv_params) { #if (BLUETOOTH_SD == 110) if ((err_code = sd_ble_gap_adv_data_set(adv_data, byte_pos, NULL, 0)) != 0) { mp_raise_msg_varg(&mp_type_OSError, - "Can not apply advertisment data. status: 0x" HEX2_FMT, (uint16_t)err_code); + MP_ERROR_TEXT("Can not apply advertisment data. status: 0x" HEX2_FMT), (uint16_t)err_code); } #else if ((err_code = sd_ble_gap_adv_set_configure(&m_adv_handle, &m_adv_data, &m_adv_params)) != 0) { mp_raise_msg_varg(&mp_type_OSError, - "Can not apply advertisment data. status: 0x" HEX2_FMT, (uint16_t)err_code); + MP_ERROR_TEXT("Can not apply advertisment data. status: 0x" HEX2_FMT), (uint16_t)err_code); } #endif BLE_DRIVER_LOG("Set Adv data size: " UINT_FMT "\n", byte_pos); @@ -627,7 +626,7 @@ bool ble_drv_advertise_data(ubluepy_advertise_data_t * p_adv_params) { #endif if (err_code != 0) { mp_raise_msg_varg(&mp_type_OSError, - "Can not start advertisment. status: 0x" HEX2_FMT, (uint16_t)err_code); + MP_ERROR_TEXT("Can not start advertisment. status: 0x" HEX2_FMT), (uint16_t)err_code); } m_adv_in_progress = true; @@ -642,12 +641,12 @@ void ble_drv_advertise_stop(void) { #if (BLUETOOTH_SD == 110) if ((err_code = sd_ble_gap_adv_stop()) != 0) { mp_raise_msg_varg(&mp_type_OSError, - "Can not stop advertisment. status: 0x" HEX2_FMT, (uint16_t)err_code); + MP_ERROR_TEXT("Can not stop advertisment. status: 0x" HEX2_FMT), (uint16_t)err_code); } #else if ((err_code = sd_ble_gap_adv_stop(m_adv_handle)) != 0) { mp_raise_msg_varg(&mp_type_OSError, - "Can not stop advertisment. status: 0x" HEX2_FMT, (uint16_t)err_code); + MP_ERROR_TEXT("Can not stop advertisment. status: 0x" HEX2_FMT), (uint16_t)err_code); } #endif } @@ -667,7 +666,7 @@ void ble_drv_attr_s_read(uint16_t conn_handle, uint16_t handle, uint16_t len, ui &gatts_value); if (err_code != 0) { mp_raise_msg_varg(&mp_type_OSError, - "Can not read attribute value. status: 0x" HEX2_FMT, (uint16_t)err_code); + MP_ERROR_TEXT("Can not read attribute value. status: 0x" HEX2_FMT), (uint16_t)err_code); } } @@ -684,7 +683,7 @@ void ble_drv_attr_s_write(uint16_t conn_handle, uint16_t handle, uint16_t len, u if (err_code != 0) { mp_raise_msg_varg(&mp_type_OSError, - "Can not write attribute value. status: 0x" HEX2_FMT, (uint16_t)err_code); + MP_ERROR_TEXT("Can not write attribute value. status: 0x" HEX2_FMT), (uint16_t)err_code); } } @@ -708,7 +707,7 @@ void ble_drv_attr_s_notify(uint16_t conn_handle, uint16_t handle, uint16_t len, uint32_t err_code; if ((err_code = sd_ble_gatts_hvx(conn_handle, &hvx_params)) != 0) { mp_raise_msg_varg(&mp_type_OSError, - "Can not notify attribute value. status: 0x" HEX2_FMT, (uint16_t)err_code); + MP_ERROR_TEXT("Can not notify attribute value. status: 0x" HEX2_FMT), (uint16_t)err_code); } m_tx_in_progress++; BLE_DRIVER_LOG("Queued TX, m_tx_in_progress: %u\n", m_tx_in_progress); @@ -747,7 +746,7 @@ void ble_drv_attr_c_read(uint16_t conn_handle, uint16_t handle, mp_obj_t obj, bl 0); if (err_code != 0) { mp_raise_msg_varg(&mp_type_OSError, - "Can not read attribute value. status: 0x" HEX2_FMT, (uint16_t)err_code); + MP_ERROR_TEXT("Can not read attribute value. status: 0x" HEX2_FMT), (uint16_t)err_code); } while (gattc_char_data_handle != NULL) { @@ -777,7 +776,7 @@ void ble_drv_attr_c_write(uint16_t conn_handle, uint16_t handle, uint16_t len, u if (err_code != 0) { mp_raise_msg_varg(&mp_type_OSError, - "Can not write attribute value. status: 0x" HEX2_FMT, (uint16_t)err_code); + MP_ERROR_TEXT("Can not write attribute value. status: 0x" HEX2_FMT), (uint16_t)err_code); } while (m_write_done != true) { @@ -808,7 +807,7 @@ void ble_drv_scan_start(bool cont) { } if ((err_code = sd_ble_gap_scan_start(p_scan_params, &scan_buffer)) != 0) { mp_raise_msg_varg(&mp_type_OSError, - "Can not start scanning. status: 0x" HEX2_FMT, (uint16_t)err_code); + MP_ERROR_TEXT("Can not start scanning. status: 0x" HEX2_FMT), (uint16_t)err_code); } } @@ -854,7 +853,7 @@ void ble_drv_connect(uint8_t * p_addr, uint8_t addr_type) { &conn_params, conn_tag)) != 0) { mp_raise_msg_varg(&mp_type_OSError, - "Can not connect. status: 0x" HEX2_FMT, (uint16_t)err_code); + MP_ERROR_TEXT("Can not connect. status: 0x" HEX2_FMT), (uint16_t)err_code); } } @@ -1131,6 +1130,12 @@ static void ble_evt_handler(ble_evt_t * p_ble_evt) { BLE_DRIVER_LOG("GATTS EVT EXCHANGE MTU REQUEST\n"); (void)sd_ble_gatts_exchange_mtu_reply(p_ble_evt->evt.gatts_evt.conn_handle, 23); // MAX MTU size break; + + case BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST: + BLE_DRIVER_LOG("BLE GAP EVT DATA LENGTH UPDATE REQUEST\n"); + sd_ble_gap_data_length_update(p_ble_evt->evt.gap_evt.conn_handle, NULL, NULL); + break; + #endif // (BLUETOOTH_SD == 132) || (BLUETOOTH_SD == 140) default: diff --git a/ports/nrf/drivers/bluetooth/ble_uart.c b/ports/nrf/drivers/bluetooth/ble_uart.c index b94780e26011b..64e3a05f93192 100644 --- a/ports/nrf/drivers/bluetooth/ble_uart.c +++ b/ports/nrf/drivers/bluetooth/ble_uart.c @@ -31,6 +31,11 @@ #include "ringbuffer.h" #include "mphalport.h" #include "lib/utils/interrupt_char.h" +#include "py/runtime.h" + +#if MICROPY_PY_SYS_STDFILES +#include "py/stream.h" +#endif #if MICROPY_PY_BLE_NUS @@ -131,10 +136,38 @@ void mp_hal_stdout_tx_strn(const char *str, size_t len) { } } +void ble_uart_tx_char(char c) { + // Not connected: drop output + if (!ble_uart_enabled()) return; + + ubluepy_characteristic_obj_t * p_char = &ble_uart_char_tx; + + ble_drv_attr_s_notify(p_char->p_service->p_periph->conn_handle, + p_char->handle, + 1, + (uint8_t *)&c); +} + void mp_hal_stdout_tx_strn_cooked(const char *str, mp_uint_t len) { - mp_hal_stdout_tx_strn(str, len); + for (const char *top = str + len; str < top; str++) { + if (*str == '\n') { + ble_uart_tx_char('\r'); + } + ble_uart_tx_char(*str); + } } +#if MICROPY_PY_SYS_STDFILES +uintptr_t mp_hal_stdio_poll(uintptr_t poll_flags) { + uintptr_t ret = 0; + if ((poll_flags & MP_STREAM_POLL_RD) && ble_uart_enabled() + && !isBufferEmpty(mp_rx_ring_buffer)) { + ret |= MP_STREAM_POLL_RD; + } + return ret; +} +#endif + STATIC void gap_event_handler(mp_obj_t self_in, uint16_t event_id, uint16_t conn_handle, uint16_t length, uint8_t * data) { ubluepy_peripheral_obj_t * self = MP_OBJ_TO_PTR(self_in); @@ -212,7 +245,7 @@ void ble_uart_init0(void) { ble_uart_peripheral.conn_handle = 0xFFFF; - char device_name[] = "mpus"; + static char device_name[] = "mpus"; mp_obj_t service_list = mp_obj_new_list(0, NULL); mp_obj_list_append(service_list, MP_OBJ_FROM_PTR(&ble_uart_service)); diff --git a/ports/nrf/drivers/usb/usb_descriptors.c b/ports/nrf/drivers/usb/usb_descriptors.c index 3704e5d0dd180..f6724c1bc078c 100644 --- a/ports/nrf/drivers/usb/usb_descriptors.c +++ b/ports/nrf/drivers/usb/usb_descriptors.c @@ -39,6 +39,7 @@ #define USBD_CDC_EP_OUT (0x02) #define USBD_CDC_EP_IN (0x82) #define USBD_CDC_CMD_MAX_SIZE (8) +#define USBD_CDC_IN_OUT_MAX_SIZE (64) #define USBD_STR_0 (0x00) #define USBD_STR_MANUF (0x01) @@ -70,7 +71,7 @@ static const uint8_t usbd_desc_cfg[USBD_DESC_LEN] = { TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, USBD_MAX_POWER_MA), TUD_CDC_DESCRIPTOR(USBD_ITF_CDC, USBD_STR_CDC, USBD_CDC_EP_CMD, - USBD_CDC_CMD_MAX_SIZE, USBD_CDC_EP_OUT, USBD_CDC_EP_IN, CFG_TUD_CDC_RX_BUFSIZE), + USBD_CDC_CMD_MAX_SIZE, USBD_CDC_EP_OUT, USBD_CDC_EP_IN, USBD_CDC_IN_OUT_MAX_SIZE), }; static const char *const usbd_desc_str[] = { diff --git a/ports/nrf/main.c b/ports/nrf/main.c index 3c5d0a05d87f1..86ba83342b9f3 100644 --- a/ports/nrf/main.c +++ b/ports/nrf/main.c @@ -52,6 +52,8 @@ #include "i2c.h" #include "adc.h" #include "rtcounter.h" +#include "mphalport.h" + #if MICROPY_PY_MACHINE_HW_PWM #include "pwm.h" #endif @@ -101,6 +103,9 @@ int main(int argc, char **argv) { soft_reset: + #if MICROPY_PY_TIME_TICKS + rtc1_init_time_ticks(); + #endif led_init(); @@ -275,10 +280,10 @@ mp_import_stat_t mp_import_stat(const char *path) { return uos_mbfs_import_stat(path); } -STATIC mp_obj_t mp_builtin_open(size_t n_args, const mp_obj_t *args) { +mp_obj_t mp_builtin_open(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { return uos_mbfs_open(n_args, args); } -MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_open_obj, 1, 2, mp_builtin_open); +MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_open_obj, 1, mp_builtin_open); #else // use dummy functions - no filesystem available @@ -290,7 +295,7 @@ mp_import_stat_t mp_import_stat(const char *path) { return MP_IMPORT_STAT_NO_EXIST; } -STATIC mp_obj_t mp_builtin_open(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { +mp_obj_t mp_builtin_open(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { mp_raise_OSError(MP_EPERM); } MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_open_obj, 1, mp_builtin_open); diff --git a/ports/nrf/modules/machine/i2c.c b/ports/nrf/modules/machine/i2c.c index afa906c3598d7..ab4d516621bc7 100644 --- a/ports/nrf/modules/machine/i2c.c +++ b/ports/nrf/modules/machine/i2c.c @@ -63,8 +63,6 @@ #endif -STATIC const mp_obj_type_t machine_hard_i2c_type; - typedef struct _machine_hard_i2c_obj_t { mp_obj_base_t base; nrfx_twi_t p_twi; // Driver instance @@ -96,6 +94,8 @@ STATIC void machine_hard_i2c_print(const mp_print_t *print, mp_obj_t self_in, mp /* MicroPython bindings for machine API */ mp_obj_t machine_hard_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + MP_MACHINE_I2C_CHECK_FOR_LEGACY_SOFTI2C_CONSTRUCTION(n_args, n_kw, all_args); + enum { ARG_id, ARG_scl, ARG_sda }; static const mp_arg_t allowed_args[] = { { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_OBJ }, @@ -161,7 +161,7 @@ STATIC const mp_machine_i2c_p_t machine_hard_i2c_p = { .transfer_single = machine_hard_i2c_transfer_single, }; -STATIC const mp_obj_type_t machine_hard_i2c_type = { +const mp_obj_type_t machine_hard_i2c_type = { { &mp_type_type }, .name = MP_QSTR_I2C, .print = machine_hard_i2c_print, diff --git a/ports/nrf/modules/machine/i2c.h b/ports/nrf/modules/machine/i2c.h index 92194ce75763f..1dfb1f077a446 100644 --- a/ports/nrf/modules/machine/i2c.h +++ b/ports/nrf/modules/machine/i2c.h @@ -27,7 +27,9 @@ #ifndef I2C_H__ #define I2C_H__ -extern const mp_obj_type_t machine_i2c_type; +#include "extmod/machine_i2c.h" + +extern const mp_obj_type_t machine_hard_i2c_type; void i2c_init0(void); diff --git a/ports/nrf/modules/machine/modmachine.c b/ports/nrf/modules/machine/modmachine.c index 48730c849f6bf..3330349cd5963 100644 --- a/ports/nrf/modules/machine/modmachine.c +++ b/ports/nrf/modules/machine/modmachine.c @@ -208,7 +208,8 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_hard_spi_type) }, #endif #if MICROPY_PY_MACHINE_I2C - { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_i2c_type) }, + { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_hard_i2c_type) }, + { MP_ROM_QSTR(MP_QSTR_SoftI2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, #endif #if MICROPY_PY_MACHINE_ADC { MP_ROM_QSTR(MP_QSTR_ADC), MP_ROM_PTR(&machine_adc_type) }, diff --git a/ports/nrf/modules/machine/rtcounter.c b/ports/nrf/modules/machine/rtcounter.c index 5fb28557d8230..c9f907774e6a3 100644 --- a/ports/nrf/modules/machine/rtcounter.c +++ b/ports/nrf/modules/machine/rtcounter.c @@ -153,6 +153,13 @@ STATIC mp_obj_t machine_rtc_make_new(const mp_obj_type_t *type, size_t n_args, s int rtc_id = rtc_find(args[ARG_id].u_obj); + #if MICROPY_PY_TIME_TICKS + if (rtc_id == 1) { + // time module uses RTC1, prevent using it + mp_raise_ValueError(MP_ERROR_TEXT("RTC1 reserved by time module")); + } + #endif + // const and non-const part of the RTC object. const machine_rtc_obj_t * self = &machine_rtc_obj[rtc_id]; machine_rtc_config_t *config = self->config; diff --git a/ports/nrf/modules/uos/microbitfs.c b/ports/nrf/modules/uos/microbitfs.c index 7f16372cf2224..e8628f4945c20 100644 --- a/ports/nrf/modules/uos/microbitfs.c +++ b/ports/nrf/modules/uos/microbitfs.c @@ -36,7 +36,6 @@ #include "py/stream.h" #include "py/runtime.h" #include "extmod/vfs.h" -#include "mpconfigport.h" #if MICROPY_MBFS diff --git a/ports/nrf/modules/utime/modutime.c b/ports/nrf/modules/utime/modutime.c index 60cdbe4f33140..bb29141011eab 100644 --- a/ports/nrf/modules/utime/modutime.c +++ b/ports/nrf/modules/utime/modutime.c @@ -42,6 +42,10 @@ STATIC const mp_rom_map_elem_t time_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_sleep_ms), MP_ROM_PTR(&mp_utime_sleep_ms_obj) }, { MP_ROM_QSTR(MP_QSTR_sleep_us), MP_ROM_PTR(&mp_utime_sleep_us_obj) }, + { MP_ROM_QSTR(MP_QSTR_ticks_ms), MP_ROM_PTR(&mp_utime_ticks_ms_obj) }, + { MP_ROM_QSTR(MP_QSTR_ticks_us), MP_ROM_PTR(&mp_utime_ticks_us_obj) }, + { MP_ROM_QSTR(MP_QSTR_ticks_add), MP_ROM_PTR(&mp_utime_ticks_add_obj) }, + { MP_ROM_QSTR(MP_QSTR_ticks_diff), MP_ROM_PTR(&mp_utime_ticks_diff_obj) }, }; STATIC MP_DEFINE_CONST_DICT(time_module_globals, time_module_globals_table); diff --git a/ports/nrf/mpconfigdevice_nrf51822.h b/ports/nrf/mpconfigdevice_nrf51822.h new file mode 100644 index 0000000000000..2f85c9f4c5468 --- /dev/null +++ b/ports/nrf/mpconfigdevice_nrf51822.h @@ -0,0 +1,61 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Glenn Ruben Bakke + * + * 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. + */ + +// Board overridable build configuration. + +#ifndef MICROPY_MBFS +#define MICROPY_MBFS (1) +#endif + +#ifndef MICROPY_VFS +#define MICROPY_VFS (0) +#endif + +// Board overridable feature configuration. + +#ifndef MICROPY_PY_ARRAY_SLICE_ASSIGN +#if defined(BLUETOOTH_SD) +#define MICROPY_PY_ARRAY_SLICE_ASSIGN (0) +#else +#define MICROPY_PY_ARRAY_SLICE_ASSIGN (1) +#endif +#endif + +#ifndef MICROPY_PY_SYS_STDFILES +#if defined(BLUETOOTH_SD) +#define MICROPY_PY_SYS_STDFILES (0) +#else +#define MICROPY_PY_SYS_STDFILES (1) +#endif +#endif + +#ifndef MICROPY_PY_UBINASCII +#if defined(BLUETOOTH_SD) +#define MICROPY_PY_UBINASCII (0) +#else +#define MICROPY_PY_UBINASCII (1) +#endif +#endif diff --git a/ports/nrf/mpconfigdevice_nrf52832.h b/ports/nrf/mpconfigdevice_nrf52832.h new file mode 100644 index 0000000000000..2bfd047ca1456 --- /dev/null +++ b/ports/nrf/mpconfigdevice_nrf52832.h @@ -0,0 +1,49 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Glenn Ruben Bakke + * + * 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. + */ + +// Board overridable build configuration. + +#ifndef MICROPY_MBFS +#define MICROPY_MBFS (1) +#endif + +#ifndef MICROPY_VFS +#define MICROPY_VFS (0) +#endif + +// Board overridable feature configuration. + +#ifndef MICROPY_PY_ARRAY_SLICE_ASSIGN +#define MICROPY_PY_ARRAY_SLICE_ASSIGN (1) +#endif + +#ifndef MICROPY_PY_SYS_STDFILES +#define MICROPY_PY_SYS_STDFILES (1) +#endif + +#ifndef MICROPY_PY_UBINASCII +#define MICROPY_PY_UBINASCII (1) +#endif diff --git a/ports/nrf/mpconfigdevice_nrf52840.h b/ports/nrf/mpconfigdevice_nrf52840.h new file mode 100644 index 0000000000000..2bfd047ca1456 --- /dev/null +++ b/ports/nrf/mpconfigdevice_nrf52840.h @@ -0,0 +1,49 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Glenn Ruben Bakke + * + * 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. + */ + +// Board overridable build configuration. + +#ifndef MICROPY_MBFS +#define MICROPY_MBFS (1) +#endif + +#ifndef MICROPY_VFS +#define MICROPY_VFS (0) +#endif + +// Board overridable feature configuration. + +#ifndef MICROPY_PY_ARRAY_SLICE_ASSIGN +#define MICROPY_PY_ARRAY_SLICE_ASSIGN (1) +#endif + +#ifndef MICROPY_PY_SYS_STDFILES +#define MICROPY_PY_SYS_STDFILES (1) +#endif + +#ifndef MICROPY_PY_UBINASCII +#define MICROPY_PY_UBINASCII (1) +#endif diff --git a/ports/nrf/mpconfigdevice_nrf9160.h b/ports/nrf/mpconfigdevice_nrf9160.h new file mode 100644 index 0000000000000..2bfd047ca1456 --- /dev/null +++ b/ports/nrf/mpconfigdevice_nrf9160.h @@ -0,0 +1,49 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Glenn Ruben Bakke + * + * 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. + */ + +// Board overridable build configuration. + +#ifndef MICROPY_MBFS +#define MICROPY_MBFS (1) +#endif + +#ifndef MICROPY_VFS +#define MICROPY_VFS (0) +#endif + +// Board overridable feature configuration. + +#ifndef MICROPY_PY_ARRAY_SLICE_ASSIGN +#define MICROPY_PY_ARRAY_SLICE_ASSIGN (1) +#endif + +#ifndef MICROPY_PY_SYS_STDFILES +#define MICROPY_PY_SYS_STDFILES (1) +#endif + +#ifndef MICROPY_PY_UBINASCII +#define MICROPY_PY_UBINASCII (1) +#endif diff --git a/ports/nrf/mpconfigport.h b/ports/nrf/mpconfigport.h index 28076114fe42a..a025feb2ec65a 100644 --- a/ports/nrf/mpconfigport.h +++ b/ports/nrf/mpconfigport.h @@ -24,11 +24,20 @@ * THE SOFTWARE. */ -#ifndef NRF5_MPCONFIGPORT_H__ -#define NRF5_MPCONFIGPORT_H__ - #include +#if defined(NRF51822) + #include "mpconfigdevice_nrf51822.h" +#elif defined(NRF52832) + #include "mpconfigdevice_nrf52832.h" +#elif defined(NRF52840) + #include "mpconfigdevice_nrf52840.h" +#elif defined(NRF9160) + #include "mpconfigdevice_nrf9160.h" +#else + #pragma error "Device not defined" +#endif + // options to control how MicroPython is built #ifndef MICROPY_VFS #define MICROPY_VFS (0) @@ -105,11 +114,9 @@ #define MICROPY_MODULE_BUILTIN_INIT (1) #define MICROPY_PY_ALL_SPECIAL_METHODS (0) #define MICROPY_PY_MICROPYTHON_MEM_INFO (1) -#define MICROPY_PY_ARRAY_SLICE_ASSIGN (0) #define MICROPY_PY_BUILTINS_SLICE_ATTRS (0) #define MICROPY_PY_SYS_EXIT (1) #define MICROPY_PY_SYS_MAXSIZE (1) -#define MICROPY_PY_SYS_STDFILES (0) #define MICROPY_PY_SYS_STDIO_BUFFER (0) #define MICROPY_PY_COLLECTIONS_ORDEREDDICT (0) #define MICROPY_PY_MATH_SPECIAL_FUNCTIONS (0) @@ -117,7 +124,6 @@ #define MICROPY_PY_IO (0) #define MICROPY_PY_IO_FILEIO (0) #define MICROPY_PY_UERRNO (0) -#define MICROPY_PY_UBINASCII (0) #define MICROPY_PY_URANDOM (1) #define MICROPY_PY_URANDOM_EXTRA_FUNCS (1) #define MICROPY_PY_UCTYPES (0) @@ -129,7 +135,6 @@ #define MICROPY_PY_UTIME_MP_HAL (1) #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_PULSE (0) -#define MICROPY_PY_MACHINE_I2C_MAKE_NEW machine_hard_i2c_make_new #define MICROPY_PY_MACHINE_SPI (0) #define MICROPY_PY_MACHINE_SPI_MIN_DELAY (0) #define MICROPY_PY_FRAMEBUF (0) @@ -174,6 +179,9 @@ #define MICROPY_PY_MACHINE_RTCOUNTER (0) #endif +#ifndef MICROPY_PY_TIME_TICKS +#define MICROPY_PY_TIME_TICKS (1) +#endif #define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (1) #define MICROPY_EMERGENCY_EXCEPTION_BUF_SIZE (0) @@ -317,11 +325,16 @@ extern const struct _mp_obj_module_t ble_module; /* micro:bit root pointers */ \ void *async_data[2]; \ +#define MICROPY_EVENT_POLL_HOOK \ + do { \ + extern void mp_handle_pending(bool); \ + mp_handle_pending(true); \ + __WFI(); \ + } while (0); + #define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn_cooked(str, len) // We need to provide a declaration/definition of alloca() #include #define MICROPY_PIN_DEFS_PORT_H "pin_defs_nrf5.h" - -#endif diff --git a/ports/nrf/mphalport.c b/ports/nrf/mphalport.c index 4469adc80d84f..b8e4c2e4d8b55 100644 --- a/ports/nrf/mphalport.c +++ b/ports/nrf/mphalport.c @@ -35,6 +35,121 @@ #include "nrfx_errors.h" #include "nrfx_config.h" +#if MICROPY_PY_TIME_TICKS +#include "nrfx_rtc.h" +#include "nrf_clock.h" +#endif + +#if MICROPY_PY_TIME_TICKS + +// Use RTC1 for time ticks generation (ms and us) with 32kHz tick resolution +// and overflow handling in RTC IRQ. + +#define RTC_TICK_INCREASE_MSEC (33) + +#define RTC_RESCHEDULE_CC(rtc, cc_nr, ticks) \ + do { \ + nrfx_rtc_cc_set(&rtc, cc_nr, nrfx_rtc_counter_get(&rtc) + ticks, true); \ + } while (0); + +// RTC overflow irq handling notes: +// - If has_overflowed is set it could be before or after COUNTER is read. +// If before then an adjustment must be made, if after then no adjustment is necessary. +// - The before case is when COUNTER is very small (because it just overflowed and was set to zero), +// the after case is when COUNTER is very large (because it's just about to overflow +// but we read it right before it overflows). +// - The extra check for counter is to distinguish these cases. 1<<23 because it's halfway +// between min and max values of COUNTER. +#define RTC1_GET_TICKS_ATOMIC(rtc, overflows, counter) \ + do { \ + rtc.p_reg->INTENCLR = RTC_INTENCLR_OVRFLW_Msk; \ + overflows = rtc_overflows; \ + counter = rtc.p_reg->COUNTER; \ + uint32_t has_overflowed = rtc.p_reg->EVENTS_OVRFLW; \ + if (has_overflowed && counter < (1 << 23)) { \ + overflows += 1; \ + } \ + rtc.p_reg->INTENSET = RTC_INTENSET_OVRFLW_Msk; \ + } while (0); + +nrfx_rtc_t rtc1 = NRFX_RTC_INSTANCE(1); +volatile mp_uint_t rtc_overflows = 0; + +const nrfx_rtc_config_t rtc_config_time_ticks = { + .prescaler = 0, + .reliable = 0, + .tick_latency = 0, + #ifdef NRF51 + .interrupt_priority = 1, + #else + .interrupt_priority = 3, + #endif +}; + +STATIC void rtc_irq_time(nrfx_rtc_int_type_t event) { + // irq handler for overflow + if (event == NRFX_RTC_INT_OVERFLOW) { + rtc_overflows += 1; + } + // irq handler for wakeup from WFI (~1msec) + if (event == NRFX_RTC_INT_COMPARE0) { + RTC_RESCHEDULE_CC(rtc1, 0, RTC_TICK_INCREASE_MSEC) + } +} + +void rtc1_init_time_ticks(void) { + // Start the low-frequency clock (if it hasn't been started already) + if (!nrf_clock_lf_is_running(NRF_CLOCK)) { + nrf_clock_task_trigger(NRF_CLOCK, NRF_CLOCK_TASK_LFCLKSTART); + } + // Uninitialize first, then set overflow IRQ and first CC event + nrfx_rtc_uninit(&rtc1); + nrfx_rtc_init(&rtc1, &rtc_config_time_ticks, rtc_irq_time); + nrfx_rtc_overflow_enable(&rtc1, true); + RTC_RESCHEDULE_CC(rtc1, 0, RTC_TICK_INCREASE_MSEC) + nrfx_rtc_enable(&rtc1); +} + +mp_uint_t mp_hal_ticks_ms(void) { + // Compute: (rtc_overflows << 24 + COUNTER) * 1000 / 32768 + // + // Note that COUNTER * 1000 / 32768 would overflow during calculation, so use + // the less obvious * 125 / 4096 calculation (overflow secure). + // + // Make sure not to call this function within an irq with higher prio than the + // RTC's irq. This would introduce the danger of preempting the RTC irq and + // calling mp_hal_ticks_ms() at that time would return a false result. + uint32_t overflows; + uint32_t counter; + // guard against overflow irq + RTC1_GET_TICKS_ATOMIC(rtc1, overflows, counter) + return (overflows << 9) * 1000 + (counter * 125 / 4096); +} + +mp_uint_t mp_hal_ticks_us(void) { + // Compute: ticks_us = (overflows << 24 + counter) * 1000000 / 32768 + // = (overflows << 15 * 15625) + (counter * 15625 / 512) + // Since this function is likely to be called in a poll loop it must + // be fast, using an optimized 64bit mult/divide. + uint32_t overflows; + uint32_t counter; + // guard against overflow irq + RTC1_GET_TICKS_ATOMIC(rtc1, overflows, counter) + // first compute counter * 15625 + uint32_t counter_lo = (counter & 0xffff) * 15625; + uint32_t counter_hi = (counter >> 16) * 15625; + // actual value is counter_hi << 16 + counter_lo + return ((overflows << 15) * 15625) + ((counter_hi << 7) + (counter_lo >> 9)); +} + +#else + +mp_uint_t mp_hal_ticks_ms(void) { + return 0; +} + +#endif + // this table converts from HAL_StatusTypeDef to POSIX errno const byte mp_hal_status_to_errno_table[4] = { [HAL_OK] = 0, @@ -70,7 +185,7 @@ int mp_hal_stdin_rx_chr(void) { if (MP_STATE_PORT(board_stdio_uart) != NULL && uart_rx_any(MP_STATE_PORT(board_stdio_uart))) { return uart_rx_char(MP_STATE_PORT(board_stdio_uart)); } - __WFI(); + MICROPY_EVENT_POLL_HOOK } return 0; @@ -93,6 +208,31 @@ void mp_hal_stdout_tx_str(const char *str) { mp_hal_stdout_tx_strn(str, strlen(str)); } +#if MICROPY_PY_TIME_TICKS + +void mp_hal_delay_us(mp_uint_t us) { + uint32_t now; + if (us == 0) { + return; + } + now = mp_hal_ticks_us(); + while (mp_hal_ticks_us() - now < us) { + } +} + +void mp_hal_delay_ms(mp_uint_t ms) { + uint32_t now; + if (ms == 0) { + return; + } + now = mp_hal_ticks_ms(); + while (mp_hal_ticks_ms() - now < ms) { + MICROPY_EVENT_POLL_HOOK + } +} + +#else + void mp_hal_delay_us(mp_uint_t us) { if (us == 0) { return; @@ -175,6 +315,7 @@ void mp_hal_delay_ms(mp_uint_t ms) { mp_hal_delay_us(999); } } +#endif #if defined(NRFX_LOG_ENABLED) && (NRFX_LOG_ENABLED == 1) diff --git a/ports/nrf/mphalport.h b/ports/nrf/mphalport.h index 5614be29fa29f..5900559b5dd76 100644 --- a/ports/nrf/mphalport.h +++ b/ports/nrf/mphalport.h @@ -41,12 +41,6 @@ typedef enum HAL_TIMEOUT = 0x03 } HAL_StatusTypeDef; -static inline uint32_t hal_tick_fake(void) { - return 0; -} - -#define mp_hal_ticks_ms hal_tick_fake // TODO: implement. Right now, return 0 always - extern const unsigned char mp_hal_status_to_errno_table[4]; NORETURN void mp_hal_raise(HAL_StatusTypeDef status); @@ -60,8 +54,10 @@ void mp_hal_delay_us(mp_uint_t us); const char *nrfx_error_code_lookup(uint32_t err_code); +#define MP_HAL_PIN_FMT "%q" #define mp_hal_pin_obj_t const pin_obj_t * #define mp_hal_get_pin_obj(o) pin_find(o) +#define mp_hal_pin_name(p) ((p)->name) #define mp_hal_pin_high(p) nrf_gpio_pin_set(p->pin) #define mp_hal_pin_low(p) nrf_gpio_pin_clear(p->pin) #define mp_hal_pin_read(p) (nrf_gpio_pin_dir_get(p->pin) == NRF_GPIO_PIN_DIR_OUTPUT) ? nrf_gpio_pin_out_read(p->pin) : nrf_gpio_pin_read(p->pin) @@ -70,10 +66,15 @@ const char *nrfx_error_code_lookup(uint32_t err_code); #define mp_hal_pin_od_high(p) mp_hal_pin_high(p) #define mp_hal_pin_open_drain(p) nrf_gpio_cfg_input(p->pin, NRF_GPIO_PIN_NOPULL) +#if MICROPY_PY_TIME_TICKS +void rtc1_init_time_ticks(); +#else +mp_uint_t mp_hal_ticks_ms(void); +#define mp_hal_ticks_us() (0) +#endif // TODO: empty implementation for now. Used by machine_spi.c:69 #define mp_hal_delay_us_fast(p) -#define mp_hal_ticks_us() (0) #define mp_hal_ticks_cpu() (0) #endif diff --git a/ports/nrf/nrfx_config.h b/ports/nrf/nrfx_config.h index 19c744510feb5..beb6b34ab368d 100644 --- a/ports/nrf/nrfx_config.h +++ b/ports/nrf/nrfx_config.h @@ -130,7 +130,7 @@ #define NRFX_RTC_ENABLED (MICROPY_PY_MACHINE_RTCOUNTER) #define NRFX_RTC0_ENABLED 1 #define NRFX_RTC1_ENABLED 1 -#define NRFX_RTC2_ENABLED (!NRF51) +#define NRFX_RTC2_ENABLED (!NRF51) && (!NRF9160_XXAA) #define NRFX_TIMER_ENABLED (MICROPY_PY_MACHINE_TIMER) #define NRFX_TIMER0_ENABLED 1 diff --git a/ports/powerpc/Makefile b/ports/powerpc/Makefile index 7aa4f1449c508..1f5ec80d43653 100644 --- a/ports/powerpc/Makefile +++ b/ports/powerpc/Makefile @@ -6,10 +6,13 @@ QSTR_DEFS = qstrdefsport.h # include py core make definitions include $(TOP)/py/py.mk +# potato or lpc_serial +UART ?= potato + ARCH = $(shell uname -m) ifneq ("$(ARCH)", "ppc64") ifneq ("$(ARCH)", "ppc64le") - CROSS_COMPILE ?= powerpc64le-linux- + CROSS_COMPILE ?= powerpc64le-linux-gnu- endif endif @@ -30,9 +33,7 @@ LIBS = SRC_C = \ main.c \ - uart_core.c \ - uart_potato.c \ - uart_lpc_serial.c \ + uart_$(UART).c \ lib/utils/printf.c \ lib/utils/stdout_helpers.c \ lib/utils/pyexec.c \ @@ -52,7 +53,7 @@ $(BUILD)/_frozen_mpy.c: frozentest.mpy $(BUILD)/genhdr/qstrdefs.generated.h $(BUILD)/firmware.elf: $(OBJ) powerpc.lds $(ECHO) "LINK $@" - $(Q)$(LD) $(LDFLAGS) -o $@ $^ $(LIBS) + $(Q)$(LD) $(LDFLAGS) -o $@ $(OBJ) $(LIBS) $(Q)$(SIZE) $@ $(BUILD)/firmware.bin: $(BUILD)/firmware.elf diff --git a/ports/powerpc/README.md b/ports/powerpc/README.md index 862bfcd3c5002..631924c7c0afb 100644 --- a/ports/powerpc/README.md +++ b/ports/powerpc/README.md @@ -6,10 +6,14 @@ potato UART. ## Building -By default the port will be built for the host machine: +By default the port will be built with the potato uart for microwatt: $ make +To instead build for a machine with LPC serial, such as QEMU powernv: + + $ make UART=lpc_serial + ## Cross compilation for POWERPC If you need to cross compilers you'll want to grab a powerpc64le diff --git a/ports/powerpc/main.c b/ports/powerpc/main.c index 421f9be714c90..fdeec13abe0a5 100644 --- a/ports/powerpc/main.c +++ b/ports/powerpc/main.c @@ -65,7 +65,6 @@ int main(int argc, char **argv) { int stack_dummy; stack_top = (char *)&stack_dummy; - // microwatt has argc/r3 = 0 whereas QEMU has r3 set in head.S uart_init_ppc(argc); #if MICROPY_ENABLE_PYSTACK diff --git a/ports/powerpc/uart_lpc_serial.c b/ports/powerpc/uart_lpc_serial.c index c15fc049b9d5a..760615041cf94 100644 --- a/ports/powerpc/uart_lpc_serial.c +++ b/ports/powerpc/uart_lpc_serial.c @@ -96,20 +96,24 @@ static int lpc_uart_rx_empty(void) { return !(lpc_uart_reg_read(REG_LSR) & LSR_DR); } -void lpc_uart_init(void) { +void uart_init_ppc(void) { lpc_uart_base = LPC_UART_BASE; } -char lpc_uart_read(void) { +int mp_hal_stdin_rx_chr(void) { while (lpc_uart_rx_empty()) { ; } return lpc_uart_reg_read(REG_THR); } -void lpc_uart_write(char c) { - while (lpc_uart_tx_full()) { - ; + +void mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) { + int i; + for (i = 0; i < len; i++) { + while (lpc_uart_tx_full()) { + ; + } + lpc_uart_reg_write(REG_RBR, str[i]); } - lpc_uart_reg_write(REG_RBR, c); } diff --git a/ports/powerpc/uart_potato.c b/ports/powerpc/uart_potato.c index 4a487f30322da..29f21934ddf44 100644 --- a/ports/powerpc/uart_potato.c +++ b/ports/powerpc/uart_potato.c @@ -34,10 +34,12 @@ #include #include "py/mpconfig.h" -#define PROC_FREQ 50000000 +#define SYSCON_BASE 0xc0000000 /* System control regs */ +#define SYS_REG_CLKINFO 0x20 + #define UART_FREQ 115200 #define POTATO_UART_BASE 0xc0002000 -uint64_t potato_uart_base; +static uint64_t potato_uart_base; #define POTATO_CONSOLE_TX 0x00 #define POTATO_CONSOLE_RX 0x08 @@ -60,7 +62,7 @@ static uint64_t potato_uart_reg_read(int offset) { return val; } -void potato_uart_reg_write(int offset, uint64_t val) { +static void potato_uart_reg_write(int offset, uint64_t val) { uint64_t addr; addr = potato_uart_base + offset; @@ -96,13 +98,16 @@ static unsigned long potato_uart_divisor(unsigned long proc_freq, unsigned long return proc_freq / (uart_freq * 16) - 1; } -void potato_uart_init(void) { +void uart_init_ppc(void) { + uint64_t proc_freq; + potato_uart_base = POTATO_UART_BASE; - potato_uart_reg_write(POTATO_CONSOLE_CLOCK_DIV, potato_uart_divisor(PROC_FREQ, UART_FREQ)); + proc_freq = *(volatile uint64_t *)(SYSCON_BASE + SYS_REG_CLKINFO); + potato_uart_reg_write(POTATO_CONSOLE_CLOCK_DIV, potato_uart_divisor(proc_freq, UART_FREQ)); } -char potato_uart_read(void) { +int mp_hal_stdin_rx_chr(void) { uint64_t val; while (potato_uart_rx_empty()) { @@ -113,13 +118,15 @@ char potato_uart_read(void) { return (char)(val & 0x000000ff); } -void potato_uart_write(char c) { - uint64_t val; +void mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) { + int i; - val = c; + for (i = 0; i < len; i++) { + uint64_t val = str[i]; - while (potato_uart_tx_full()) { - ; + while (potato_uart_tx_full()) { + ; + } + potato_uart_reg_write(POTATO_CONSOLE_TX, val); } - potato_uart_reg_write(POTATO_CONSOLE_TX, val); } diff --git a/ports/qemu-arm/Makefile b/ports/qemu-arm/Makefile index 430740ccf73f7..aebc5afaeb773 100644 --- a/ports/qemu-arm/Makefile +++ b/ports/qemu-arm/Makefile @@ -42,10 +42,11 @@ INC += -I$(BUILD) CFLAGS += $(INC) -Wall -Wpointer-arith -Wdouble-promotion -Wfloat-conversion -Werror -std=gnu99 $(COPT) \ -ffunction-sections -fdata-sections +CFLAGS += $(CFLAGS_EXTRA) -#Debugging/Optimization +# Debugging/Optimization ifeq ($(DEBUG), 1) -CFLAGS += -g -DPENDSV_DEBUG +CFLAGS += -g COPT = -O0 else COPT += -Os -DNDEBUG diff --git a/ports/qemu-arm/README.md b/ports/qemu-arm/README.md index 4f1e79b101772..2c815c54b088a 100644 --- a/ports/qemu-arm/README.md +++ b/ports/qemu-arm/README.md @@ -15,8 +15,9 @@ The purposes of this port are to enable: - no need to use OpenOCD or anything else that might slow down the process in terms of plugging things together, pressing buttons, etc. -This port will only work with with [GCC ARM Embedded](launchpad.net/gcc-arm-embedded) -toolchain and not with CodeSourcery toolchain. You will need to modify +This port will only work with the [GNU ARM Embedded Toolchain]( +https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm) + and not with CodeSourcery toolchain. You will need to modify `LDFLAGS` if you want to use CodeSourcery's version of `arm-none-eabi`. The difference is that CodeSourcery needs `-T generic-m-hosted.ld` while ARM's version requires `--specs=nano.specs --specs=rdimon.specs` to be diff --git a/ports/qemu-arm/startup.c b/ports/qemu-arm/startup.c index 002d5ef62418a..58bdf7af9e98e 100644 --- a/ports/qemu-arm/startup.c +++ b/ports/qemu-arm/startup.c @@ -1,4 +1,5 @@ #include +#include #include #include "uart.h" @@ -73,6 +74,14 @@ __attribute__((naked)) void exit(int status) { } } +#ifndef NDEBUG +void __assert_func(const char *file, int line, const char *func, const char *expr) { + (void)func; + printf("Assertion '%s' failed, at file %s:%d\n", expr, file, line); + exit(1); +} +#endif + // The following are needed for tinytest #include diff --git a/ports/samd/tusb_port.c b/ports/samd/tusb_port.c index 019e3a891c262..f3d417f1a182e 100644 --- a/ports/samd/tusb_port.c +++ b/ports/samd/tusb_port.c @@ -40,6 +40,7 @@ #define USBD_CDC_EP_OUT (0x02) #define USBD_CDC_EP_IN (0x82) #define USBD_CDC_CMD_MAX_SIZE (8) +#define USBD_CDC_IN_OUT_MAX_SIZE (64) #define USBD_STR_0 (0x00) #define USBD_STR_MANUF (0x01) @@ -71,7 +72,7 @@ static const uint8_t usbd_desc_cfg[USBD_DESC_LEN] = { TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, USBD_MAX_POWER_MA), TUD_CDC_DESCRIPTOR(USBD_ITF_CDC, USBD_STR_CDC, USBD_CDC_EP_CMD, - USBD_CDC_CMD_MAX_SIZE, USBD_CDC_EP_OUT, USBD_CDC_EP_IN, CFG_TUD_CDC_RX_BUFSIZE), + USBD_CDC_CMD_MAX_SIZE, USBD_CDC_EP_OUT, USBD_CDC_EP_IN, USBD_CDC_IN_OUT_MAX_SIZE), }; static const char *const usbd_desc_str[] = { diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index fdea95e92f5c9..cf3a589ca9087 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -124,7 +124,7 @@ endif LDFLAGS = -nostdlib -L $(LD_DIR) $(addprefix -T,$(LD_FILES)) -Map=$(@:.elf=.map) --cref LDFLAGS += --defsym=_estack_reserve=8 -LIBS = $(shell $(CC) $(CFLAGS) -print-libgcc-file-name) +LIBS = "$(shell $(CC) $(CFLAGS) -print-libgcc-file-name)" # Remove uncalled code from the final image. CFLAGS += -fdata-sections -ffunction-sections @@ -191,6 +191,7 @@ LIBM_SRC_C = $(addprefix lib/libm_dbl/,\ nearbyint.c \ pow.c \ rint.c \ + round.c \ scalbn.c \ sin.c \ sinh.c \ @@ -258,7 +259,7 @@ DRIVERS_SRC_C = $(addprefix drivers/,\ dht/dht.c \ ) -SRC_C = \ +SRC_C += \ main.c \ stm32_it.c \ usbd_conf.c \ @@ -329,7 +330,7 @@ SRC_C = \ adc.c \ $(wildcard $(BOARD_DIR)/*.c) -SRC_O = \ +SRC_O += \ $(STARTUP_FILE) \ $(SYSTEM_FILE) @@ -472,11 +473,14 @@ endif ifeq ($(MICROPY_SSL_MBEDTLS),1) CFLAGS_MOD += -DMBEDTLS_CONFIG_FILE='"mbedtls/mbedtls_config.h"' SRC_MOD += mbedtls/mbedtls_port.c +# replace mbedtls' error.c by ours +SRC_MOD := $(filter-out %/mbedtls/library/error.c, $(SRC_MOD)) +LIB_SRC_C += lib/mbedtls_errors/mp_mbedtls_errors.c endif ifeq ($(MICROPY_PY_BLUETOOTH),1) -SRC_C += modbluetooth_hci.c +SRC_C += mpbthciport.c ifeq ($(MICROPY_BLUETOOTH_NIMBLE),1) ifeq ($(MICROPY_BLUETOOTH_BTSTACK),1) @@ -486,12 +490,13 @@ endif ifeq ($(MICROPY_BLUETOOTH_NIMBLE),1) include $(TOP)/extmod/nimble/nimble.mk -SRC_C += nimble.c +SRC_C += mpnimbleport.c endif ifeq ($(MICROPY_BLUETOOTH_BTSTACK),1) +MICROPY_BLUETOOTH_BTSTACK_H4 ?= 1 include $(TOP)/extmod/btstack/btstack.mk -SRC_C += btstack.c +SRC_C += mpbtstackport.c endif ifeq ($(MICROPY_PY_NETWORK_CYW43),1) @@ -500,7 +505,6 @@ endif endif -OBJ = OBJ += $(PY_O) OBJ += $(addprefix $(BUILD)/, $(LIB_SRC_C:.c=.o)) OBJ += $(LIBM_O) diff --git a/ports/stm32/adc.c b/ports/stm32/adc.c index bdd4ec1aeb8e8..3793ef5ff28a2 100644 --- a/ports/stm32/adc.c +++ b/ports/stm32/adc.c @@ -110,14 +110,14 @@ #define ADC_CAL2 ((uint16_t *)(0x1FF1E840)) #define ADC_CAL_BITS (16) -#elif defined(STM32L4) +#elif defined(STM32L4) || defined(STM32WB) #define ADC_FIRST_GPIO_CHANNEL (1) #define ADC_LAST_GPIO_CHANNEL (16) -#define ADC_SCALE_V (3.0f) -#define ADC_CAL_ADDRESS (0x1fff75aa) -#define ADC_CAL1 ((uint16_t *)(ADC_CAL_ADDRESS - 2)) -#define ADC_CAL2 ((uint16_t *)(ADC_CAL_ADDRESS + 0x20)) +#define ADC_SCALE_V (VREFINT_CAL_VREF / 1000.0f) +#define ADC_CAL_ADDRESS (VREFINT_CAL_ADDR) +#define ADC_CAL1 (TEMPSENSOR_CAL1_ADDR) +#define ADC_CAL2 (TEMPSENSOR_CAL2_ADDR) #define ADC_CAL_BITS (12) #else @@ -147,7 +147,8 @@ #elif defined(STM32L432xx) || \ defined(STM32L451xx) || defined(STM32L452xx) || \ defined(STM32L462xx) || defined(STM32L475xx) || \ - defined(STM32L476xx) || defined(STM32L496xx) + defined(STM32L476xx) || defined(STM32L496xx) || \ + defined(STM32WB55xx) #define VBAT_DIV (3) #else #error Unsupported processor @@ -203,6 +204,11 @@ STATIC bool is_adcx_channel(int channel) { ADC_HandleTypeDef handle; handle.Instance = ADCx; return IS_ADC_CHANNEL(&handle, channel); + #elif defined(STM32WB) + ADC_HandleTypeDef handle; + handle.Instance = ADCx; + return __HAL_ADC_IS_CHANNEL_INTERNAL(channel) + || IS_ADC_CHANNEL(&handle, __HAL_ADC_DECIMAL_NB_TO_CHANNEL(channel)); #else #error Unsupported processor #endif @@ -212,7 +218,7 @@ STATIC void adc_wait_for_eoc_or_timeout(int32_t timeout) { uint32_t tickstart = HAL_GetTick(); #if defined(STM32F4) || defined(STM32F7) while ((ADCx->SR & ADC_FLAG_EOC) != ADC_FLAG_EOC) { - #elif defined(STM32F0) || defined(STM32H7) || defined(STM32L4) + #elif defined(STM32F0) || defined(STM32H7) || defined(STM32L4) || defined(STM32WB) while (READ_BIT(ADCx->ISR, ADC_FLAG_EOC) != ADC_FLAG_EOC) { #else #error Unsupported processor @@ -229,7 +235,10 @@ STATIC void adcx_clock_enable(void) { #elif defined(STM32H7) __HAL_RCC_ADC3_CLK_ENABLE(); __HAL_RCC_ADC_CONFIG(RCC_ADCCLKSOURCE_CLKP); - #elif defined(STM32L4) + #elif defined(STM32L4) || defined(STM32WB) + if (__HAL_RCC_GET_ADC_SOURCE() == RCC_ADCCLKSOURCE_NONE) { + __HAL_RCC_ADC_CONFIG(RCC_ADCCLKSOURCE_SYSCLK); + } __HAL_RCC_ADC_CLK_ENABLE(); #else #error Unsupported processor @@ -269,7 +278,7 @@ STATIC void adcx_init_periph(ADC_HandleTypeDef *adch, uint32_t resolution) { adch->Init.OversamplingMode = DISABLE; adch->Init.LeftBitShift = ADC_LEFTBITSHIFT_NONE; adch->Init.ConversionDataManagement = ADC_CONVERSIONDATA_DR; - #elif defined(STM32L4) + #elif defined(STM32L4) || defined(STM32WB) adch->Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1; adch->Init.ScanConvMode = ADC_SCAN_DISABLE; adch->Init.LowPowerAutoWait = DISABLE; @@ -286,7 +295,7 @@ STATIC void adcx_init_periph(ADC_HandleTypeDef *adch, uint32_t resolution) { #if defined(STM32H7) HAL_ADCEx_Calibration_Start(adch, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED); #endif - #if defined(STM32L4) + #if defined(STM32L4) || defined(STM32WB) HAL_ADCEx_Calibration_Start(adch, ADC_SINGLE_ENDED); #endif } @@ -313,7 +322,7 @@ STATIC void adc_init_single(pyb_obj_adc_t *adc_obj) { STATIC void adc_config_channel(ADC_HandleTypeDef *adc_handle, uint32_t channel) { ADC_ChannelConfTypeDef sConfig; - #if defined(STM32H7) + #if defined(STM32H7) || defined(STM32WB) sConfig.Rank = ADC_REGULAR_RANK_1; if (__HAL_ADC_IS_CHANNEL_INTERNAL(channel) == 0) { channel = __HAL_ADC_DECIMAL_NB_TO_CHANNEL(channel); @@ -337,7 +346,7 @@ STATIC void adc_config_channel(ADC_HandleTypeDef *adc_handle, uint32_t channel) sConfig.OffsetNumber = ADC_OFFSET_NONE; sConfig.OffsetRightShift = DISABLE; sConfig.OffsetSignedSaturation = DISABLE; - #elif defined(STM32L4) + #elif defined(STM32L4) || defined(STM32WB) if (__HAL_ADC_IS_CHANNEL_INTERNAL(channel)) { sConfig.SamplingTime = ADC_SAMPLETIME_247CYCLES_5; } else { @@ -523,7 +532,7 @@ STATIC mp_obj_t adc_read_timed(mp_obj_t self_in, mp_obj_t buf_in, mp_obj_t freq_ // for subsequent samples we can just set the "start sample" bit #if defined(STM32F4) || defined(STM32F7) ADCx->CR2 |= (uint32_t)ADC_CR2_SWSTART; - #elif defined(STM32F0) || defined(STM32H7) || defined(STM32L4) + #elif defined(STM32F0) || defined(STM32H7) || defined(STM32L4) || defined(STM32WB) SET_BIT(ADCx->CR, ADC_CR_ADSTART); #else #error Unsupported processor @@ -633,7 +642,7 @@ STATIC mp_obj_t adc_read_timed_multi(mp_obj_t adc_array_in, mp_obj_t buf_array_i // ADC is started: set the "start sample" bit #if defined(STM32F4) || defined(STM32F7) ADCx->CR2 |= (uint32_t)ADC_CR2_SWSTART; - #elif defined(STM32F0) || defined(STM32H7) || defined(STM32L4) + #elif defined(STM32F0) || defined(STM32H7) || defined(STM32L4) || defined(STM32WB) SET_BIT(ADCx->CR, ADC_CR_ADSTART); #else #error Unsupported processor diff --git a/ports/stm32/boards/MIKROE_CLICKER2_STM32/mpconfigboard.h b/ports/stm32/boards/MIKROE_CLICKER2_STM32/mpconfigboard.h index eb622cd296a84..71af3ce03823d 100644 --- a/ports/stm32/boards/MIKROE_CLICKER2_STM32/mpconfigboard.h +++ b/ports/stm32/boards/MIKROE_CLICKER2_STM32/mpconfigboard.h @@ -83,3 +83,4 @@ #define MBOOT_BOOTPIN_PULL (MP_HAL_PIN_PULL_NONE) #define MBOOT_BOOTPIN_ACTIVE (0) #define MBOOT_FSLOAD (1) +#define MBOOT_VFS_FAT (1) diff --git a/ports/stm32/boards/NUCLEO_WB55/mpconfigboard.h b/ports/stm32/boards/NUCLEO_WB55/mpconfigboard.h index 2992ccce71772..2061349417c64 100644 --- a/ports/stm32/boards/NUCLEO_WB55/mpconfigboard.h +++ b/ports/stm32/boards/NUCLEO_WB55/mpconfigboard.h @@ -10,7 +10,6 @@ #define MICROPY_HW_HAS_FLASH (1) #define MICROPY_HW_ENABLE_RTC (1) #define MICROPY_HW_ENABLE_RNG (1) -#define MICROPY_HW_ENABLE_ADC (0) #define MICROPY_HW_ENABLE_USB (1) #define MICROPY_HW_HAS_SWITCH (1) @@ -34,7 +33,6 @@ #define MICROPY_HW_I2C3_SDA (pin_C1) // Arduino A1, pin 30 on CN7 // SPI buses -#if 0 // TODO need working DMA #define MICROPY_HW_SPI1_NSS (pin_A4) // Arduino D10 pin 17 on CN10 #define MICROPY_HW_SPI1_SCK (pin_A5) // Arduino D13, pin 11 on CN10 #define MICROPY_HW_SPI1_MISO (pin_A6) // Arduino D12, pin 13 on CN10 @@ -43,7 +41,6 @@ #define MICROPY_HW_SPI2_SCK (pin_B13) // pin 30 on CN10 #define MICROPY_HW_SPI2_MISO (pin_B14) // pin 28 on CN10 #define MICROPY_HW_SPI2_MOSI (pin_B15) // pin 26 on CN10 -#endif // User switch; pressing the button makes the input go low #define MICROPY_HW_USRSW_PIN (pin_C4) diff --git a/ports/stm32/boards/NUCLEO_WB55/mpconfigboard.mk b/ports/stm32/boards/NUCLEO_WB55/mpconfigboard.mk index 416364df9ef6d..dcec788ed443f 100644 --- a/ports/stm32/boards/NUCLEO_WB55/mpconfigboard.mk +++ b/ports/stm32/boards/NUCLEO_WB55/mpconfigboard.mk @@ -1,9 +1,18 @@ MCU_SERIES = wb CMSIS_MCU = STM32WB55xx AF_FILE = boards/stm32wb55_af.csv -LD_FILES = boards/stm32wb55xg.ld boards/common_basic.ld STARTUP_FILE = lib/stm32lib/CMSIS/STM32WBxx/Source/Templates/gcc/startup_stm32wb55xx_cm4.o +ifeq ($(USE_MBOOT),1) +# When using Mboot all the text goes together after the bootloader +LD_FILES = boards/stm32wb55xg.ld boards/common_bl.ld +TEXT0_ADDR = 0x08004000 +else +# When not using Mboot the text goes at the start of flash +LD_FILES = boards/stm32wb55xg.ld boards/common_basic.ld +TEXT0_ADDR = 0x08000000 +endif + # MicroPython settings MICROPY_PY_BLUETOOTH = 1 MICROPY_BLUETOOTH_NIMBLE = 1 diff --git a/ports/stm32/boards/NUCLEO_WB55/pins.csv b/ports/stm32/boards/NUCLEO_WB55/pins.csv index 49fdab0c28c58..d7e0babf36d25 100644 --- a/ports/stm32/boards/NUCLEO_WB55/pins.csv +++ b/ports/stm32/boards/NUCLEO_WB55/pins.csv @@ -6,6 +6,14 @@ ,PA5 ,PA6 ,PA7 +,PA8 +,PA9 +,PA10 +,PA11 +,PA12 +,PA13 +,PA14 +,PA15 ,PB0 ,PB1 ,PB2 @@ -26,7 +34,20 @@ ,PC1 ,PC2 ,PC3 +,PC4 +,PC5 +,PC6 +,PC10 +,PC11 +,PC12 +,PC13 +,PD0 +,PD1 +,PE4 SW,PC4 +SW1,PC4 +SW2,PD0 +SW3,PD1 LED_GREEN,PB0 LED_RED,PB1 LED_BLUE,PB5 diff --git a/ports/stm32/boards/NUCLEO_WB55/rfcore_debug.py b/ports/stm32/boards/NUCLEO_WB55/rfcore_debug.py new file mode 100644 index 0000000000000..4dbead0acc63d --- /dev/null +++ b/ports/stm32/boards/NUCLEO_WB55/rfcore_debug.py @@ -0,0 +1,237 @@ +# This file is part of the MicroPython project, http://micropython.org/ +# +# The MIT License (MIT) +# +# Copyright (c) 2020 Damien P. George +# Copyright (c) 2020 Jim Mussared +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +# This script provides some helpers to allow Python code to access the IPCC +# mechanism in the WB55, and works with the memory layout configured in +# ports/stm32/rfcore.c -- i.e. it expects that rfcore_init() has been run. + +# e.g. +# ../../tools/pyboard.py --device /dev/ttyACM0 boards/NUCLEO_WB55/rfcore.py +# to print out SRAM2A, register state and FUS/WS info. +# +# The `stm` module provides some helper functions to access rfcore functionality. +# See rfcore_firmware.py for more information. + +from machine import mem8, mem16, mem32 +import stm + +SRAM2A_BASE = const(0x2003_0000) + +# for vendor OGF +OGF_VENDOR = const(0x3F) +OCF_FUS_GET_STATE = const(0x52) +OCF_FUS_FW_UPGRADE = const(0x54) +OCF_FUS_FW_DELETE = const(0x55) +OCF_FUS_START_WS = const(0x5A) +OCF_BLE_INIT = const(0x66) + +TABLE_DEVICE_INFO = const(0) +TABLE_BLE = const(1) +TABLE_SYS = const(3) +TABLE_MEM_MANAGER = const(4) + +CHANNEL_BLE = const(1) +CHANNEL_SYS = const(2) +CHANNEL_TRACES = const(4) +CHANNEL_ACL = const(6) + +INDICATOR_HCI_COMMAND = const(0x01) +INDICATOR_HCI_EVENT = const(0x04) +INDICATOR_FUS_COMMAND = const(0x10) +INDICATOR_FUS_RESPONSE = const(0x11) +INDICATOR_FUS_EVENT = const(0x12) + +MAGIC_FUS_ACTIVE = const(0xA94656B9) + + +def get_ipccdba(): + return mem32[stm.FLASH + stm.FLASH_IPCCBR] & 0x3FFF + + +def get_ipcc_table(table): + return mem32[SRAM2A_BASE + get_ipccdba() + table * 4] + + +def get_ipcc_table_word(table, offset): + return mem32[get_ipcc_table(table) + offset * 4] & 0xFFFFFFFF + + +def get_ipcc_table_byte(table, offset): + return mem8[get_ipcc_table(table) + offset] & 0xFF + + +def sram2a_dump(num_words=64, width=8): + print("SRAM2A @%08x" % SRAM2A_BASE) + for i in range((num_words + width - 1) // width): + print(" %04x " % (i * 4 * width), end="") + for j in range(width): + print(" %08x" % (mem32[SRAM2A_BASE + (i * width + j) * 4] & 0xFFFFFFFF), end="") + print() + + +SYS_CMD_BUF = 0 # next*,prev*,type8,...; 272 bytes +SYS_SYS_QUEUE = 0 # next*,prev* + +MM_BLE_SPARE_EVT_BUF = 0 # next*,prev*; 272 bytes +MM_SYS_SPARE_EVT_BUF = 0 # next*,prev*; 272 bytes +MM_BLE_POOL = 0 # ? +MM_BLE_POOL_SIZE = 0 # ? +MM_FREE_BUF_QUEUE = 0 # next*,prev* +MM_EV_POOL = 0 # ? +MM_EV_POOL_SIZE = 0 # ? + +BLE_CMD_BUF = 0 +BLE_CS_BUF = 0 +BLE_EVT_QUEUE = 0 +BLE_HCI_ACL_DATA_BUF = 0 + + +def ipcc_init(): + global SYS_CMD_BUF, SYS_SYS_QUEUE + SYS_CMD_BUF = get_ipcc_table_word(TABLE_SYS, 0) + SYS_SYS_QUEUE = get_ipcc_table_word(TABLE_SYS, 1) + + global MM_BLE_SPARE_EVT_BUF, MM_SYS_SPARE_EVT_BUF, MM_BLE_POOL, MM_BLE_POOL_SIZE, MM_FREE_BUF_QUEUE, MM_EV_POOL, MM_EV_POOL_SIZE + MM_BLE_SPARE_EVT_BUF = get_ipcc_table_word(TABLE_MEM_MANAGER, 0) + MM_SYS_SPARE_EVT_BUF = get_ipcc_table_word(TABLE_MEM_MANAGER, 1) + MM_BLE_POOL = get_ipcc_table_word(TABLE_MEM_MANAGER, 2) + MM_BLE_POOL_SIZE = get_ipcc_table_word(TABLE_MEM_MANAGER, 3) + MM_FREE_BUF_QUEUE = get_ipcc_table_word(TABLE_MEM_MANAGER, 4) + MM_EV_POOL = get_ipcc_table_word(TABLE_MEM_MANAGER, 5) + MM_EV_POOL_SIZE = get_ipcc_table_word(TABLE_MEM_MANAGER, 6) + + global BLE_CMD_BUF, BLE_CS_BUF, BLE_EVT_QUEUE, BLE_HCI_ACL_DATA_BUF + BLE_CMD_BUF = get_ipcc_table_word(TABLE_BLE, 0) + BLE_CS_BUF = get_ipcc_table_word(TABLE_BLE, 1) + BLE_EVT_QUEUE = get_ipcc_table_word(TABLE_BLE, 2) + BLE_HCI_ACL_DATA_BUF = get_ipcc_table_word(TABLE_BLE, 3) + + # Disable interrupts, the code here uses polling + mem32[stm.IPCC + stm.IPCC_C1CR] = 0 + + print("IPCC initialised") + print("SYS: 0x%08x 0x%08x" % (SYS_CMD_BUF, SYS_SYS_QUEUE)) + print("BLE: 0x%08x 0x%08x 0x%08x" % (BLE_CMD_BUF, BLE_CS_BUF, BLE_EVT_QUEUE)) + + +def fus_active(): + return get_ipcc_table_word(TABLE_DEVICE_INFO, 0) == MAGIC_FUS_ACTIVE + + +def info(): + sfr = mem32[stm.FLASH + stm.FLASH_SFR] + srrvr = mem32[stm.FLASH + stm.FLASH_SRRVR] + + print("IPCCDBA : 0x%08x" % (get_ipccdba() & 0x3FFF)) + print("DDS : %r" % bool(sfr & (1 << 12))) + print("FSD : %r" % bool(sfr & (1 << 8))) + print("SFSA : 0x%08x" % (sfr & 0xFF)) + print("C2OPT : %r" % bool(srrvr & (1 << 31))) + print("NBRSD : %r" % bool(srrvr & (1 << 30))) + print("SNBRSA : 0x%08x" % ((srrvr >> 25) & 0x1F)) + print("BRSD : %r" % bool(srrvr & (1 << 23))) + print("SBRSA : 0x%08x" % ((srrvr >> 18) & 0x1F)) + print("SBRV : 0x%08x" % (srrvr & 0x3FFFF)) + + +def dev_info(): + def dump_version(offset): + x = get_ipcc_table_word(TABLE_DEVICE_INFO, offset) + print( + "0x%08x (%u.%u.%u.%u.%u)" + % (x, x >> 24, x >> 16 & 0xFF, x >> 8 & 0xFF, x >> 4 & 0xF, x & 0xF) + ) + + def dump_memory_size(offset): + x = get_ipcc_table_word(TABLE_DEVICE_INFO, offset) + print( + "0x%08x (SRAM2b=%uk SRAM2a=%uk flash=%uk)" + % (x, x >> 24, x >> 16 & 0xFF, (x & 0xFF) * 4) + ) + + print("Device information table @%08x:" % get_ipcc_table(TABLE_DEVICE_INFO)) + if fus_active(): + # layout when running FUS + print("FUS is active") + print("state : 0x%08x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 0)) + print("last FUS active state : 0x%02x" % get_ipcc_table_byte(TABLE_DEVICE_INFO, 5)) + print("last wireless stack state: 0x%02x" % get_ipcc_table_byte(TABLE_DEVICE_INFO, 6)) + print("cur wireless stack type : 0x%02x" % get_ipcc_table_byte(TABLE_DEVICE_INFO, 7)) + print("safe boot version : ", end="") + dump_version(2) + print("FUS version : ", end="") + dump_version(3) + print("FUS memory size : ", end="") + dump_memory_size(4) + print("wireless stack version : ", end="") + dump_version(5) + print("wireless stack mem size : ", end="") + dump_memory_size(6) + print("wireless FW-BLE info : 0x%08x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 7)) + print("wireless FW-thread info : 0x%08x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 8)) + print( + "UID64 : 0x%08x 0x%08x" + % ( + get_ipcc_table_word(TABLE_DEVICE_INFO, 9), + get_ipcc_table_word(TABLE_DEVICE_INFO, 10), + ) + ) + print("device ID : 0x%04x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 11)) + else: + # layout when running WS + print("WS is active") + print("safe boot version : ", end="") + dump_version(0) + print("FUS version : ", end="") + dump_version(1) + print("FUS memory size : ", end="") + dump_memory_size(2) + print("FUS info : 0x%08x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 3)) + print("wireless stack version : ", end="") + dump_version(4) + print("wireless stack mem size : ", end="") + dump_memory_size(5) + print("wireless stack info : 0x%08x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 7)) + print("wireless reserved : 0x%08x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 7)) + + +def ipcc_state(): + print("IPCC:") + print(" C1CR: 0x%08x" % (mem32[stm.IPCC + stm.IPCC_C1CR] & 0xFFFFFFFF), end="") + print(" C2CR: 0x%08x" % (mem32[stm.IPCC + stm.IPCC_C2CR] & 0xFFFFFFFF)) + print(" C1MR: 0x%08x" % (mem32[stm.IPCC + stm.IPCC_C1MR] & 0xFFFFFFFF), end="") + print(" C2MR: 0x%08x" % (mem32[stm.IPCC + stm.IPCC_C2MR] & 0xFFFFFFFF)) + # these always read 0 + # print(' C1SCR: 0x%08x' % (mem32[stm.IPCC + stm.IPCC_C1SCR] & 0xffffffff), end='') + # print(' C2SCR: 0x%08x' % (mem32[stm.IPCC + stm.IPCC_C2SCR] & 0xffffffff)) + print(" C1TOC2SR: 0x%08x" % (mem32[stm.IPCC + stm.IPCC_C1TOC2SR] & 0xFFFFFFFF), end="") + print(" C2TOC1SR: 0x%08x" % (mem32[stm.IPCC + stm.IPCC_C2TOC1SR] & 0xFFFFFFFF)) + + +sram2a_dump(264) +ipcc_init() +info() +dev_info() +ipcc_state() diff --git a/ports/stm32/boards/NUCLEO_WB55/rfcore_firmware.py b/ports/stm32/boards/NUCLEO_WB55/rfcore_firmware.py new file mode 100644 index 0000000000000..b5f1d0072e349 --- /dev/null +++ b/ports/stm32/boards/NUCLEO_WB55/rfcore_firmware.py @@ -0,0 +1,559 @@ +# This file is part of the MicroPython project, http://micropython.org/ +# +# The MIT License (MIT) +# +# Copyright (c) 2020 Damien P. George +# Copyright (c) 2020 Jim Mussared +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +# This script provides helpers for working with the FUS/WS firmware on the WB55. +# It can be frozen into the MicroPython firmware (via manifest.py) +# +# The current FUS and WS firmware version and state can be queried via the +# `stm` module, e.g. +# stm.rfcore_status() (returns the first word of the device info table) +# stm.rfcore_fw_version(id) (returns a 5-tuple indicating fw version; id is: 0=FUS, 1=WS) +# stm.rfcore_sys_hci(ogf, ocf, cmd_buf) (synchronously execute HCI command on SYS channel) +# +# To perform a firmware update: +# +# 1. Generate "obfuscated" binary images using rfcore_makefirmware.py +# ./boards/NUCLEO_WB55/rfcore_makefirmware.py ~/src/github.com/STMicroelectronics/STM32CubeWB/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x/ /tmp +# This will generate /tmp/{fus_102,fus_110,ws_ble_hci}.bin +# +# 2. Copy required files to the device filesystem. +# In general, it's always safe to copy all three files and the updater will +# figure out what needs to be done. This is the recommended option. +# However, if you already have the latest FUS (1.1.0) installed, then just the +# WS firmware is required. +# If a FUS binary is present, then the existing WS will be removed so it's a good +# idea to always include the WS binary if updating FUS. +# Note that a WS binary will not be installed unless FUS 1.1.0 is installed. +# +# 3. Ensure boot.py calls `rfcore_firmware.resume()`. +# The WB55 will reset several times during the firmware update process, so this +# script manages the update state using RTC backup registers. +# `rfcore_firmware.resume()` will continue the update operation on startup to +# resume any in-progress update operation, and either trigger another reset, or +# return 0 to indicate that the operation completed successfully, or a reason +# code (see REASON_* below) to indicate failure. +# +# 4. Call rfcore_firmware.check_for_updates() to start the update process. +# The device will then immediately reboot and when the firmware update completes, +# the status will be returned from rfcore_firmware.resume(). See the REASON_ codes below. +# You can use the built-in stm.rfcore_fw_version() to query the installed version +# from your application code. + +import struct, os +import machine, stm +from micropython import const + +_OGF_VENDOR = const(0x3F) + +_OCF_FUS_GET_STATE = const(0x52) +_OCF_FUS_FW_UPGRADE = const(0x54) +_OCF_FUS_FW_DELETE = const(0x55) +_OCF_FUS_START_WS = const(0x5A) +_OCF_BLE_INIT = const(0x66) + +_HCI_KIND_VENDOR_RESPONSE = const(0x11) + + +# The firmware updater will search all of flash for the image to install, so +# it's important that the file doesn't exist anywhere on the filesystem and +# that the updater only finds the version that we copy into the reserved area. +# Otherwise it will find matching headers/footers in the flash filesystem and +# get confused leading to either "FUS_STATE_IMG_NOT_AUTHENTIC" or (worse) +# corrupting the FUS. +# See footnote [1] referenced by Table 9 in AN5185 - Rev 4 -- the address +# passed to FUS_FW_UPGRADE is ignored (implying that it must be searching the +# flash). This requires that the firmware files have been pre-processed by +# rfcore_makefirmware.py and this key must match the one there. +_OBFUSCATION_KEY = const(0x0573B55AA) + +# On boards using the internal flash filesystem, this must match the +# `_flash_fs_end` symbol defined by the linker script (boards/stm32wb55xg.ld). +# We erase everything from here until the start of the secure area (defined by +# SFSA) just to ensure that no other fragments of firmware files are left +# behind. On boards with external flash, this just needs to ensure that it +# includes any regions that may contain partial firmware data. +# This is non-const so it can be override. +STAGING_AREA_START = 0x80C0000 + +# First word of device info table indicating FUS state (returned by `stm.rfcore_status()`). +_MAGIC_FUS_ACTIVE = const(0xA94656B9) # AN5185 +_MAGIC_IPCC_MEM_INCORRECT = const(0x3DE96F61) # # AN5185 + +# Argument to `stm.rfcore_fw_version()`. +_FW_VERSION_FUS = const(0) +_FW_VERSION_WS = const(1) + +# No firmware update in progress. Boot normally. +_STATE_IDLE = const(0) + +# A previous firmware update failed. Will return reason code from resume(). +_STATE_FAILED = const(1) + +# Trying to get into the FUS. Keep issuing GET_STATE until the FUS is active. +_STATE_WAITING_FOR_FUS = const(2) + +# Trying to get into the WS. Keep issuing START_WS until the WS is active (or fails). +_STATE_WAITING_FOR_WS = const(3) + +# FW_DELETE has been issued. Waiting for the WS version to report zero. +_STATE_DELETING_WS = const(4) + +# Flash copy has started for FUS/WS. If a reboot occurs, then fail. +_STATE_COPYING_FUS = const(5) +_STATE_COPYING_WS = const(6) + +# Flash write fully completed, ready for install. +_STATE_COPIED_FUS = const(7) +_STATE_COPIED_WS = const(8) + +# Check for next update to perform. +# Either we've just gotten into the FUS, or the first update in a sequence +# has completed. (e.g. FUS done, now do WS). +_STATE_CHECK_UPDATES = const(9) + +# Installation has started, keep polling GET_STATE. +_STATE_INSTALLING_WS = const(10) +_STATE_INSTALLING_FUS = const(11) + +# Update completed successfully. +REASON_OK = const(0) +# The device reset during flash copy. Possibly WS still installed. +REASON_FLASH_COPY_FAILED = const(1) +# Unable to start the WS after firmware update. +REASON_NO_WS = const(2) +# Copying FUS image to staging area caused FUS to fail. +REASON_FLASH_FUS_BAD_STATE = const(3) +# Copying WS image to staging area caused FUS to fail. +REASON_FLASH_WS_BAD_STATE = const(4) +# Cannot get into the FUS. Perhaps rfcore misconfigured. +REASON_FUS_NOT_RESPONDING = const(5) +# After a FUS install, unable to get back to the FUS. +REASON_FUS_NOT_RESPONDING_AFTER_FUS = const(6) +# After a WS install, unable to get back to the FUS. +REASON_FUS_NOT_RESPONDING_AFTER_WS = const(7) +# Unable to query rfcore version/active. +REASON_RFCORE_NOT_CONFIGURED = const(8) +# The WS deletion didn't have any effect. +REASON_WS_STILL_PRESENT = const(9) +# FUS refused to delete the WS. +REASON_WS_DELETION_FAILED = const(10) +# FUS returned a specific code for a FUS update. +# See AN5185 Rev 4, Table 12. Reason between 0x00-0x11 will be added. +REASON_FUS_VENDOR = const(0x10) +# FUS returned a specific code for a WS update. Values as for the FUS update. +REASON_WS_VENDOR = const(0x30) + +# FUS 1.0.2 must be installed before FUS 1.1.0 can be installed. +# A factory Nucleo board has FUS (0, 5, 3, 0, 0) and WS (0, 5, 1, 0, 0). +_FUS_VERSION_102 = (1, 0, 2, 0, 0) +_FUS_VERSION_110 = (1, 1, 0, 0, 0) +_PATH_FUS_102 = "fus_102.bin" +_PATH_FUS_110 = "fus_110.bin" +_PATH_WS_BLE_HCI = "ws_ble_hci.bin" + +# This address is correct for versions up to v1.8 (assuming existing firmware deleted). +# Note any address from the end of the filesystem to the SFSA would be fine, but if +# the FUS is fixed in the future to use the specified address then these are the "correct" +# ones. +_ADDR_FUS = 0x080EC000 +_ADDR_WS_BLE_HCI = 0x080DC000 + +# When installing the FUS/WS it can take a long time to return to the first +# GET_STATE HCI command. +# e.g. Installing stm32wb5x_BLE_Stack_full_fw.bin takes 3600ms to respond. +_INSTALLING_FUS_GET_STATE_TIMEOUT = const(1000) +_INSTALLING_WS_GET_STATE_TIMEOUT = const(6000) + + +def log(msg, *args, **kwargs): + print("[rfcore update]", msg.format(*args, **kwargs)) + + +class _Flash: + _FLASH_KEY1 = 0x45670123 + _FLASH_KEY2 = 0xCDEF89AB + + _FLASH_CR_STRT_MASK = 1 << 16 + _FLASH_CR_LOCK_MASK = 1 << 31 + _FLASH_SR_BSY_MASK = 1 << 16 + + def wait_not_busy(self): + while machine.mem32[stm.FLASH + stm.FLASH_SR] & _Flash._FLASH_SR_BSY_MASK: + machine.idle() + + def unlock(self): + if machine.mem32[stm.FLASH + stm.FLASH_CR] & _Flash._FLASH_CR_LOCK_MASK: + # Only unlock if already locked (i.e. FLASH_CR_LOCK is set). + machine.mem32[stm.FLASH + stm.FLASH_KEYR] = _Flash._FLASH_KEY1 + machine.mem32[stm.FLASH + stm.FLASH_KEYR] = _Flash._FLASH_KEY2 + else: + log("Flash was already unlocked.") + + def lock(self): + machine.mem32[stm.FLASH + stm.FLASH_CR] = _Flash._FLASH_CR_LOCK_MASK + + def erase_page(self, page): + assert 0 <= page <= 255 # 1MiB range (4k page) + self.wait_not_busy() + cr = page << 3 | 1 << 1 # PNB # PER + machine.mem32[stm.FLASH + stm.FLASH_CR] = cr + machine.mem32[stm.FLASH + stm.FLASH_CR] = cr | _Flash._FLASH_CR_STRT_MASK + self.wait_not_busy() + machine.mem32[stm.FLASH + stm.FLASH_CR] = 0 + + def write(self, addr, buf, sz, key=0): + assert sz % 4 == 0 + self.wait_not_busy() + cr = 1 << 0 # PG + machine.mem32[stm.FLASH + stm.FLASH_CR] = cr + off = 0 + while off < sz: + v = (buf[off]) | (buf[off + 1] << 8) | (buf[off + 2] << 16) | (buf[off + 3] << 24) + machine.mem32[addr + off] = v ^ key + off += 4 + if off % 8 == 0: + self.wait_not_busy() + if off % 8: + machine.mem32[addr + off] = 0 + self.wait_not_busy() + machine.mem32[stm.FLASH + stm.FLASH_CR] = 0 + + +def _copy_file_to_flash(filename, addr): + flash = _Flash() + flash.unlock() + try: + # Erase the entire staging area in flash. + erase_addr = STAGING_AREA_START + sfr_sfsa = machine.mem32[stm.FLASH + stm.FLASH_SFR] & 0xFF + erase_limit = 0x08000000 + sfr_sfsa * 4096 + while erase_addr < erase_limit: + flash.erase_page((erase_addr - 0x08000000) // 4096) + erase_addr += 4096 + + # Write the contents of the firmware (note flash.write will apply the + # XOR de-obfuscation). + with open(filename, "rb") as f: + buf = bytearray(4096) + + while 1: + sz = f.readinto(buf) + if sz == 0: + break + flash.write(addr, buf, sz, _OBFUSCATION_KEY) + addr += 4096 + + finally: + flash.lock() + + +def _parse_vendor_response(data): + assert len(data) >= 7 + assert data[0] == _HCI_KIND_VENDOR_RESPONSE + assert data[1] == 0x0E + # assert data[3] == 0xff # "Num HCI" -- docs say 0xff, but we see 0x01 + op = (data[5] << 8) | data[4] + return (op >> 10, op & 0x3FF, data[6], data[7] if len(data) > 7 else 0) + + +def _run_sys_hci_cmd(ogf, ocf, buf=b"", timeout=0): + try: + ogf_out, ocf_out, status, result = _parse_vendor_response( + stm.rfcore_sys_hci(ogf, ocf, buf, timeout) + ) + except OSError: + # Timeout or FUS not active. + return (0xFF, 0xFF) + assert ogf_out == ogf + assert ocf_out == ocf + return (status, result) + + +def fus_get_state(timeout=0): + return _run_sys_hci_cmd(_OGF_VENDOR, _OCF_FUS_GET_STATE, timeout=timeout) + + +def fus_is_idle(): + return fus_get_state() == (0, 0) + + +def fus_start_ws(): + return _run_sys_hci_cmd(_OGF_VENDOR, _OCF_FUS_START_WS) + + +def _fus_fwdelete(): + return _run_sys_hci_cmd(_OGF_VENDOR, _OCF_FUS_FW_DELETE) + + +def _fus_run_fwupgrade(addr): + # Note: Address is ignored by the FUS (see comments above). + return _run_sys_hci_cmd(_OGF_VENDOR, _OCF_FUS_FW_UPGRADE, struct.pack(" FUS 1.1.0 -> WS (depending on what's available). + elif state == _STATE_CHECK_UPDATES: + log("Checking for updates") + fus_version = stm.rfcore_fw_version(_FW_VERSION_FUS) + log("FUS version {}", fus_version) + if fus_version < _FUS_VERSION_102: + log("Factory FUS detected") + if _stat_and_start_copy( + _PATH_FUS_102, _ADDR_FUS, _STATE_COPYING_FUS, _STATE_COPIED_FUS + ): + continue + elif fus_version >= _FUS_VERSION_102 and fus_version < _FUS_VERSION_110: + log("FUS 1.0.2 detected") + if _stat_and_start_copy( + _PATH_FUS_110, _ADDR_FUS, _STATE_COPYING_FUS, _STATE_COPIED_FUS + ): + continue + else: + log("FUS is up-to-date") + + if fus_version >= _FUS_VERSION_110: + if _stat_and_start_copy( + _PATH_WS_BLE_HCI, _ADDR_WS_BLE_HCI, _STATE_COPYING_WS, _STATE_COPIED_WS + ): + continue + else: + log("No WS updates available") + else: + # Don't attempt to install WS if we're running an old FUS. + log("Need latest FUS to install WS") + + # Attempt to go back to WS. + # Either this will fail (because WS was removed due to FUS install), or + # this whole thing was a no-op and we should be fine to restart WS. + _write_state(_STATE_WAITING_FOR_WS) + + # This shouldn't happen - the flash write should always complete and + # move straight onto the COPIED state. Failure here indicates that + # the rfcore is misconfigured or the WS firmware was not deleted first. + elif state == _STATE_COPYING_FUS or state == _STATE_COPYING_WS: + log("Flash copy failed mid-write") + _write_failure_state(REASON_FLASH_COPY_FAILED) + + # Flash write completed, we should immediately see GET_STATE return 0,0 + # so we can start the FUS install. + elif state == _STATE_COPIED_FUS: + if fus_is_idle(): + log("FUS copy complete, installing") + _write_state(_STATE_INSTALLING_FUS) + _fus_run_fwupgrade(_ADDR_FUS) + else: + log("FUS copy bad state") + _write_failure_state(REASON_FLASH_FUS_BAD_STATE) + + # Keep polling the state until we see a 0,0 (success) or non-transient + # error. In general we should expect to see (16,0) several times, + # followed by a (255,0), followed by (0, 0). + elif state == _STATE_INSTALLING_FUS: + log("Installing FUS...") + status, result = fus_get_state(_INSTALLING_FUS_GET_STATE_TIMEOUT) + log("FUS state: {} {}", status, result) + if 0x20 <= status <= 0x2F and result == 0: + # FUS_STATE_FUS_UPGRD_ONGOING + log("FUS still in progress...") + elif 0x10 <= status <= 0x1F and result == 0x11: + # FUS_STATE_FW_UPGRD_ONGOING and FUS_FW_ROLLBACK_ERROR + # Confusingly this is a "FW_UPGRD" (0x10) not "FUS_UPRD" (0x20). + log("Attempted to install same FUS version... re-querying FUS state to resume.") + elif status == 0: + log("FUS update successful") + _write_state(_STATE_CHECK_UPDATES) + elif result == 0: + # See below (for equivalent path for WS install -- we + # sometimes see (255,0) right at the end). + log("Re-querying FUS state...") + elif result == 0xFF: + _write_failure_state(REASON_FUS_NOT_RESPONDING_AFTER_FUS) + else: + _write_failure_state(REASON_FUS_VENDOR + result) + + # Keep polling the state until we see 0,0 or failure (1,0). Any other + # result means retry (but the docs say that 0 and 1 are the only + # status values). + elif state == _STATE_DELETING_WS: + log("Deleting WS...") + status, result = fus_get_state() + log("FUS state: {} {}", status, result) + if status == 0: + if sum(stm.rfcore_fw_version(_FW_VERSION_WS)) == 0: + log("WS deletion complete") + _write_state(_STATE_CHECK_UPDATES) + else: + log("WS deletion no effect") + _write_failure_state(REASON_WS_STILL_PRESENT) + elif status == 1: + log("WS deletion failed") + _write_failure_state(REASON_WS_DELETION_FAILED) + + # As for _STATE_COPIED_FUS above. We should immediately see 0,0. + elif state == _STATE_COPIED_WS: + if fus_is_idle(): + log("WS copy complete, installing") + _write_state(_STATE_INSTALLING_WS) + _fus_run_fwupgrade(_ADDR_WS_BLE_HCI) + else: + log("WS copy bad state") + _write_failure_state(REASON_FLASH_WS_BAD_STATE) + + # As for _STATE_INSTALLING_FUS above. + elif state == _STATE_INSTALLING_WS: + log("Installing WS...") + status, result = fus_get_state(_INSTALLING_WS_GET_STATE_TIMEOUT) + log("FUS state: {} {}", status, result) + if 0x10 <= status <= 0x1F and result == 0: + # FUS_STATE_FW_UPGRD_ONGOING + log("WS still in progress...") + elif 0x10 <= status <= 0x1F and result == 0x11: + # FUS_FW_ROLLBACK_ERROR + log("Attempted to install same WS version... re-querying FUS state to resume.") + elif status == 0: + log("WS update successful") + _write_state(_STATE_WAITING_FOR_WS) + elif result == 0: + # We get a error response with no payload sometimes at the end + # of the update (this is not in AN5185). Re-try the GET_STATE. + # The same thing happens transitioning from WS to FUS mode. + # The actual HCI response has no payload, the result=0 comes from + # _parse_vendor_response above when len=7. + log("Re-querying FUS state...") + elif result == 0xFF: + # This is specifically a failure sending the HCI command. + _write_failure_state(REASON_FUS_NOT_RESPONDING_AFTER_WS) + else: + _write_failure_state(REASON_WS_VENDOR + result) + + +# Start a firmware update. +# This will immediately trigger a reset and start the update process on boot. +def check_for_updates(): + log("Starting firmware update") + _write_state(_STATE_WAITING_FOR_FUS) + machine.reset() diff --git a/ports/stm32/boards/NUCLEO_WB55/rfcore_makefirmware.py b/ports/stm32/boards/NUCLEO_WB55/rfcore_makefirmware.py new file mode 100755 index 0000000000000..23f3d20f0c9ce --- /dev/null +++ b/ports/stm32/boards/NUCLEO_WB55/rfcore_makefirmware.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +# +# This file is part of the MicroPython project, http://micropython.org/ +# +# The MIT License (MIT) +# +# Copyright (c) 2020 Jim Mussared +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +# This script obfuscates the ST wireless binaries so they can be safely copied +# to the flash filesystem and not be accidentally discovered by the FUS during +# an update. See more information (and the corresponding de-obfuscation) in +# rfcore_firmware.py as well as instructions on how to use. + +import os +import struct +import sys + +# Must match rfcore_firmware.py. +_OBFUSCATION_KEY = 0x0573B55AA + +_FIRMWARE_FILES = { + "stm32wb5x_FUS_fw_1_0_2.bin": "fus_102.bin", + "stm32wb5x_FUS_fw.bin": "fus_110.bin", + "stm32wb5x_BLE_HCILayer_fw.bin": "ws_ble_hci.bin", +} + + +def main(src_path, dest_path): + for src_file, dest_file in _FIRMWARE_FILES.items(): + src_file = os.path.join(src_path, src_file) + dest_file = os.path.join(dest_path, dest_file) + if not os.path.exists(src_file): + print("Unable to find: {}".format(src_file)) + continue + sz = 0 + with open(src_file, "rb") as src: + with open(dest_file, "wb") as dest: + while True: + b = src.read(4) + if not b: + break + (v,) = struct.unpack("RAM2A + + /* Put all IPCC buffers into SRAM2B. */ + .ram2b_bss : + { + . = ALIGN(4); + *rfcore.o(.bss.ipcc_membuf_*) + . = ALIGN(4); + } >RAM2B } diff --git a/ports/stm32/dma.c b/ports/stm32/dma.c index cbcc8542a19a2..4320315ba4e5d 100644 --- a/ports/stm32/dma.c +++ b/ports/stm32/dma.c @@ -34,18 +34,6 @@ #include "dma.h" #include "irq.h" -#if defined(STM32WB) - -// DMA is currently not implemented for this MCU - -void dma_init(DMA_HandleTypeDef *dma, const dma_descr_t *dma_descr, uint32_t dir, void *data) { -} - -void dma_deinit(const dma_descr_t *dma_descr) { -} - -#else - #define DMA_IDLE_ENABLED() (dma_idle.enabled != 0) #define DMA_SYSTICK_LOG2 (3) #define DMA_SYSTICK_MASK ((1 << DMA_SYSTICK_LOG2) - 1) @@ -82,7 +70,7 @@ typedef union { struct _dma_descr_t { #if defined(STM32F4) || defined(STM32F7) || defined(STM32H7) DMA_Stream_TypeDef *instance; - #elif defined(STM32F0) || defined(STM32L0) || defined(STM32L4) + #elif defined(STM32F0) || defined(STM32L0) || defined(STM32L4) || defined(STM32WB) DMA_Channel_TypeDef *instance; #else #error "Unsupported Processor" @@ -97,7 +85,7 @@ struct _dma_descr_t { static const DMA_InitTypeDef dma_init_struct_spi_i2c = { #if defined(STM32F4) || defined(STM32F7) .Channel = 0, - #elif defined(STM32H7) || defined(STM32L0) || defined(STM32L4) + #elif defined(STM32H7) || defined(STM32L0) || defined(STM32L4) || defined(STM32WB) .Request = 0, #endif .Direction = 0, @@ -120,7 +108,7 @@ static const DMA_InitTypeDef dma_init_struct_spi_i2c = { static const DMA_InitTypeDef dma_init_struct_sdio = { #if defined(STM32F4) || defined(STM32F7) .Channel = 0, - #elif defined(STM32L0) || defined(STM32L4) + #elif defined(STM32L0) || defined(STM32L4) || defined(STM32WB) .Request = 0, #endif .Direction = 0, @@ -130,7 +118,7 @@ static const DMA_InitTypeDef dma_init_struct_sdio = { .MemDataAlignment = DMA_MDATAALIGN_WORD, #if defined(STM32F4) || defined(STM32F7) .Mode = DMA_PFCTRL, - #elif defined(STM32L0) || defined(STM32L4) + #elif defined(STM32L0) || defined(STM32L4) || defined(STM32WB) .Mode = DMA_NORMAL, #endif .Priority = DMA_PRIORITY_VERY_HIGH, @@ -148,7 +136,7 @@ static const DMA_InitTypeDef dma_init_struct_sdio = { static const DMA_InitTypeDef dma_init_struct_dac = { #if defined(STM32F4) || defined(STM32F7) .Channel = 0, - #elif defined(STM32H7) || defined(STM32L0) || defined(STM32L4) + #elif defined(STM32H7) || defined(STM32L0) || defined(STM32L4) || defined(STM32WB) .Request = 0, #endif .Direction = 0, @@ -438,6 +426,40 @@ static const uint8_t dma_irqn[NSTREAM] = { DMA2_Channel7_IRQn, }; +#elif defined(STM32WB) + +#define NCONTROLLERS (2) +#define NSTREAMS_PER_CONTROLLER (7) +#define NSTREAM (NCONTROLLERS * NSTREAMS_PER_CONTROLLER) + +#define DMA_SUB_INSTANCE_AS_UINT8(dma_request) (dma_request) + +#define DMA1_ENABLE_MASK (0x007f) // Bits in dma_enable_mask corresponding to DMA1 +#define DMA2_ENABLE_MASK (0x3f80) // Bits in dma_enable_mask corresponding to DMA2 + +// DMA1 streams +const dma_descr_t dma_SPI_1_RX = { DMA1_Channel1, DMA_REQUEST_SPI1_RX, dma_id_0, &dma_init_struct_spi_i2c }; +const dma_descr_t dma_SPI_1_TX = { DMA1_Channel2, DMA_REQUEST_SPI1_TX, dma_id_1, &dma_init_struct_spi_i2c }; +const dma_descr_t dma_SPI_2_RX = { DMA1_Channel3, DMA_REQUEST_SPI2_RX, dma_id_2, &dma_init_struct_spi_i2c }; +const dma_descr_t dma_SPI_2_TX = { DMA1_Channel4, DMA_REQUEST_SPI2_TX, dma_id_3, &dma_init_struct_spi_i2c }; + +static const uint8_t dma_irqn[NSTREAM] = { + DMA1_Channel1_IRQn, + DMA1_Channel2_IRQn, + DMA1_Channel3_IRQn, + DMA1_Channel4_IRQn, + DMA1_Channel5_IRQn, + DMA1_Channel6_IRQn, + DMA1_Channel7_IRQn, + DMA2_Channel1_IRQn, + DMA2_Channel2_IRQn, + DMA2_Channel3_IRQn, + DMA2_Channel4_IRQn, + DMA2_Channel5_IRQn, + DMA2_Channel6_IRQn, + DMA2_Channel7_IRQn, +}; + #elif defined(STM32H7) #define NCONTROLLERS (2) @@ -717,7 +739,7 @@ void DMA1_Channel4_5_6_7_IRQHandler(void) { IRQ_EXIT(DMA1_Channel4_5_6_7_IRQn); } -#elif defined(STM32L4) +#elif defined(STM32L4) || defined(STM32WB) void DMA1_Channel1_IRQHandler(void) { IRQ_ENTER(DMA1_Channel1_IRQn); @@ -836,6 +858,13 @@ static void dma_enable_clock(dma_id_t dma_id) { dma_enable_mask |= (1 << dma_id); MICROPY_END_ATOMIC_SECTION(irq_state); + #if defined(STM32WB) + // This MCU has a DMAMUX peripheral which needs to be enabled to multiplex the channels. + if (!__HAL_RCC_DMAMUX1_IS_CLK_ENABLED()) { + __HAL_RCC_DMAMUX1_CLK_ENABLE(); + } + #endif + if (dma_id < NSTREAMS_PER_CONTROLLER) { if (((old_enable_mask & DMA1_ENABLE_MASK) == 0) && !DMA1_IS_CLK_ENABLED()) { __HAL_RCC_DMA1_CLK_ENABLE(); @@ -877,7 +906,7 @@ void dma_init_handle(DMA_HandleTypeDef *dma, const dma_descr_t *dma_descr, uint3 dma->Instance = dma_descr->instance; dma->Init = *dma_descr->init; dma->Init.Direction = dir; - #if defined(STM32L0) || defined(STM32L4) || defined(STM32H7) + #if defined(STM32L0) || defined(STM32L4) || defined(STM32H7) || defined(STM32WB) dma->Init.Request = dma_descr->sub_instance; #else #if !defined(STM32F0) @@ -904,7 +933,7 @@ void dma_init(DMA_HandleTypeDef *dma, const dma_descr_t *dma_descr, uint32_t dir dma_enable_clock(dma_id); - #if defined(STM32H7) || defined(STM32L0) || defined(STM32L4) + #if defined(STM32H7) || defined(STM32L0) || defined(STM32L4) || defined(STM32WB) // Always reset and configure the H7 and L0/L4 DMA peripheral // (dma->State is set to HAL_DMA_STATE_RESET by memset above) // TODO: understand how L0/L4 DMA works so this is not needed @@ -1062,6 +1091,10 @@ void dma_nohal_start(const dma_descr_t *descr, uint32_t src_addr, uint32_t dst_a dma->CCR |= DMA_CCR_EN; } +#elif defined(STM32WB) + +// These functions are currently not implemented or needed for this MCU. + #else void dma_nohal_init(const dma_descr_t *descr, uint32_t config) { @@ -1135,5 +1168,3 @@ void dma_nohal_start(const dma_descr_t *descr, uint32_t src_addr, uint32_t dst_a } #endif - -#endif // defined(STM32WB) diff --git a/ports/stm32/factoryreset.c b/ports/stm32/factoryreset.c index 04591101e87df..725ecd12a871c 100644 --- a/ports/stm32/factoryreset.c +++ b/ports/stm32/factoryreset.c @@ -34,6 +34,8 @@ #if MICROPY_HW_ENABLE_STORAGE +#if MICROPY_VFS_FAT + static const char fresh_boot_py[] = "# boot.py -- run on boot-up\r\n" "# can run arbitrary Python, but best to keep it minimal\r\n" @@ -128,4 +130,13 @@ MP_WEAK int factory_reset_create_filesystem(void) { return 0; // success } +#else + +// If FAT is not enabled then it's up to the board to create a fresh filesystem. +MP_WEAK int factory_reset_create_filesystem(void) { + return 0; // success +} + +#endif + #endif // MICROPY_HW_ENABLE_STORAGE diff --git a/ports/stm32/fdcan.c b/ports/stm32/fdcan.c index b3b1da998c79d..2892572f40a96 100644 --- a/ports/stm32/fdcan.c +++ b/ports/stm32/fdcan.c @@ -200,26 +200,38 @@ void can_clearfilter(pyb_can_obj_t *self, uint32_t f, uint8_t bank) { int can_receive(FDCAN_HandleTypeDef *can, int fifo, FDCAN_RxHeaderTypeDef *hdr, uint8_t *data, uint32_t timeout_ms) { volatile uint32_t *rxf, *rxa; + uint32_t fl; + if (fifo == FDCAN_RX_FIFO0) { rxf = &can->Instance->RXF0S; rxa = &can->Instance->RXF0A; + fl = FDCAN_RXF0S_F0FL; } else { rxf = &can->Instance->RXF1S; rxa = &can->Instance->RXF1A; + fl = FDCAN_RXF1S_F1FL; } // Wait for a message to become available, with timeout uint32_t start = HAL_GetTick(); - while ((*rxf & 7) == 0) { - MICROPY_EVENT_POLL_HOOK - if (HAL_GetTick() - start >= timeout_ms) { - return -MP_ETIMEDOUT; + while ((*rxf & fl) == 0) { + if (timeout_ms != HAL_MAX_DELAY) { + if (HAL_GetTick() - start >= timeout_ms) { + return -MP_ETIMEDOUT; + } } + MICROPY_EVENT_POLL_HOOK } // Get pointer to incoming message - uint32_t index = (can->Instance->RXF0S & FDCAN_RXF0S_F0GI) >> 8; - uint32_t *address = (uint32_t *)(can->msgRam.RxFIFO0SA + (index * can->Init.RxFifo0ElmtSize * 4)); + uint32_t index, *address; + if (fifo == FDCAN_RX_FIFO0) { + index = (*rxf & FDCAN_RXF0S_F0GI) >> FDCAN_RXF0S_F0GI_Pos; + address = (uint32_t *)(can->msgRam.RxFIFO0SA + (index * can->Init.RxFifo0ElmtSize * 4)); + } else { + index = (*rxf & FDCAN_RXF1S_F1GI) >> FDCAN_RXF1S_F1GI_Pos; + address = (uint32_t *)(can->msgRam.RxFIFO1SA + (index * can->Init.RxFifo1ElmtSize * 4)); + } // Parse header of message hdr->IdType = *address & FDCAN_ELEMENT_MASK_XTD; diff --git a/ports/stm32/flash.c b/ports/stm32/flash.c index 99c95f7d993ed..499129a6f3e5d 100644 --- a/ports/stm32/flash.c +++ b/ports/stm32/flash.c @@ -26,6 +26,7 @@ #include "py/mpconfig.h" #include "py/misc.h" +#include "py/mphal.h" #include "flash.h" typedef struct { @@ -69,10 +70,15 @@ static const flash_layout_t flash_layout[] = { { 0x08020000, 0x20000, 3 }, }; #else +// This is for dual-bank mode disabled static const flash_layout_t flash_layout[] = { { 0x08000000, 0x08000, 4 }, { 0x08020000, 0x20000, 1 }, + #if FLASH_SECTOR_TOTAL == 8 { 0x08040000, 0x40000, 3 }, + #else + { 0x08040000, 0x40000, 7 }, + #endif }; #endif @@ -138,7 +144,14 @@ static uint32_t get_page(uint32_t addr) { #endif -uint32_t flash_get_sector_info(uint32_t addr, uint32_t *start_addr, uint32_t *size) { +bool flash_is_valid_addr(uint32_t addr) { + uint8_t last = MP_ARRAY_SIZE(flash_layout) - 1; + uint32_t end_of_flash = flash_layout[last].base_address + + flash_layout[last].sector_count * flash_layout[last].sector_size; + return flash_layout[0].base_address <= addr && addr < end_of_flash; +} + +int32_t flash_get_sector_info(uint32_t addr, uint32_t *start_addr, uint32_t *size) { if (addr >= flash_layout[0].base_address) { uint32_t sector_index = 0; for (int i = 0; i < MP_ARRAY_SIZE(flash_layout); ++i) { @@ -159,20 +172,21 @@ uint32_t flash_get_sector_info(uint32_t addr, uint32_t *start_addr, uint32_t *si } } } - return 0; + return -1; } -void flash_erase(uint32_t flash_dest, uint32_t num_word32) { +int flash_erase(uint32_t flash_dest, uint32_t num_word32) { // check there is something to write if (num_word32 == 0) { - return; + return 0; } - // unlock + // Unlock the flash for erase. HAL_FLASH_Unlock(); - FLASH_EraseInitTypeDef EraseInitStruct; + // Clear pending flags (if any) and set up EraseInitStruct. + FLASH_EraseInitTypeDef EraseInitStruct; #if defined(STM32F0) __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_WRPERR | FLASH_FLAG_PGERR); EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES; @@ -190,17 +204,14 @@ void flash_erase(uint32_t flash_dest, uint32_t num_word32) { EraseInitStruct.NbPages = (4 * num_word32 + FLASH_PAGE_SIZE - 4) / FLASH_PAGE_SIZE; #elif defined(STM32L4) __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS); - - // erase the sector(s) // The sector returned by flash_get_sector_info can not be used // as the flash has on each bank 0/1 pages 0..255 EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES; EraseInitStruct.Banks = get_bank(flash_dest); EraseInitStruct.Page = get_page(flash_dest); EraseInitStruct.NbPages = get_page(flash_dest + 4 * num_word32 - 1) - EraseInitStruct.Page + 1; - ; #else - // Clear pending flags (if any) + #if defined(STM32H7) __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS_BANK1 | FLASH_FLAG_ALL_ERRORS_BANK2); #else @@ -208,7 +219,6 @@ void flash_erase(uint32_t flash_dest, uint32_t num_word32) { FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR); #endif - // erase the sector(s) EraseInitStruct.TypeErase = TYPEERASE_SECTORS; EraseInitStruct.VoltageRange = VOLTAGE_RANGE_3; // voltage range needs to be 2.7V to 3.6V #if defined(STM32H7) @@ -216,14 +226,17 @@ void flash_erase(uint32_t flash_dest, uint32_t num_word32) { #endif EraseInitStruct.Sector = flash_get_sector_info(flash_dest, NULL, NULL); EraseInitStruct.NbSectors = flash_get_sector_info(flash_dest + 4 * num_word32 - 1, NULL, NULL) - EraseInitStruct.Sector + 1; + #endif + // Erase the sectors. uint32_t SectorError = 0; - if (HAL_FLASHEx_Erase(&EraseInitStruct, &SectorError) != HAL_OK) { - // error occurred during sector erase - HAL_FLASH_Lock(); // lock the flash - return; - } + HAL_StatusTypeDef status = HAL_FLASHEx_Erase(&EraseInitStruct, &SectorError); + + // Lock the flash after erase. + HAL_FLASH_Lock(); + + return mp_hal_status_to_neg_errno(status); } /* @@ -255,16 +268,21 @@ void flash_erase_it(uint32_t flash_dest, uint32_t num_word32) { } */ -void flash_write(uint32_t flash_dest, const uint32_t *src, uint32_t num_word32) { +int flash_write(uint32_t flash_dest, const uint32_t *src, uint32_t num_word32) { + // Unlock the flash for write. + HAL_FLASH_Unlock(); + + HAL_StatusTypeDef status = HAL_OK; + #if defined(STM32L4) || defined(STM32WB) // program the flash uint64 by uint64 for (int i = 0; i < num_word32 / 2; i++) { uint64_t val = *(uint64_t *)src; - if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, flash_dest, val) != HAL_OK) { - // error occurred during flash write - HAL_FLASH_Lock(); // lock the flash - return; + status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, flash_dest, val); + if (status != HAL_OK) { + num_word32 = 0; // don't write any odd word after this loop + break; } flash_dest += 8; src += 2; @@ -272,21 +290,16 @@ void flash_write(uint32_t flash_dest, const uint32_t *src, uint32_t num_word32) if ((num_word32 & 0x01) == 1) { uint64_t val = *(uint64_t *)flash_dest; val = (val & 0xffffffff00000000uL) | (*src); - if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, flash_dest, val) != HAL_OK) { - // error occurred during flash write - HAL_FLASH_Lock(); // lock the flash - return; - } + status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, flash_dest, val); } #elif defined(STM32H7) // program the flash 256 bits at a time for (int i = 0; i < num_word32 / 8; i++) { - if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, flash_dest, (uint64_t)(uint32_t)src) != HAL_OK) { - // error occurred during flash write - HAL_FLASH_Lock(); // lock the flash - return; + status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, flash_dest, (uint64_t)(uint32_t)src); + if (status != HAL_OK) { + break; } flash_dest += 32; src += 8; @@ -296,10 +309,9 @@ void flash_write(uint32_t flash_dest, const uint32_t *src, uint32_t num_word32) // program the flash word by word for (int i = 0; i < num_word32; i++) { - if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, flash_dest, *src) != HAL_OK) { - // error occurred during flash write - HAL_FLASH_Lock(); // lock the flash - return; + status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, flash_dest, *src); + if (status != HAL_OK) { + break; } flash_dest += 4; src += 1; @@ -307,8 +319,10 @@ void flash_write(uint32_t flash_dest, const uint32_t *src, uint32_t num_word32) #endif - // lock the flash + // Lock the flash after write. HAL_FLASH_Lock(); + + return mp_hal_status_to_neg_errno(status); } /* diff --git a/ports/stm32/flash.h b/ports/stm32/flash.h index b9edf610610cb..ecda923db7826 100644 --- a/ports/stm32/flash.h +++ b/ports/stm32/flash.h @@ -26,8 +26,9 @@ #ifndef MICROPY_INCLUDED_STM32_FLASH_H #define MICROPY_INCLUDED_STM32_FLASH_H -uint32_t flash_get_sector_info(uint32_t addr, uint32_t *start_addr, uint32_t *size); -void flash_erase(uint32_t flash_dest, uint32_t num_word32); -void flash_write(uint32_t flash_dest, const uint32_t *src, uint32_t num_word32); +bool flash_is_valid_addr(uint32_t addr); +int32_t flash_get_sector_info(uint32_t addr, uint32_t *start_addr, uint32_t *size); +int flash_erase(uint32_t flash_dest, uint32_t num_word32); +int flash_write(uint32_t flash_dest, const uint32_t *src, uint32_t num_word32); #endif // MICROPY_INCLUDED_STM32_FLASH_H diff --git a/ports/stm32/flashbdev.c b/ports/stm32/flashbdev.c index e105bd35381b9..4153a713c8636 100644 --- a/ports/stm32/flashbdev.c +++ b/ports/stm32/flashbdev.c @@ -181,7 +181,7 @@ int32_t flash_bdev_ioctl(uint32_t op, uint32_t arg) { static uint8_t *flash_cache_get_addr_for_write(uint32_t flash_addr) { uint32_t flash_sector_start; uint32_t flash_sector_size; - uint32_t flash_sector_id = flash_get_sector_info(flash_addr, &flash_sector_start, &flash_sector_size); + int32_t flash_sector_id = flash_get_sector_info(flash_addr, &flash_sector_start, &flash_sector_size); if (flash_sector_size > FLASH_SECTOR_SIZE_MAX) { flash_sector_size = FLASH_SECTOR_SIZE_MAX; } @@ -201,7 +201,7 @@ static uint8_t *flash_cache_get_addr_for_write(uint32_t flash_addr) { static uint8_t *flash_cache_get_addr_for_read(uint32_t flash_addr) { uint32_t flash_sector_start; uint32_t flash_sector_size; - uint32_t flash_sector_id = flash_get_sector_info(flash_addr, &flash_sector_start, &flash_sector_size); + int32_t flash_sector_id = flash_get_sector_info(flash_addr, &flash_sector_start, &flash_sector_size); if (flash_cache_sector_id == flash_sector_id) { // in cache, copy from there return (uint8_t *)CACHE_MEM_START_ADDR + flash_addr - flash_sector_start; diff --git a/ports/stm32/i2cslave.c b/ports/stm32/i2cslave.c index cacc00e0c4141..a575c53085168 100644 --- a/ports/stm32/i2cslave.c +++ b/ports/stm32/i2cslave.c @@ -42,25 +42,25 @@ void i2c_slave_ev_irq_handler(i2c_slave_t *i2c) { // Read of SR1, SR2 needed to clear ADDR bit sr1 = i2c->SR1; uint32_t sr2 = i2c->SR2; - i2c_slave_process_addr_match((sr2 >> I2C_SR2_TRA_Pos) & 1); + 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->DR = i2c_slave_process_tx_byte(i2c); } if (sr1 & I2C_SR1_RXNE) { - i2c_slave_process_rx_byte(i2c->DR); + i2c_slave_process_rx_byte(i2c, i2c->DR); } if (sr1 & I2C_SR1_STOPF) { // STOPF only set at end of RX mode (in TX mode AF is set on NACK) // Read of SR1, write CR1 needed to clear STOPF bit sr1 = i2c->SR1; i2c->CR1 &= ~I2C_CR1_ACK; - i2c_slave_process_rx_end(); + i2c_slave_process_rx_end(i2c); i2c->CR1 |= I2C_CR1_ACK; } } -#elif defined(STM32F7) || defined(STM32H7) +#elif defined(STM32F7) || defined(STM32H7) || defined(STM32WB) void i2c_slave_init_helper(i2c_slave_t *i2c, int addr) { i2c->CR1 = I2C_CR1_STOPIE | I2C_CR1_ADDRIE | I2C_CR1_RXIE | I2C_CR1_TXIE; @@ -77,22 +77,22 @@ void i2c_slave_ev_irq_handler(i2c_slave_t *i2c) { // Set TXE so that TXDR is flushed and ready for the first byte i2c->ISR = I2C_ISR_TXE; i2c->ICR = I2C_ICR_ADDRCF; - i2c_slave_process_addr_match(0); + i2c_slave_process_addr_match(i2c, (i2c->ISR >> I2C_ISR_DIR_Pos) & 1); } if (isr & I2C_ISR_TXIS) { - i2c->TXDR = i2c_slave_process_tx_byte(); + i2c->TXDR = i2c_slave_process_tx_byte(i2c); } if (isr & I2C_ISR_RXNE) { - i2c_slave_process_rx_byte(i2c->RXDR); + i2c_slave_process_rx_byte(i2c, i2c->RXDR); } if (isr & I2C_ISR_STOPF) { // STOPF only set for STOP condition, not a repeated START i2c->ICR = I2C_ICR_STOPCF; i2c->OAR1 &= ~I2C_OAR1_OA1EN; if (i2c->ISR & I2C_ISR_DIR) { - // i2c_slave_process_tx_end(); + i2c_slave_process_tx_end(i2c); } else { - i2c_slave_process_rx_end(); + i2c_slave_process_rx_end(i2c); } i2c->OAR1 |= I2C_OAR1_OA1EN; } diff --git a/ports/stm32/i2cslave.h b/ports/stm32/i2cslave.h index 55882acd8eb2e..4a51bf378e924 100644 --- a/ports/stm32/i2cslave.h +++ b/ports/stm32/i2cslave.h @@ -28,6 +28,11 @@ #include STM32_HAL_H +#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)) +#endif + typedef I2C_TypeDef i2c_slave_t; void i2c_slave_init_helper(i2c_slave_t *i2c, int addr); @@ -42,6 +47,10 @@ static inline void i2c_slave_init(i2c_slave_t *i2c, int irqn, int irq_pri, int a RCC->APB1LENR |= 1 << (RCC_APB1LENR_I2C1EN_Pos + i2c_idx); volatile uint32_t tmp = RCC->APB1LENR; // Delay after enabling clock (void)tmp; + #elif defined(STM32WB) + RCC->APB1ENR1 |= 1 << (RCC_APB1ENR1_I2C1EN_Pos + i2c_idx); + volatile uint32_t tmp = RCC->APB1ENR1; // Delay after enabling clock + (void)tmp; #endif i2c_slave_init_helper(i2c, addr); @@ -58,9 +67,10 @@ static inline void i2c_slave_shutdown(i2c_slave_t *i2c, int irqn) { void i2c_slave_ev_irq_handler(i2c_slave_t *i2c); // These should be provided externally -int i2c_slave_process_addr_match(int rw); -int i2c_slave_process_rx_byte(uint8_t val); -void i2c_slave_process_rx_end(void); -uint8_t i2c_slave_process_tx_byte(void); +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); +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_end(i2c_slave_t *i2c); #endif // MICROPY_INCLUDED_STM32_I2CSLAVE_H diff --git a/ports/stm32/irq.c b/ports/stm32/irq.c index 0100899733098..fdaf2385cc9d1 100644 --- a/ports/stm32/irq.c +++ b/ports/stm32/irq.c @@ -27,44 +27,28 @@ #include "py/obj.h" #include "py/mphal.h" #include "irq.h" - -/// \moduleref pyb +#include "modmachine.h" #if IRQ_ENABLE_STATS uint32_t irq_stats[IRQ_STATS_MAX] = {0}; #endif -/// \function wfi() -/// Wait for an interrupt. -/// This executies a `wfi` instruction which reduces power consumption -/// of the MCU until an interrupt occurs, at which point execution continues. -STATIC mp_obj_t pyb_wfi(void) { - __WFI(); - return mp_const_none; -} -MP_DEFINE_CONST_FUN_OBJ_0(pyb_wfi_obj, pyb_wfi); - -/// \function disable_irq() -/// Disable interrupt requests. -/// Returns the previous IRQ state: `False`/`True` for disabled/enabled IRQs -/// respectively. This return value can be passed to enable_irq to restore -/// the IRQ to its original state. -STATIC mp_obj_t pyb_disable_irq(void) { +// disable_irq() +// Disable interrupt requests. +// Returns the previous IRQ state which can be passed to enable_irq. +STATIC mp_obj_t machine_disable_irq(void) { return mp_obj_new_bool(disable_irq() == IRQ_STATE_ENABLED); } -MP_DEFINE_CONST_FUN_OBJ_0(pyb_disable_irq_obj, pyb_disable_irq); +MP_DEFINE_CONST_FUN_OBJ_0(machine_disable_irq_obj, machine_disable_irq); -/// \function enable_irq(state=True) -/// Enable interrupt requests. -/// If `state` is `True` (the default value) then IRQs are enabled. -/// If `state` is `False` then IRQs are disabled. The most common use of -/// this function is to pass it the value returned by `disable_irq` to -/// exit a critical section. -STATIC mp_obj_t pyb_enable_irq(uint n_args, const mp_obj_t *arg) { +// enable_irq(state=True) +// Enable interrupt requests, based on the argument, which is usually the +// value returned by a previous call to disable_irq. +STATIC mp_obj_t machine_enable_irq(uint n_args, const mp_obj_t *arg) { enable_irq((n_args == 0 || mp_obj_is_true(arg[0])) ? IRQ_STATE_ENABLED : IRQ_STATE_DISABLED); return mp_const_none; } -MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_enable_irq_obj, 0, 1, pyb_enable_irq); +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_enable_irq_obj, 0, 1, machine_enable_irq); #if IRQ_ENABLE_STATS // return a memoryview of the irq statistics array diff --git a/ports/stm32/irq.h b/ports/stm32/irq.h index 4b1251666c202..6c10f5e1b07f5 100644 --- a/ports/stm32/irq.h +++ b/ports/stm32/irq.h @@ -52,7 +52,7 @@ extern uint32_t irq_stats[IRQ_STATS_MAX]; #define IRQ_EXIT(irq) #endif -static inline mp_uint_t query_irq(void) { +static inline uint32_t query_irq(void) { return __get_PRIMASK(); } @@ -92,11 +92,6 @@ static inline void restore_irq_pri(uint32_t state) { #endif -MP_DECLARE_CONST_FUN_OBJ_0(pyb_wfi_obj); -MP_DECLARE_CONST_FUN_OBJ_0(pyb_disable_irq_obj); -MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_enable_irq_obj); -MP_DECLARE_CONST_FUN_OBJ_0(pyb_irq_stats_obj); - // IRQ priority definitions. // // Lower number implies higher interrupt priority. diff --git a/ports/stm32/led.c b/ports/stm32/led.c index 923171884629f..adcb240cc0cc6 100644 --- a/ports/stm32/led.c +++ b/ports/stm32/led.c @@ -141,9 +141,11 @@ STATIC void led_pwm_init(int led) { case 2: __TIM2_CLK_ENABLE(); break; + #if defined(TIM3) case 3: __TIM3_CLK_ENABLE(); break; + #endif default: assert(0); } diff --git a/ports/stm32/machine_adc.c b/ports/stm32/machine_adc.c index 5ba000a2338c8..f29896d37cf26 100644 --- a/ports/stm32/machine_adc.c +++ b/ports/stm32/machine_adc.c @@ -132,8 +132,10 @@ STATIC void adc_config(ADC_TypeDef *adc, uint32_t bits) { #elif defined(STM32H7) ADC12_COMMON->CCR = 3 << ADC_CCR_CKMODE_Pos; ADC3_COMMON->CCR = 3 << ADC_CCR_CKMODE_Pos; - #elif defined(STM32L0) || defined(STM32WB) + #elif defined(STM32L0) ADC1_COMMON->CCR = 0; // ADCPR=PCLK/2 + #elif defined(STM32WB) + ADC1_COMMON->CCR = 0 << ADC_CCR_PRESC_Pos | 0 << ADC_CCR_CKMODE_Pos; // PRESC=1, MODE=ASYNC #endif #if defined(STM32H7) || defined(STM32L4) || defined(STM32WB) diff --git a/ports/stm32/machine_i2c.c b/ports/stm32/machine_i2c.c index 14dd88b78f4dc..e0c408c6d07c2 100644 --- a/ports/stm32/machine_i2c.c +++ b/ports/stm32/machine_i2c.c @@ -32,11 +32,10 @@ #include "py/mperrno.h" #include "extmod/machine_i2c.h" #include "i2c.h" +#include "modmachine.h" #if MICROPY_HW_ENABLE_HW_I2C -STATIC const mp_obj_type_t machine_hard_i2c_type; - #define I2C_POLL_DEFAULT_TIMEOUT_US (50000) // 50ms #if defined(STM32F0) || defined(STM32F4) || defined(STM32F7) @@ -194,6 +193,8 @@ STATIC void machine_hard_i2c_init(machine_hard_i2c_obj_t *self, uint32_t freq, u #endif mp_obj_t machine_hard_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + MP_MACHINE_I2C_CHECK_FOR_LEGACY_SOFTI2C_CONSTRUCTION(n_args, n_kw, all_args); + // parse args enum { ARG_id, ARG_scl, ARG_sda, ARG_freq, ARG_timeout, ARG_timingr }; static const mp_arg_t allowed_args[] = { @@ -266,7 +267,7 @@ STATIC const mp_machine_i2c_p_t machine_hard_i2c_p = { .transfer = machine_hard_i2c_transfer, }; -STATIC const mp_obj_type_t machine_hard_i2c_type = { +const mp_obj_type_t machine_hard_i2c_type = { { &mp_type_type }, .name = MP_QSTR_I2C, .print = machine_hard_i2c_print, diff --git a/ports/stm32/machine_spi.c b/ports/stm32/machine_spi.c index cf6e96ab67bf6..37c026cefc33a 100644 --- a/ports/stm32/machine_spi.c +++ b/ports/stm32/machine_spi.c @@ -46,9 +46,11 @@ STATIC void machine_hard_spi_print(const mp_print_t *print, mp_obj_t self_in, mp } mp_obj_t machine_hard_spi_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + MP_MACHINE_SPI_CHECK_FOR_LEGACY_SOFTSPI_CONSTRUCTION(n_args, n_kw, all_args); + enum { ARG_id, ARG_baudrate, ARG_polarity, ARG_phase, ARG_bits, ARG_firstbit, ARG_sck, ARG_mosi, ARG_miso }; static const mp_arg_t allowed_args[] = { - { MP_QSTR_id, MP_ARG_OBJ, {.u_obj = MP_OBJ_NEW_SMALL_INT(-1)} }, + { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_OBJ }, { MP_QSTR_baudrate, MP_ARG_INT, {.u_int = 500000} }, { MP_QSTR_polarity, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, { MP_QSTR_phase, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, @@ -137,7 +139,7 @@ const mp_obj_type_t machine_hard_spi_type = { { &mp_type_type }, .name = MP_QSTR_SPI, .print = machine_hard_spi_print, - .make_new = mp_machine_spi_make_new, // delegate to master constructor + .make_new = machine_hard_spi_make_new, .protocol = &machine_hard_spi_p, .locals_dict = (mp_obj_dict_t *)&mp_machine_spi_locals_dict, }; diff --git a/ports/stm32/machine_uart.c b/ports/stm32/machine_uart.c index 232f3629ee6b9..2862f4c0e7b15 100644 --- a/ports/stm32/machine_uart.c +++ b/ports/stm32/machine_uart.c @@ -73,77 +73,6 @@ /// /// uart.any() # returns True if any characters waiting -typedef struct _pyb_uart_irq_map_t { - uint16_t irq_en; - uint16_t flag; -} pyb_uart_irq_map_t; - -STATIC const pyb_uart_irq_map_t mp_irq_map[] = { - { USART_CR1_IDLEIE, UART_FLAG_IDLE}, // RX idle - { USART_CR1_PEIE, UART_FLAG_PE}, // parity error - { USART_CR1_TXEIE, UART_FLAG_TXE}, // TX register empty - { USART_CR1_TCIE, UART_FLAG_TC}, // TX complete - { USART_CR1_RXNEIE, UART_FLAG_RXNE}, // RX register not empty - #if 0 - // For now only IRQs selected by CR1 are supported - #if defined(STM32F4) - { USART_CR2_LBDIE, UART_FLAG_LBD}, // LIN break detection - #else - { USART_CR2_LBDIE, UART_FLAG_LBDF}, // LIN break detection - #endif - { USART_CR3_CTSIE, UART_FLAG_CTS}, // CTS - #endif -}; - -// OR-ed IRQ flags which should not be touched by the user -STATIC const uint32_t mp_irq_reserved = UART_FLAG_RXNE; - -// OR-ed IRQ flags which are allowed to be used by the user -STATIC const uint32_t mp_irq_allowed = UART_FLAG_IDLE; - -STATIC mp_obj_t pyb_uart_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); - -STATIC void pyb_uart_irq_config(pyb_uart_obj_t *self, bool enable) { - if (self->mp_irq_trigger) { - for (size_t entry = 0; entry < MP_ARRAY_SIZE(mp_irq_map); ++entry) { - if (mp_irq_map[entry].flag & mp_irq_reserved) { - continue; - } - if (mp_irq_map[entry].flag & self->mp_irq_trigger) { - if (enable) { - self->uartx->CR1 |= mp_irq_map[entry].irq_en; - } else { - self->uartx->CR1 &= ~mp_irq_map[entry].irq_en; - } - } - } - } -} - -STATIC mp_uint_t pyb_uart_irq_trigger(mp_obj_t self_in, mp_uint_t new_trigger) { - pyb_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); - pyb_uart_irq_config(self, false); - self->mp_irq_trigger = new_trigger; - pyb_uart_irq_config(self, true); - return 0; -} - -STATIC mp_uint_t pyb_uart_irq_info(mp_obj_t self_in, mp_uint_t info_type) { - pyb_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); - if (info_type == MP_IRQ_INFO_FLAGS) { - return self->mp_irq_flags; - } else if (info_type == MP_IRQ_INFO_TRIGGERS) { - return self->mp_irq_trigger; - } - return 0; -} - -STATIC const mp_irq_methods_t pyb_uart_irq_methods = { - .init = pyb_uart_irq, - .trigger = pyb_uart_irq_trigger, - .info = pyb_uart_irq_info, -}; - STATIC void pyb_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { pyb_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); if (!self->is_enabled) { @@ -237,11 +166,6 @@ STATIC mp_obj_t pyb_uart_init_helper(pyb_uart_obj_t *self, size_t n_args, const mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, (mp_arg_val_t *)&args); - // static UARTs are used for internal purposes and shouldn't be reconfigured - if (self->is_static) { - mp_raise_ValueError(MP_ERROR_TEXT("UART is static and can't be init'd")); - } - // baudrate uint32_t baudrate = args.baudrate.u_int; @@ -283,11 +207,17 @@ STATIC mp_obj_t pyb_uart_init_helper(pyb_uart_obj_t *self, size_t n_args, const // flow control uint32_t flow = args.flow.u_int; + // Save attach_to_repl setting because uart_init will disable it. + bool attach_to_repl = self->attached_to_repl; + // init UART (if it fails, it's because the port doesn't exist) if (!uart_init(self, baudrate, bits, parity, stop, flow)) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("UART(%d) doesn't exist"), self->uart_id); } + // Restore attach_to_repl setting so UART still works if attached to dupterm. + uart_attach_to_repl(self, attach_to_repl); + // set timeout self->timeout = args.timeout.u_int; @@ -300,20 +230,28 @@ STATIC mp_obj_t pyb_uart_init_helper(pyb_uart_obj_t *self, size_t n_args, const self->timeout_char = min_timeout_char; } - // setup the read buffer - m_del(byte, self->read_buf, self->read_buf_len << self->char_width); - if (args.rxbuf.u_int >= 0) { - // rxbuf overrides legacy read_buf_len - args.read_buf_len.u_int = args.rxbuf.u_int; - } - if (args.read_buf_len.u_int <= 0) { - // no read buffer - uart_set_rxbuf(self, 0, NULL); + if (self->is_static) { + // Static UARTs have fixed memory for the rxbuf and can't be reconfigured. + if (args.rxbuf.u_int >= 0) { + mp_raise_ValueError(MP_ERROR_TEXT("UART is static and rxbuf can't be changed")); + } + uart_set_rxbuf(self, self->read_buf_len, self->read_buf); } else { - // read buffer using interrupts - size_t len = args.read_buf_len.u_int + 1; // +1 to adjust for usable length of buffer - uint8_t *buf = m_new(byte, len << self->char_width); - uart_set_rxbuf(self, len, buf); + // setup the read buffer + m_del(byte, self->read_buf, self->read_buf_len << self->char_width); + if (args.rxbuf.u_int >= 0) { + // rxbuf overrides legacy read_buf_len + args.read_buf_len.u_int = args.rxbuf.u_int; + } + if (args.read_buf_len.u_int <= 0) { + // no read buffer + uart_set_rxbuf(self, 0, NULL); + } else { + // read buffer using interrupts + size_t len = args.read_buf_len.u_int + 1; // +1 to adjust for usable length of buffer + uint8_t *buf = m_new(byte, len << self->char_width); + uart_set_rxbuf(self, len, buf); + } } // compute actual baudrate that was configured @@ -510,7 +448,7 @@ STATIC mp_obj_t pyb_uart_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t * if (self->mp_irq_obj == NULL) { self->mp_irq_trigger = 0; - self->mp_irq_obj = mp_irq_new(&pyb_uart_irq_methods, MP_OBJ_FROM_PTR(self)); + self->mp_irq_obj = mp_irq_new(&uart_irq_methods, MP_OBJ_FROM_PTR(self)); } if (n_args > 1 || kw_args->used != 0) { @@ -522,17 +460,17 @@ STATIC mp_obj_t pyb_uart_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t * // Check the trigger mp_uint_t trigger = args[MP_IRQ_ARG_INIT_trigger].u_int; - mp_uint_t not_supported = trigger & ~mp_irq_allowed; + mp_uint_t not_supported = trigger & ~MP_UART_ALLOWED_FLAGS; if (trigger != 0 && not_supported) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("trigger 0x%08x unsupported"), not_supported); } // Reconfigure user IRQs - pyb_uart_irq_config(self, false); + uart_irq_config(self, false); self->mp_irq_obj->handler = handler; self->mp_irq_obj->ishard = args[MP_IRQ_ARG_INIT_hard].u_bool; self->mp_irq_trigger = trigger; - pyb_uart_irq_config(self, true); + uart_irq_config(self, true); } return MP_OBJ_FROM_PTR(self->mp_irq_obj); diff --git a/ports/stm32/make-stmconst.py b/ports/stm32/make-stmconst.py index 1b3cc08d951f3..ac5c56f5c79f5 100644 --- a/ports/stm32/make-stmconst.py +++ b/ports/stm32/make-stmconst.py @@ -46,7 +46,7 @@ def __init__(self, line): class Lexer: re_io_reg = r"__IO uint(?P8|16|32)_t +(?P[A-Z0-9]+)" - re_comment = r"(?P[A-Za-z0-9 \-/_()&]+)" + re_comment = r"(?P[A-Za-z0-9 \-/_()&:]+)" re_addr_offset = r"Address offset: (?P0x[0-9A-Z]{2,3})" regexs = ( ( @@ -78,16 +78,16 @@ class Lexer: ( "IO reg", re.compile( - re_io_reg + r"; */\*!< *" + re_comment + r", +" + re_addr_offset + r" *\*/" + re_io_reg + r" *; */\*!< *" + re_comment + r",? +" + re_addr_offset + r" *\*/" ), ), ( "IO reg array", re.compile( re_io_reg - + r"\[(?P[2-8])\]; */\*!< *" + + r"\[(?P[2-8])\] *; */\*!< *" + re_comment - + r", +" + + r",? +" + re_addr_offset + r"-(0x[0-9A-Z]{2,3}) *\*/" ), @@ -160,7 +160,11 @@ def parse_file(filename): if m[0] == "}": pass elif m[0] == "} TypeDef": - reg_defs[m[1].groupdict()["id"]] = regs + d = m[1].groupdict() + n = d["id"] + g = d["global"] + if n not in reg_defs or not g: + reg_defs[n] = regs else: raise LexerError(lexer.line_number) @@ -298,6 +302,7 @@ def main(): "USART", "WWDG", "RNG", + "IPCC", ): if reg in reg_defs: print_regs(reg, reg_defs[reg], needed_qstrs, needed_mpzs) diff --git a/ports/stm32/mbedtls/mbedtls_config.h b/ports/stm32/mbedtls/mbedtls_config.h index 338c8b354146b..56fbbf3aaf086 100644 --- a/ports/stm32/mbedtls/mbedtls_config.h +++ b/ports/stm32/mbedtls/mbedtls_config.h @@ -67,6 +67,7 @@ #define MBEDTLS_CTR_DRBG_C //#define MBEDTLS_ECP_C #define MBEDTLS_ENTROPY_C +#define MBEDTLS_ERROR_C #define MBEDTLS_MD_C #define MBEDTLS_MD5_C #define MBEDTLS_OID_C diff --git a/ports/stm32/mboot/Makefile b/ports/stm32/mboot/Makefile index 43aae2a6785f3..c901dfb3344a4 100755 --- a/ports/stm32/mboot/Makefile +++ b/ports/stm32/mboot/Makefile @@ -34,6 +34,7 @@ STFLASH ?= st-flash OPENOCD ?= openocd OPENOCD_CONFIG ?= boards/openocd_stm32f4.cfg STARTUP_FILE ?= lib/stm32lib/CMSIS/STM32$(MCU_SERIES_UPPER)xx/Source/Templates/gcc/startup_$(CMSIS_MCU_LOWER).o +SYSTEM_FILE ?= lib/stm32lib/CMSIS/STM32$(MCU_SERIES_UPPER)xx/Source/Templates/system_stm32$(MCU_SERIES)xx.o CROSS_COMPILE ?= arm-none-eabi- @@ -54,6 +55,11 @@ CFLAGS_MCU_f4 = $(CFLAGS_CORTEX_M) -mtune=cortex-m4 -mcpu=cortex-m4 CFLAGS_MCU_f7 = $(CFLAGS_CORTEX_M) -mtune=cortex-m7 -mcpu=cortex-m7 CFLAGS_MCU_h7 = $(CFLAGS_CORTEX_M) -mtune=cortex-m7 -mcpu=cortex-m7 CFLAGS_MCU_l4 = $(CFLAGS_CORTEX_M) -mtune=cortex-m4 -mcpu=cortex-m4 +CFLAGS_MCU_wb = $(CFLAGS_CORTEX_M) -mtune=cortex-m4 -mcpu=cortex-m4 + +# Standard C functions like memset need to be compiled with special flags so +# the compiler does not optimise these functions in terms of themselves. +CFLAGS_BUILTIN ?= -ffreestanding -fno-builtin -fno-lto CFLAGS = $(INC) -Wall -Wpointer-arith -Wdouble-promotion -Wfloat-conversion -Werror -std=gnu99 -nostdlib $(CFLAGS_MOD) $(CFLAGS_EXTRA) CFLAGS += -D$(CMSIS_MCU) @@ -64,6 +70,8 @@ CFLAGS += -DSTM32_HAL_H='' CFLAGS += -DBOARD_$(BOARD) CFLAGS += -DAPPLICATION_ADDR=$(TEXT0_ADDR) CFLAGS += -DFFCONF_H=\"ports/stm32/mboot/ffconf.h\" +CFLAGS += -DLFS1_NO_MALLOC -DLFS1_NO_DEBUG -DLFS1_NO_WARN -DLFS1_NO_ERROR -DLFS1_NO_ASSERT +CFLAGS += -DLFS2_NO_MALLOC -DLFS2_NO_DEBUG -DLFS2_NO_WARN -DLFS2_NO_ERROR -DLFS2_NO_ASSERT CFLAGS += -DBUILDING_MBOOT=1 CFLAGS += -DBOOTLOADER_DFU_USB_VID=$(BOOTLOADER_DFU_USB_VID) -DBOOTLOADER_DFU_USB_PID=$(BOOTLOADER_DFU_USB_PID) @@ -82,8 +90,13 @@ else COPT += -Os -DNDEBUG endif +$(BUILD)/lib/libc/string0.o: CFLAGS += $(CFLAGS_BUILTIN) LIB_SRC_C = \ lib/libc/string0.c \ + lib/littlefs/lfs1.c \ + lib/littlefs/lfs1_util.c \ + lib/littlefs/lfs2.c \ + lib/littlefs/lfs2_util.c \ lib/oofatfs/ff.c \ lib/oofatfs/ffunicode.c \ extmod/uzlib/crc32.c \ @@ -95,19 +108,24 @@ SRC_C = \ main.c \ elem.c \ fsload.c \ - diskio.c \ + gzstream.c \ + vfs_fat.c \ + vfs_lfs.c \ drivers/bus/softspi.c \ drivers/bus/softqspi.c \ drivers/memory/spiflash.c \ + ports/stm32/flash.c \ + ports/stm32/flashbdev.c \ ports/stm32/i2cslave.c \ + ports/stm32/powerctrlboot.c \ ports/stm32/qspi.c \ - ports/stm32/flashbdev.c \ ports/stm32/spibdev.c \ ports/stm32/usbd_conf.c \ $(wildcard $(BOARD_DIR)/*.c) SRC_O = \ $(STARTUP_FILE) \ + $(SYSTEM_FILE) \ ports/stm32/resethandler.o \ $(BUILD)/$(HAL_DIR)/Src/stm32$(MCU_SERIES)xx_ll_usb.o: CFLAGS += -Wno-attributes diff --git a/ports/stm32/mboot/README.md b/ports/stm32/mboot/README.md index 52bbf55675414..d8aa6d456f19e 100644 --- a/ports/stm32/mboot/README.md +++ b/ports/stm32/mboot/README.md @@ -2,11 +2,11 @@ Mboot - MicroPython boot loader =============================== Mboot is a custom bootloader for STM32 MCUs, and currently supports the -STM32F4xx and STM32F7xx families. It can provide a standard USB DFU interface -on either the FS or HS peripherals, as well as a sophisticated, custom I2C +STM32F4xx, STM32F7xx and STM32WBxx families. It can provide a standard USB DFU +interface on either the FS or HS peripherals, as well as a sophisticated, custom I2C interface. It can also load and program firmware in .dfu.gz format from a -filesystem. It can fit in 16k of flash space, but all features enabled requires -32k. +filesystem, either FAT, littlefs 1 or littlfs 2. +It can fit in 16k of flash space, but all features enabled requires 32k. How to use ---------- @@ -63,6 +63,15 @@ How to use #define MBOOT_FSLOAD (1) + and then enable one or more of the following depending on what filesystem + support is required in Mboot (note that the FAT driver is read-only and + quite compact, but littlefs supports both read and write so is rather + large): + + #define MBOOT_VFS_FAT (1) + #define MBOOT_VFS_LFS1 (1) + #define MBOOT_VFS_LFS2 (1) + 2. Build the board's main application firmware as usual. 3. Build mboot via: @@ -133,10 +142,18 @@ are located and what filename to program. The elements to use are: `u32` means unsigned 32-bit little-endian integer. The firmware to load must be a gzip'd DfuSe file (.dfu.gz) and stored within a -FAT formatted partition. +FAT or littlefs formatted partition. The provided fwupdate.py script contains helper functions to call into Mboot -with the correct data, and also to update Mboot itself. +with the correct data, and also to update Mboot itself. For example on PYBD +the following will update the main MicroPython firmware from the file +firmware.dfu.gz stored on the default FAT filesystem: + + import fwupdate + fwupdate.update_mpy('firmware.dfu.gz', 0x80000000, 2 * 1024 * 1024) + +The 0x80000000 value is the address understood by Mboot as the location of +the external SPI flash, configured via `MBOOT_SPIFLASH_ADDR`. Example: Mboot on PYBv1.x ------------------------- diff --git a/ports/stm32/mboot/dfu.h b/ports/stm32/mboot/dfu.h index e826e217f032f..a1d4d10d0ef90 100644 --- a/ports/stm32/mboot/dfu.h +++ b/ports/stm32/mboot/dfu.h @@ -67,6 +67,12 @@ typedef enum { DFU_CMD_DNLOAD = 8, } dfu_cmd_t; +enum { + DFU_CMD_DNLOAD_SET_ADDRESS = 0x21, + DFU_CMD_DNLOAD_ERASE = 0x41, + DFU_CMD_DNLOAD_READ_UNPROTECT = 0x92, +}; + // Error status flags typedef enum { DFU_STATUS_OK = 0x00, // No error condition is present. diff --git a/ports/stm32/mboot/fsload.c b/ports/stm32/mboot/fsload.c index 64eb2a3a5ba72..1e1ad7a04d015 100644 --- a/ports/stm32/mboot/fsload.c +++ b/ports/stm32/mboot/fsload.c @@ -3,7 +3,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2019 Damien P. George + * Copyright (c) 2019-2020 Damien P. George * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -27,81 +27,22 @@ #include #include "py/mphal.h" -#include "lib/oofatfs/ff.h" -#include "extmod/uzlib/uzlib.h" #include "mboot.h" +#include "vfs.h" #if MBOOT_FSLOAD -#define DICT_SIZE (1 << 15) - -typedef struct _gz_stream_t { - FIL fp; - TINF_DATA tinf; - uint8_t buf[512]; - uint8_t dict[DICT_SIZE]; -} gz_stream_t; - -static gz_stream_t gz_stream SECTION_NOZERO_BSS; - -static int gz_stream_read_src(TINF_DATA *tinf) { - UINT n; - FRESULT res = f_read(&gz_stream.fp, gz_stream.buf, sizeof(gz_stream.buf), &n); - if (res != FR_OK) { - return -1; - } - if (n == 0) { - return -1; - } - tinf->source = gz_stream.buf + 1; - tinf->source_limit = gz_stream.buf + n; - return gz_stream.buf[0]; -} - -static int gz_stream_open(FATFS *fatfs, const char *filename) { - FRESULT res = f_open(fatfs, &gz_stream.fp, filename, FA_READ); - if (res != FR_OK) { - return -1; - } - memset(&gz_stream.tinf, 0, sizeof(gz_stream.tinf)); - gz_stream.tinf.readSource = gz_stream_read_src; - - int st = uzlib_gzip_parse_header(&gz_stream.tinf); - if (st != TINF_OK) { - f_close(&gz_stream.fp); - return -1; - } - - uzlib_uncompress_init(&gz_stream.tinf, gz_stream.dict, DICT_SIZE); - - return 0; -} - -static int gz_stream_read(size_t len, uint8_t *buf) { - gz_stream.tinf.dest = buf; - gz_stream.tinf.dest_limit = buf + len; - int st = uzlib_uncompress_chksum(&gz_stream.tinf); - if (st == TINF_DONE) { - return 0; - } - if (st < 0) { - return st; - } - return gz_stream.tinf.dest - buf; -} - -static int fsload_program_file(FATFS *fatfs, const char *filename, bool write_to_flash) { - int res = gz_stream_open(fatfs, filename); - if (res != 0) { - return res; - } +#if !(MBOOT_VFS_FAT || MBOOT_VFS_LFS1 || MBOOT_VFS_LFS2) +#error Must enable at least one VFS component +#endif +static int fsload_program_file(bool write_to_flash) { // Parse DFU uint8_t buf[512]; size_t file_offset; // Read file header, <5sBIB - res = gz_stream_read(11, buf); + int res = gz_stream_read(11, buf); if (res != 11) { return -1; } @@ -204,46 +145,23 @@ static int fsload_program_file(FATFS *fatfs, const char *filename, bool write_to return 0; } -static int fsload_process_fatfs(uint32_t base_addr, uint32_t byte_len, size_t fname_len, const char *fname) { - fsload_bdev_t bdev = {base_addr, byte_len}; - FATFS fatfs; - fatfs.drv = &bdev; - FRESULT res = f_mount(&fatfs); - if (res != FR_OK) { - return -1; - } - - FF_DIR dp; - res = f_opendir(&fatfs, &dp, "/"); - if (res != FR_OK) { - return -1; - } - - // Search for firmware file with correct name - int r; - for (;;) { - FILINFO fno; - res = f_readdir(&dp, &fno); - char *fn = fno.fname; - if (res != FR_OK || fn[0] == 0) { - // Finished listing dir, no firmware found - r = -1; - break; - } - if (memcmp(fn, fname, fname_len) == 0 && fn[fname_len] == '\0') { - // Found firmware - led_state_all(2); - r = fsload_program_file(&fatfs, fn, false); - if (r == 0) { - // Firmware is valid, program it - led_state_all(4); - r = fsload_program_file(&fatfs, fn, true); +static int fsload_validate_and_program_file(void *stream, const stream_methods_t *meth, const char *fname) { + // First pass verifies the file, second pass programs it + for (unsigned int pass = 0; pass <= 1; ++pass) { + led_state_all(pass == 0 ? 2 : 4); + int res = meth->open(stream, fname); + if (res == 0) { + res = gz_stream_init(stream, meth->read); + if (res == 0) { + res = fsload_program_file(pass == 0 ? false : true); } - break; + } + meth->close(stream); + if (res != 0) { + return res; } } - - return r; + return 0; } int fsload_process(void) { @@ -252,9 +170,12 @@ int fsload_process(void) { return -1; } + // Get mount point id and create null-terminated filename uint8_t mount_point = elem[0]; uint8_t fname_len = elem[-1] - 1; - const char *fname = (const char*)&elem[1]; + char fname[256]; + memcpy(fname, &elem[1], fname_len); + fname[fname_len] = '\0'; elem = ELEM_DATA_START; for (;;) { @@ -266,23 +187,58 @@ int fsload_process(void) { if (elem[0] == mount_point) { uint32_t base_addr = get_le32(&elem[2]); uint32_t byte_len = get_le32(&elem[6]); + int ret; + union { + #if MBOOT_VFS_FAT + vfs_fat_context_t fat; + #endif + #if MBOOT_VFS_LFS1 + vfs_lfs1_context_t lfs1; + #endif + #if MBOOT_VFS_LFS2 + vfs_lfs2_context_t lfs2; + #endif + } ctx; + const stream_methods_t *methods; + #if MBOOT_VFS_FAT if (elem[1] == ELEM_MOUNT_FAT) { - int ret = fsload_process_fatfs(base_addr, byte_len, fname_len, fname); - // Flash LEDs based on success/failure of update - for (int i = 0; i < 4; ++i) { - if (ret == 0) { - led_state_all(7); - } else { - led_state_all(1); - } - mp_hal_delay_ms(100); - led_state_all(0); - mp_hal_delay_ms(100); + ret = vfs_fat_mount(&ctx.fat, base_addr, byte_len); + methods = &vfs_fat_stream_methods; + } else + #endif + #if MBOOT_VFS_LFS1 + if (elem[1] == ELEM_MOUNT_LFS1) { + ret = vfs_lfs1_mount(&ctx.lfs1, base_addr, byte_len); + methods = &vfs_lfs1_stream_methods; + } else + #endif + #if MBOOT_VFS_LFS2 + if (elem[1] == ELEM_MOUNT_LFS2) { + ret = vfs_lfs2_mount(&ctx.lfs2, base_addr, byte_len); + methods = &vfs_lfs2_stream_methods; + } else + #endif + { + // Unknown filesystem type + return -1; + } + + if (ret == 0) { + ret = fsload_validate_and_program_file(&ctx, methods, fname); + } + + // Flash LEDs based on success/failure of update + for (int i = 0; i < 4; ++i) { + if (ret == 0) { + led_state_all(7); + } else { + led_state_all(1); } - return ret; + mp_hal_delay_ms(100); + led_state_all(0); + mp_hal_delay_ms(100); } - // Unknown filesystem type - return -1; + return ret; } elem += elem[-1]; } diff --git a/ports/stm32/mboot/fwupdate.py b/ports/stm32/mboot/fwupdate.py index b44ed772c3047..dab5fa6632e99 100644 --- a/ports/stm32/mboot/fwupdate.py +++ b/ports/stm32/mboot/fwupdate.py @@ -1,9 +1,13 @@ # Update Mboot or MicroPython from a .dfu.gz file on the board's filesystem -# MIT license; Copyright (c) 2019 Damien P. George +# MIT license; Copyright (c) 2019-2020 Damien P. George import struct, time import uzlib, machine, stm +# Constants to be used with update_mpy +VFS_FAT = 1 +VFS_LFS1 = 2 +VFS_LFS2 = 3 FLASH_KEY1 = 0x45670123 FLASH_KEY2 = 0xCDEF89AB @@ -152,7 +156,7 @@ def update_mboot(filename): print("Programming finished, can now reset or turn off.") -def update_mpy(filename, fs_base, fs_len): +def update_mpy(filename, fs_base, fs_len, fs_type=VFS_FAT): # Check firmware is of .dfu.gz type try: with open(filename, "rb") as f: @@ -166,11 +170,8 @@ def update_mpy(filename, fs_base, fs_len): ELEM_TYPE_END = 1 ELEM_TYPE_MOUNT = 2 ELEM_TYPE_FSLOAD = 3 - ELEM_MOUNT_FAT = 1 mount_point = 1 - mount = struct.pack( - " + +#include "py/mphal.h" +#include "extmod/uzlib/uzlib.h" +#include "gzstream.h" +#include "mboot.h" + +#if MBOOT_FSLOAD + +#define DICT_SIZE (1 << 15) + +typedef struct _gz_stream_t { + void *stream_data; + stream_read_t stream_read; + TINF_DATA tinf; + uint8_t buf[512]; + uint8_t dict[DICT_SIZE]; +} gz_stream_t; + +static gz_stream_t gz_stream SECTION_NOZERO_BSS; + +static int gz_stream_read_src(TINF_DATA *tinf) { + int n = gz_stream.stream_read(gz_stream.stream_data, gz_stream.buf, sizeof(gz_stream.buf)); + if (n < 0) { + // Stream error + return -1; + } + if (n == 0) { + // No data / EOF + return -1; + } + + tinf->source = gz_stream.buf + 1; + tinf->source_limit = gz_stream.buf + n; + return gz_stream.buf[0]; +} + +int gz_stream_init(void *stream_data, stream_read_t stream_read) { + gz_stream.stream_data = stream_data; + gz_stream.stream_read = stream_read; + + memset(&gz_stream.tinf, 0, sizeof(gz_stream.tinf)); + gz_stream.tinf.readSource = gz_stream_read_src; + + int st = uzlib_gzip_parse_header(&gz_stream.tinf); + if (st != TINF_OK) { + return -1; + } + + uzlib_uncompress_init(&gz_stream.tinf, gz_stream.dict, DICT_SIZE); + + return 0; +} + +int gz_stream_read(size_t len, uint8_t *buf) { + gz_stream.tinf.dest = buf; + gz_stream.tinf.dest_limit = buf + len; + int st = uzlib_uncompress_chksum(&gz_stream.tinf); + if (st == TINF_DONE) { + return 0; + } + if (st < 0) { + return st; + } + return gz_stream.tinf.dest - buf; +} + +#endif // MBOOT_FSLOAD diff --git a/ports/stm32/mboot/gzstream.h b/ports/stm32/mboot/gzstream.h new file mode 100644 index 0000000000000..ec11ba79bbb02 --- /dev/null +++ b/ports/stm32/mboot/gzstream.h @@ -0,0 +1,45 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2020 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef MICROPY_INCLUDED_STM32_MBOOT_GZSTREAM_H +#define MICROPY_INCLUDED_STM32_MBOOT_GZSTREAM_H + +#include +#include + +typedef int (*stream_open_t)(void *stream, const char *fname); +typedef void (*stream_close_t)(void *stream); +typedef int (*stream_read_t)(void *stream, uint8_t *buf, size_t len); + +typedef struct _stream_methods_t { + stream_open_t open; + stream_close_t close; + stream_read_t read; +} stream_methods_t; + +int gz_stream_init(void *stream_data, stream_read_t stream_read); +int gz_stream_read(size_t len, uint8_t *buf); + +#endif // MICROPY_INCLUDED_STM32_MBOOT_GZSTREAM_H diff --git a/ports/stm32/mboot/main.c b/ports/stm32/mboot/main.c index 4ae575f3ddf41..99187e3ef8406 100644 --- a/ports/stm32/mboot/main.c +++ b/ports/stm32/mboot/main.c @@ -31,30 +31,36 @@ #include "extmod/crypto-algorithms/sha256.c" #include "usbd_core.h" #include "storage.h" +#include "flash.h" #include "i2cslave.h" +#include "irq.h" #include "mboot.h" +#include "powerctrl.h" #include "dfu.h" -// Using polling is about 10% faster than not using it (and using IRQ instead) -// This DFU code with polling runs in about 70% of the time of the ST bootloader -#define USE_USB_POLLING (1) +// This option selects whether to use explicit polling or IRQs for USB events. +// In some test cases polling mode can run slightly faster, but it uses more power. +// Polling mode will also cause failures with the mass-erase command because USB +// events will not be serviced for the duration of the mass erase. +// With STM32WB MCUs only non-polling/IRQ mode is supported. +#define USE_USB_POLLING (0) // Using cache probably won't make it faster because we run at a low frequency, and best // to keep the MCU config as minimal as possible. #define USE_CACHE (0) // IRQ priorities (encoded values suitable for NVIC_SetPriority) -#define IRQ_PRI_SYSTICK (NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 0, 0)) +// Most values are defined in irq.h. #define IRQ_PRI_I2C (NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 1, 0)) // Configure PLL to give the desired CPU freq #undef MICROPY_HW_FLASH_LATENCY -#if defined(STM32H7) -#define CORE_PLL_FREQ (96000000) -#define MICROPY_HW_FLASH_LATENCY FLASH_LATENCY_2 -#else +#if defined(STM32F4) || defined(STM32F7) #define CORE_PLL_FREQ (48000000) #define MICROPY_HW_FLASH_LATENCY FLASH_LATENCY_1 +#elif defined(STM32H7) +#define CORE_PLL_FREQ (96000000) +#define MICROPY_HW_FLASH_LATENCY FLASH_LATENCY_2 #endif #undef MICROPY_HW_CLK_PLLM #undef MICROPY_HW_CLK_PLLN @@ -92,7 +98,11 @@ uint32_t get_le32(const uint8_t *b) { void mp_hal_delay_us(mp_uint_t usec) { // use a busy loop for the delay // sys freq is always a multiple of 2MHz, so division here won't lose precision + #if defined(CORE_PLL_FREQ) const uint32_t ucount = CORE_PLL_FREQ / 2000000 * usec / 2; + #else + const uint32_t ucount = SystemCoreClock / 2000000 * usec / 2; + #endif for (uint32_t count = 0; ++count <= ucount;) { } } @@ -139,72 +149,6 @@ static void __fatal_error(const char *msg) { /******************************************************************************/ // CLOCK -#if defined(STM32F4) || defined(STM32F7) - -#define CONFIG_RCC_CR_1ST (RCC_CR_HSION) -#define CONFIG_RCC_CR_2ND (RCC_CR_HSEON || RCC_CR_CSSON || RCC_CR_PLLON) -#define CONFIG_RCC_PLLCFGR (0x24003010) - -#elif defined(STM32H7) - -#define CONFIG_RCC_CR_1ST (RCC_CR_HSION) -#define CONFIG_RCC_CR_2ND (RCC_CR_PLL3ON | RCC_CR_PLL2ON | RCC_CR_PLL1ON | RCC_CR_CSSHSEON \ - | RCC_CR_HSEON | RCC_CR_HSI48ON | RCC_CR_CSIKERON | RCC_CR_CSION) -#define CONFIG_RCC_PLLCFGR (0x00000000) - -#else -#error Unknown processor -#endif - -void SystemInit(void) { - #if defined(STM32H7) - // Configure write-once power options, and wait for voltage levels to be ready - PWR->CR3 = PWR_CR3_LDOEN; - while (!(PWR->CSR1 & PWR_CSR1_ACTVOSRDY)) { - } - #endif - - // Set HSION bit - RCC->CR |= CONFIG_RCC_CR_1ST; - - // Reset CFGR register - RCC->CFGR = 0x00000000; - - // Reset HSEON, CSSON and PLLON bits - RCC->CR &= ~CONFIG_RCC_CR_2ND; - - // Reset PLLCFGR register - RCC->PLLCFGR = CONFIG_RCC_PLLCFGR; - - #if defined(STM32H7) - // Reset PLL and clock configuration registers - RCC->D1CFGR = 0x00000000; - RCC->D2CFGR = 0x00000000; - RCC->D3CFGR = 0x00000000; - RCC->PLLCKSELR = 0x00000000; - RCC->D1CCIPR = 0x00000000; - RCC->D2CCIP1R = 0x00000000; - RCC->D2CCIP2R = 0x00000000; - RCC->D3CCIPR = 0x00000000; - #endif - - // Reset HSEBYP bit - RCC->CR &= (uint32_t)0xFFFBFFFF; - - // Disable all interrupts - #if defined(STM32F4) || defined(STM32F7) - RCC->CIR = 0x00000000; - #elif defined(STM32H7) - RCC->CIER = 0x00000000; - #endif - - // Set location of vector table - SCB->VTOR = FLASH_BASE; - - // Enable 8-byte stack alignment for IRQ handlers, in accord with EABI - SCB->CCR |= SCB_CCR_STKALIGN_Msk; -} - void systick_init(void) { // Configure SysTick as 1ms ticker SysTick_Config(SystemCoreClock / 1000); @@ -378,6 +322,9 @@ uint32_t HAL_RCC_GetHCLKFreq(void) { #elif defined(STM32H7) #define AHBxENR AHB4ENR #define AHBxENR_GPIOAEN_Pos RCC_AHB4ENR_GPIOAEN_Pos +#elif defined(STM32WB) +#define AHBxENR AHB2ENR +#define AHBxENR_GPIOAEN_Pos RCC_AHB2ENR_GPIOAEN_Pos #endif void mp_hal_pin_config(mp_hal_pin_obj_t port_pin, uint32_t mode, uint32_t pull, uint32_t alt) { @@ -492,6 +439,11 @@ static int usrbtn_state(void) { /******************************************************************************/ // FLASH +#if defined(STM32WB) +#define FLASH_END FLASH_END_ADDR +#endif +#define APPLICATION_FLASH_LENGTH (FLASH_END + 1 - APPLICATION_ADDR) + #ifndef MBOOT_SPIFLASH_LAYOUT #define MBOOT_SPIFLASH_LAYOUT "" #endif @@ -500,153 +452,49 @@ static int usrbtn_state(void) { #define MBOOT_SPIFLASH2_LAYOUT "" #endif -typedef struct { - uint32_t base_address; - uint32_t sector_size; - uint32_t sector_count; -} flash_layout_t; - -#if defined(STM32F7) -// FLASH_FLAG_PGSERR (Programming Sequence Error) was renamed to -// FLASH_FLAG_ERSERR (Erasing Sequence Error) in STM32F7 -#define FLASH_FLAG_PGSERR FLASH_FLAG_ERSERR -#endif - #if defined(STM32F4) \ || defined(STM32F722xx) \ || defined(STM32F723xx) \ || defined(STM32F732xx) \ || defined(STM32F733xx) - #define FLASH_LAYOUT_STR "@Internal Flash /0x08000000/04*016Kg,01*064Kg,07*128Kg" MBOOT_SPIFLASH_LAYOUT MBOOT_SPIFLASH2_LAYOUT - -static const flash_layout_t flash_layout[] = { - { 0x08000000, 0x04000, 4 }, - { 0x08010000, 0x10000, 1 }, - { 0x08020000, 0x20000, 3 }, - #if defined(FLASH_SECTOR_8) - { 0x08080000, 0x20000, 4 }, - #endif - #if defined(FLASH_SECTOR_12) - { 0x08100000, 0x04000, 4 }, - { 0x08110000, 0x10000, 1 }, - { 0x08120000, 0x20000, 7 }, - #endif -}; - #elif defined(STM32F765xx) || defined(STM32F767xx) || defined(STM32F769xx) - #define FLASH_LAYOUT_STR "@Internal Flash /0x08000000/04*032Kg,01*128Kg,07*256Kg" MBOOT_SPIFLASH_LAYOUT MBOOT_SPIFLASH2_LAYOUT - -// This is for dual-bank mode disabled -static const flash_layout_t flash_layout[] = { - { 0x08000000, 0x08000, 4 }, - { 0x08020000, 0x20000, 1 }, - { 0x08040000, 0x40000, 7 }, -}; - #elif defined(STM32H743xx) - #define FLASH_LAYOUT_STR "@Internal Flash /0x08000000/16*128Kg" MBOOT_SPIFLASH_LAYOUT MBOOT_SPIFLASH2_LAYOUT - -static const flash_layout_t flash_layout[] = { - { 0x08000000, 0x20000, 16 }, -}; - +#elif defined(STM32WB) +#define FLASH_LAYOUT_STR "@Internal Flash /0x08000000/256*04Kg" MBOOT_SPIFLASH_LAYOUT MBOOT_SPIFLASH2_LAYOUT #endif -static inline bool flash_is_valid_addr(uint32_t addr) { - uint8_t last = MP_ARRAY_SIZE(flash_layout) - 1; - uint32_t end_of_flash = flash_layout[last].base_address + - flash_layout[last].sector_count * flash_layout[last].sector_size; - return flash_layout[0].base_address <= addr && addr < end_of_flash; -} - -static uint32_t flash_get_sector_index(uint32_t addr, uint32_t *sector_size) { - if (addr >= flash_layout[0].base_address) { - uint32_t sector_index = 0; - for (int i = 0; i < MP_ARRAY_SIZE(flash_layout); ++i) { - for (int j = 0; j < flash_layout[i].sector_count; ++j) { - uint32_t sector_start_next = flash_layout[i].base_address - + (j + 1) * flash_layout[i].sector_size; - if (addr < sector_start_next) { - *sector_size = flash_layout[i].sector_size; - return sector_index; - } - ++sector_index; - } - } - } - return 0; -} - -#if defined(STM32H7) -// get the bank of a given flash address -static uint32_t get_bank(uint32_t addr) { - if (READ_BIT(FLASH->OPTCR, FLASH_OPTCR_SWAP_BANK) == 0) { - // no bank swap - if (addr < (FLASH_BASE + FLASH_BANK_SIZE)) { - return FLASH_BANK_1; - } else { - return FLASH_BANK_2; - } - } else { - // bank swap - if (addr < (FLASH_BASE + FLASH_BANK_SIZE)) { - return FLASH_BANK_2; - } else { - return FLASH_BANK_1; - } - } -} -#endif - -static int flash_mass_erase(void) { - // TODO - return -1; +static int mboot_flash_mass_erase(void) { + // Erase all flash pages after mboot. + int ret = flash_erase(APPLICATION_ADDR, APPLICATION_FLASH_LENGTH / sizeof(uint32_t)); + return ret; } -static int flash_page_erase(uint32_t addr, uint32_t *next_addr) { +static int mboot_flash_page_erase(uint32_t addr, uint32_t *next_addr) { uint32_t sector_size = 0; - uint32_t sector = flash_get_sector_index(addr, §or_size); - if (sector == 0) { - // Don't allow to erase the sector with this bootloader in it + uint32_t sector_start = 0; + int32_t sector = flash_get_sector_info(addr, §or_start, §or_size); + if (sector <= 0) { + // Don't allow to erase the sector with this bootloader in it, or invalid sectors dfu_context.status = DFU_STATUS_ERROR_ADDRESS; - dfu_context.error = MBOOT_ERROR_STR_OVERWRITE_BOOTLOADER_IDX; + dfu_context.error = (sector == 0) ? MBOOT_ERROR_STR_OVERWRITE_BOOTLOADER_IDX + : MBOOT_ERROR_STR_INVALID_ADDRESS_IDX; return -1; } - *next_addr = addr + sector_size; - - HAL_FLASH_Unlock(); - - // Clear pending flags (if any) - #if defined(STM32H7) - __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS_BANK1 | FLASH_FLAG_ALL_ERRORS_BANK2); - #else - __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | - FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR); - #endif - - // erase the sector(s) - FLASH_EraseInitTypeDef EraseInitStruct; - EraseInitStruct.TypeErase = TYPEERASE_SECTORS; - EraseInitStruct.VoltageRange = VOLTAGE_RANGE_3; // voltage range needs to be 2.7V to 3.6V - #if defined(STM32H7) - EraseInitStruct.Banks = get_bank(addr); - #endif - EraseInitStruct.Sector = sector; - EraseInitStruct.NbSectors = 1; + *next_addr = sector_start + sector_size; - uint32_t SectorError = 0; - if (HAL_FLASHEx_Erase(&EraseInitStruct, &SectorError) != HAL_OK) { - // error occurred during sector erase - return -1; + // Erase the flash page. + int ret = flash_erase(sector_start, sector_size / sizeof(uint32_t)); + if (ret != 0) { + return ret; } // Check the erase set bits to 1, at least for the first 256 bytes for (int i = 0; i < 64; ++i) { - if (((volatile uint32_t*)addr)[i] != 0xffffffff) { + if (((volatile uint32_t*)sector_start)[i] != 0xffffffff) { return -2; } } @@ -654,42 +502,25 @@ static int flash_page_erase(uint32_t addr, uint32_t *next_addr) { return 0; } -static int flash_write(uint32_t addr, const uint8_t *src8, size_t len) { - if (addr >= flash_layout[0].base_address && addr < flash_layout[0].base_address + flash_layout[0].sector_size) { +static int mboot_flash_write(uint32_t addr, const uint8_t *src8, size_t len) { + int32_t sector = flash_get_sector_info(addr, NULL, NULL); + if (sector <= 0) { // Don't allow to write the sector with this bootloader in it dfu_context.status = DFU_STATUS_ERROR_ADDRESS; - dfu_context.error = MBOOT_ERROR_STR_OVERWRITE_BOOTLOADER_IDX; + dfu_context.error = (sector == 0) ? MBOOT_ERROR_STR_OVERWRITE_BOOTLOADER_IDX + : MBOOT_ERROR_STR_INVALID_ADDRESS_IDX; return -1; } const uint32_t *src = (const uint32_t*)src8; size_t num_word32 = (len + 3) / 4; - HAL_FLASH_Unlock(); - #if defined(STM32H7) - - // program the flash 256 bits at a time - for (int i = 0; i < num_word32 / 8; ++i) { - if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, addr, (uint64_t)(uint32_t)src) != HAL_OK) { - return - 1; - } - addr += 32; - src += 8; + // Write the data to flash. + int ret = flash_write(addr, src, num_word32); + if (ret != 0) { + return ret; } - #else - - // program the flash word by word - for (size_t i = 0; i < num_word32; i++) { - if (HAL_FLASH_Program(TYPEPROGRAM_WORD, addr, *src) != HAL_OK) { - return -1; - } - addr += 4; - src += 1; - } - - #endif - // TODO verify data return 0; @@ -699,8 +530,8 @@ static int flash_write(uint32_t addr, const uint8_t *src8, size_t len) { // Writable address space interface static int do_mass_erase(void) { - // TODO - return flash_mass_erase(); + // TODO spiflash erase ? + return mboot_flash_mass_erase(); } #if defined(MBOOT_SPIFLASH_ADDR) || defined(MBOOT_SPIFLASH2_ADDR) @@ -735,7 +566,7 @@ int do_page_erase(uint32_t addr, uint32_t *next_addr) { } else #endif { - ret = flash_page_erase(addr, next_addr); + ret = mboot_flash_page_erase(addr, next_addr); } led0_state((ret == 0) ? LED0_STATE_SLOW_FLASH : LED0_STATE_SLOW_INVERTED_FLASH); @@ -775,7 +606,7 @@ int do_write(uint32_t addr, const uint8_t *src8, size_t len) { } else #endif if (flash_is_valid_addr(addr)) { - ret = flash_write(addr, src8, len); + ret = mboot_flash_write(addr, src8, len); } else { dfu_context.status = DFU_STATUS_ERROR_ADDRESS; dfu_context.error = MBOOT_ERROR_STR_INVALID_ADDRESS_IDX; @@ -840,7 +671,7 @@ void i2c_init(int addr) { i2c_slave_init(MBOOT_I2Cx, I2Cx_EV_IRQn, IRQ_PRI_I2C, addr); } -int i2c_slave_process_addr_match(int rw) { +int i2c_slave_process_addr_match(i2c_slave_t *i2c, int rw) { if (i2c_obj.cmd_arg_sent) { i2c_obj.cmd_send_arg = false; } @@ -848,14 +679,14 @@ int i2c_slave_process_addr_match(int rw) { return 0; // ACK } -int i2c_slave_process_rx_byte(uint8_t val) { +int i2c_slave_process_rx_byte(i2c_slave_t *i2c, uint8_t val) { if (i2c_obj.cmd_buf_pos < sizeof(i2c_obj.cmd_buf)) { i2c_obj.cmd_buf[i2c_obj.cmd_buf_pos++] = val; } return 0; // ACK } -void i2c_slave_process_rx_end(void) { +void i2c_slave_process_rx_end(i2c_slave_t *i2c) { if (i2c_obj.cmd_buf_pos == 0) { return; } @@ -940,7 +771,7 @@ void i2c_slave_process_rx_end(void) { i2c_obj.cmd_arg_sent = false; } -uint8_t i2c_slave_process_tx_byte(void) { +uint8_t i2c_slave_process_tx_byte(i2c_slave_t *i2c) { if (i2c_obj.cmd_send_arg) { i2c_obj.cmd_arg_sent = true; return i2c_obj.cmd_arg; @@ -951,6 +782,9 @@ uint8_t i2c_slave_process_tx_byte(void) { } } +void i2c_slave_process_tx_end(i2c_slave_t *i2c) { +} + #endif // defined(MBOOT_I2C_SCL) /******************************************************************************/ @@ -968,16 +802,19 @@ static int dfu_process_dnload(void) { int ret = -1; if (dfu_context.wBlockNum == 0) { // download control commands - if (dfu_context.wLength >= 1 && dfu_context.buf[0] == 0x41) { + if (dfu_context.wLength >= 1 && dfu_context.buf[0] == DFU_CMD_DNLOAD_ERASE) { if (dfu_context.wLength == 1) { // mass erase ret = do_mass_erase(); + if (ret != 0) { + dfu_context.cmd = DFU_CMD_NONE; + } } else if (dfu_context.wLength == 5) { // erase page uint32_t next_addr; ret = do_page_erase(get_le32(&dfu_context.buf[1]), &next_addr); } - } else if (dfu_context.wLength >= 1 && dfu_context.buf[0] == 0x21) { + } else if (dfu_context.wLength >= 1 && dfu_context.buf[0] == DFU_CMD_DNLOAD_SET_ADDRESS) { if (dfu_context.wLength == 5) { // set address dfu_context.addr = get_le32(&dfu_context.buf[1]); @@ -1061,13 +898,19 @@ static int dfu_handle_tx(int cmd, int arg, int len, uint8_t *buf, int max_len) { default: dfu_context.state = DFU_STATE_BUSY; } - buf[0] = dfu_context.status; // bStatus - buf[1] = 0; // bwPollTimeout (ms) - buf[2] = 0; // bwPollTimeout (ms) - buf[3] = 0; // bwPollTimeout (ms) - buf[4] = dfu_context.state; // bState - buf[5] = dfu_context.error; // iString + buf[0] = dfu_context.status; // bStatus + buf[1] = 0; // bwPollTimeout_lsb (ms) + buf[2] = 0; // bwPollTimeout (ms) + buf[3] = 0; // bwPollTimeout_msb (ms) + buf[4] = dfu_context.state; // bState + buf[5] = dfu_context.error; // iString + // Clear errors now they've been sent + dfu_context.status = DFU_STATUS_OK; + dfu_context.error = 0; return 6; + } else if (cmd == DFU_GETSTATE && len == 1) { + buf[0] = dfu_context.state; // bState + return 1; } return -1; } @@ -1116,12 +959,19 @@ typedef struct _pyb_usbdd_obj_t { #define MBOOT_USB_PID BOOTLOADER_DFU_USB_PID #endif +#if !MICROPY_HW_USB_IS_MULTI_OTG +STATIC const uint8_t usbd_fifo_size[USBD_PMA_NUM_FIFO] = { + 32, 32, // EP0(out), EP0(in) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 14x unused +}; +#else static const uint8_t usbd_fifo_size[] = { 32, 8, 16, 8, 16, 0, 0, // FS: RX, EP0(in), 5x IN endpoints #if MICROPY_HW_USB_HS 116, 8, 64, 4, 64, 0, 0, 0, 0, 0, // HS: RX, EP0(in), 8x IN endpoints #endif }; +#endif __ALIGN_BEGIN static const uint8_t USBD_LangIDDesc[USB_LEN_LANGID_STR_DESC] __ALIGN_END = { USB_LEN_LANGID_STR_DESC, @@ -1437,12 +1287,29 @@ static void do_reset(void) { NVIC_SystemReset(); } -uint32_t SystemCoreClock; - extern PCD_HandleTypeDef pcd_fs_handle; extern PCD_HandleTypeDef pcd_hs_handle; void stm32_main(int initial_r0) { + #if defined(STM32H7) + // Configure write-once power options, and wait for voltage levels to be ready + PWR->CR3 = PWR_CR3_LDOEN; + while (!(PWR->CSR1 & PWR_CSR1_ACTVOSRDY)) { + } + + // Reset the kernel clock configuration registers for all domains. + RCC->D1CCIPR = 0x00000000; + RCC->D2CCIP1R = 0x00000000; + RCC->D2CCIP2R = 0x00000000; + RCC->D3CCIPR = 0x00000000; + #endif + + // Make sure IRQ vector table points to flash where this bootloader lives. + SCB->VTOR = FLASH_BASE; + + // Enable 8-byte stack alignment for IRQ handlers, in accord with EABI + SCB->CCR |= SCB_CCR_STKALIGN_Msk; + #if defined(STM32F4) #if INSTRUCTION_CACHE_ENABLE __HAL_FLASH_INSTRUCTION_CACHE_ENABLE(); @@ -1481,9 +1348,6 @@ void stm32_main(int initial_r0) { goto enter_bootloader; } - // MCU starts up with HSI - SystemCoreClock = HSI_VALUE; - int reset_mode = get_reset_mode(); uint32_t msp = *(volatile uint32_t*)APPLICATION_ADDR; if (reset_mode != 4 && (msp & APP_VALIDITY_BITS) == 0) { @@ -1636,6 +1500,15 @@ void I2Cx_EV_IRQHandler(void) { #endif #if !USE_USB_POLLING + +#if defined(STM32WB) + +void USB_LP_IRQHandler(void) { + HAL_PCD_IRQHandler(&pcd_fs_handle); +} + +#else + #if MBOOT_USB_AUTODETECT_PORT || MICROPY_HW_USB_MAIN_DEV == USB_PHY_FS_ID void OTG_FS_IRQHandler(void) { HAL_PCD_IRQHandler(&pcd_fs_handle); @@ -1648,3 +1521,5 @@ void OTG_HS_IRQHandler(void) { } #endif #endif + +#endif diff --git a/ports/stm32/mboot/mboot.h b/ports/stm32/mboot/mboot.h index 7dc1ada0c32f6..37929665ebe95 100644 --- a/ports/stm32/mboot/mboot.h +++ b/ports/stm32/mboot/mboot.h @@ -3,7 +3,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2019 Damien P. George + * Copyright (c) 2019-2020 Damien P. George * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,6 +23,8 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ +#ifndef MICROPY_INCLUDED_STM32_MBOOT_MBOOT_H +#define MICROPY_INCLUDED_STM32_MBOOT_MBOOT_H #include #include @@ -42,13 +44,10 @@ enum { enum { ELEM_MOUNT_FAT = 1, + ELEM_MOUNT_LFS1, + ELEM_MOUNT_LFS2, }; -typedef struct _fsload_bdev_t { - uint32_t base_addr; - uint32_t byte_len; -} fsload_bdev_t; - extern uint8_t _estack[ELEM_DATA_SIZE]; uint32_t get_le32(const uint8_t *b); @@ -60,3 +59,5 @@ int do_write(uint32_t addr, const uint8_t *src8, size_t len); const uint8_t *elem_search(const uint8_t *elem, uint8_t elem_id); int fsload_process(void); + +#endif // MICROPY_INCLUDED_STM32_MBOOT_MBOOT_H diff --git a/ports/stm32/mboot/mphalport.h b/ports/stm32/mboot/mphalport.h index 0c8cb91a6883a..56d18a9b05d1d 100644 --- a/ports/stm32/mboot/mphalport.h +++ b/ports/stm32/mboot/mphalport.h @@ -28,6 +28,11 @@ #include "genhdr/pins.h" +// For simplicity just convert all HAL errors to one errno. +static inline int mp_hal_status_to_neg_errno(HAL_StatusTypeDef status) { + return status == HAL_OK ? 0 : -1; +} + #define mp_hal_delay_us_fast(us) mp_hal_delay_us(us) #define MP_HAL_PIN_MODE_INPUT (0) diff --git a/ports/stm32/mboot/vfs.h b/ports/stm32/mboot/vfs.h new file mode 100644 index 0000000000000..6cf883a13975a --- /dev/null +++ b/ports/stm32/mboot/vfs.h @@ -0,0 +1,96 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2020 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef MICROPY_INCLUDED_STM32_MBOOT_VFS_H +#define MICROPY_INCLUDED_STM32_MBOOT_VFS_H + +#include "gzstream.h" +#include "mboot.h" + +#if MBOOT_VFS_FAT + +#include "lib/oofatfs/ff.h" + +typedef struct _vfs_fat_context_t { + uint32_t bdev_base_addr; + uint32_t bdev_byte_len; + FATFS fatfs; + FIL fp; +} vfs_fat_context_t; + +extern const stream_methods_t vfs_fat_stream_methods; + +int vfs_fat_mount(vfs_fat_context_t *ctx, uint32_t base_addr, uint32_t byte_len); + +#endif + +#if MBOOT_VFS_LFS1 + +#include "lib/littlefs/lfs1.h" + +#define LFS_READ_SIZE (32) +#define LFS_PROG_SIZE (32) +#define LFS_LOOKAHEAD_SIZE (32) + +typedef struct _vfs_lfs1_context_t { + uint32_t bdev_base_addr; + struct lfs1_config config; + lfs1_t lfs; + struct lfs1_file_config filecfg; + uint8_t filebuf[LFS_PROG_SIZE]; + lfs1_file_t file; +} vfs_lfs1_context_t; + +extern const stream_methods_t vfs_lfs1_stream_methods; + +int vfs_lfs1_mount(vfs_lfs1_context_t *ctx, uint32_t base_addr, uint32_t byte_len); + +#endif + +#if MBOOT_VFS_LFS2 + +#include "lib/littlefs/lfs2.h" + +#define LFS_READ_SIZE (32) +#define LFS_PROG_SIZE (32) +#define LFS_CACHE_SIZE (4 * LFS_READ_SIZE) +#define LFS_LOOKAHEAD_SIZE (32) + +typedef struct _vfs_lfs2_context_t { + uint32_t bdev_base_addr; + struct lfs2_config config; + lfs2_t lfs; + struct lfs2_file_config filecfg; + uint8_t filebuf[LFS_CACHE_SIZE]; // lfs2 specific + lfs2_file_t file; +} vfs_lfs2_context_t; + +extern const stream_methods_t vfs_lfs2_stream_methods; + +int vfs_lfs2_mount(vfs_lfs2_context_t *ctx, uint32_t base_addr, uint32_t byte_len); + +#endif + +#endif // MICROPY_INCLUDED_STM32_MBOOT_VFS_H diff --git a/ports/stm32/mboot/diskio.c b/ports/stm32/mboot/vfs_fat.c similarity index 59% rename from ports/stm32/mboot/diskio.c rename to ports/stm32/mboot/vfs_fat.c index 5f68f26a8e669..20de074f0dcf0 100644 --- a/ports/stm32/mboot/diskio.c +++ b/ports/stm32/mboot/vfs_fat.c @@ -3,7 +3,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2019 Damien P. George + * Copyright (c) 2019-2020 Damien P. George * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -28,8 +28,9 @@ #include "lib/oofatfs/ff.h" #include "lib/oofatfs/diskio.h" #include "mboot.h" +#include "vfs.h" -#if MBOOT_FSLOAD +#if MBOOT_FSLOAD && MBOOT_VFS_FAT #if FF_MAX_SS == FF_MIN_SS #define SECSIZE (FF_MIN_SS) @@ -38,10 +39,10 @@ #endif DRESULT disk_read(void *pdrv, BYTE *buf, DWORD sector, UINT count) { - fsload_bdev_t *bdev = pdrv; + vfs_fat_context_t *ctx = pdrv; - if (0 <= sector && sector < bdev->byte_len / 512) { - do_read(bdev->base_addr + sector * SECSIZE, count * SECSIZE, buf); + if (0 <= sector && sector < ctx->bdev_byte_len / 512) { + do_read(ctx->bdev_base_addr + sector * SECSIZE, count * SECSIZE, buf); return RES_OK; } @@ -49,14 +50,14 @@ DRESULT disk_read(void *pdrv, BYTE *buf, DWORD sector, UINT count) { } DRESULT disk_ioctl(void *pdrv, BYTE cmd, void *buf) { - fsload_bdev_t *bdev = pdrv; + vfs_fat_context_t *ctx = pdrv; switch (cmd) { case CTRL_SYNC: return RES_OK; case GET_SECTOR_COUNT: - *((DWORD*)buf) = bdev->byte_len / SECSIZE; + *((DWORD*)buf) = ctx->bdev_byte_len / SECSIZE; return RES_OK; case GET_SECTOR_SIZE: @@ -77,4 +78,45 @@ DRESULT disk_ioctl(void *pdrv, BYTE cmd, void *buf) { } } -#endif // MBOOT_FSLOAD +int vfs_fat_mount(vfs_fat_context_t *ctx, uint32_t base_addr, uint32_t byte_len) { + ctx->bdev_base_addr = base_addr; + ctx->bdev_byte_len = byte_len; + ctx->fatfs.drv = ctx; + FRESULT res = f_mount(&ctx->fatfs); + if (res != FR_OK) { + return -1; + } + return 0; +} + +static int vfs_fat_stream_open(void *stream_in, const char *fname) { + vfs_fat_context_t *stream = stream_in; + FRESULT res = f_open(&stream->fatfs, &stream->fp, fname, FA_READ); + if (res != FR_OK) { + return -1; + } + return 0; +} + +static void vfs_fat_stream_close(void *stream_in) { + vfs_fat_context_t *stream = stream_in; + f_close(&stream->fp); +} + +static int vfs_fat_stream_read(void *stream_in, uint8_t *buf, size_t len) { + vfs_fat_context_t *stream = stream_in; + UINT n; + FRESULT res = f_read(&stream->fp, buf, len, &n); + if (res != FR_OK) { + return -1; + } + return n; +} + +const stream_methods_t vfs_fat_stream_methods = { + vfs_fat_stream_open, + vfs_fat_stream_close, + vfs_fat_stream_read, +}; + +#endif // MBOOT_FSLOAD && MBOOT_VFS_FAT diff --git a/ports/stm32/mboot/vfs_lfs.c b/ports/stm32/mboot/vfs_lfs.c new file mode 100644 index 0000000000000..dec7c015fc087 --- /dev/null +++ b/ports/stm32/mboot/vfs_lfs.c @@ -0,0 +1,177 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "py/mphal.h" +#include "mboot.h" +#include "vfs.h" + +#if MBOOT_FSLOAD && (MBOOT_VFS_LFS1 || MBOOT_VFS_LFS2) + +#if MBOOT_VFS_LFS1 +#if MBOOT_VFS_LFS2 +#error Unsupported +#endif + +#define LFSx_MACRO(s) LFS1##s +#define LFSx_API(x) lfs1_ ## x +#define VFS_LFSx_CONTEXT_T vfs_lfs1_context_t +#define VFS_LFSx_MOUNT vfs_lfs1_mount +#define VFS_LFSx_STREAM_METHODS vfs_lfs1_stream_methods + +#define SUPERBLOCK_MAGIC_OFFSET (40) +#define SUPERBLOCK_BLOCK_SIZE_OFFSET (28) +#define SUPERBLOCK_BLOCK_COUNT_OFFSET (32) + +static uint8_t lfs_read_buffer[LFS_READ_SIZE]; +static uint8_t lfs_prog_buffer[LFS_PROG_SIZE]; +static uint8_t lfs_lookahead_buffer[LFS_LOOKAHEAD_SIZE / 8]; + +#else + +#define LFSx_MACRO(s) LFS2##s +#define LFSx_API(x) lfs2_ ## x +#define VFS_LFSx_CONTEXT_T vfs_lfs2_context_t +#define VFS_LFSx_MOUNT vfs_lfs2_mount +#define VFS_LFSx_STREAM_METHODS vfs_lfs2_stream_methods + +#define SUPERBLOCK_MAGIC_OFFSET (8) +#define SUPERBLOCK_BLOCK_SIZE_OFFSET (24) +#define SUPERBLOCK_BLOCK_COUNT_OFFSET (28) + +static uint8_t lfs_read_buffer[LFS_CACHE_SIZE]; +static uint8_t lfs_prog_buffer[LFS_CACHE_SIZE]; +static uint8_t lfs_lookahead_buffer[LFS_LOOKAHEAD_SIZE]; + +#endif + +static int dev_read(const struct LFSx_API (config) * c, LFSx_API(block_t) block, LFSx_API(off_t) off, void *buffer, LFSx_API(size_t) size) { + VFS_LFSx_CONTEXT_T *ctx = c->context; + if (0 <= block && block < ctx->config.block_count) { + do_read(ctx->bdev_base_addr + block * ctx->config.block_size + off, size, buffer); + return LFSx_MACRO(_ERR_OK); + } + return LFSx_MACRO(_ERR_IO); +} + +static int dev_prog(const struct LFSx_API (config) * c, LFSx_API(block_t) block, LFSx_API(off_t) off, const void *buffer, LFSx_API(size_t) size) { + return LFSx_MACRO(_ERR_IO); +} + +static int dev_erase(const struct LFSx_API (config) * c, LFSx_API(block_t) block) { + return LFSx_MACRO(_ERR_IO); +} + +static int dev_sync(const struct LFSx_API (config) * c) { + return LFSx_MACRO(_ERR_OK); +} + +int VFS_LFSx_MOUNT(VFS_LFSx_CONTEXT_T *ctx, uint32_t base_addr, uint32_t byte_len) { + // Read start of superblock. + uint8_t buf[48]; + do_read(base_addr, sizeof(buf), buf); + + // Verify littlefs and detect block size. + if (memcmp(&buf[SUPERBLOCK_MAGIC_OFFSET], "littlefs", 8) != 0) { + return -1; + } + uint32_t block_size = get_le32(&buf[SUPERBLOCK_BLOCK_SIZE_OFFSET]); + uint32_t block_count = get_le32(&buf[SUPERBLOCK_BLOCK_COUNT_OFFSET]); + + // Verify size of volume. + if (block_size * block_count != byte_len) { + return -1; + } + + ctx->bdev_base_addr = base_addr; + + struct LFSx_API (config) *config = &ctx->config; + memset(config, 0, sizeof(*config)); + + config->context = ctx; + + config->read = dev_read; + config->prog = dev_prog; + config->erase = dev_erase; + config->sync = dev_sync; + + config->read_size = LFS_READ_SIZE; + config->prog_size = LFS_PROG_SIZE; + config->block_size = block_size; + config->block_count = byte_len / block_size; + + #if MBOOT_VFS_LFS1 + config->lookahead = LFS_LOOKAHEAD_SIZE; + config->read_buffer = lfs_read_buffer; + config->prog_buffer = lfs_prog_buffer; + config->lookahead_buffer = lfs_lookahead_buffer; + #else + config->block_cycles = 100; + config->cache_size = LFS_CACHE_SIZE; + config->lookahead_size = LFS_LOOKAHEAD_SIZE; + config->read_buffer = lfs_read_buffer; + config->prog_buffer = lfs_prog_buffer; + config->lookahead_buffer = lfs_lookahead_buffer; + #endif + + int ret = LFSx_API(mount)(&ctx->lfs, &ctx->config); + if (ret < 0) { + return -1; + } + return 0; +} + +static int vfs_lfs_stream_open(void *stream_in, const char *fname) { + VFS_LFSx_CONTEXT_T *ctx = stream_in; + memset(&ctx->file, 0, sizeof(ctx->file)); + memset(&ctx->filecfg, 0, sizeof(ctx->filecfg)); + ctx->filecfg.buffer = &ctx->filebuf[0]; + LFSx_API(file_opencfg)(&ctx->lfs, &ctx->file, fname, LFSx_MACRO(_O_RDONLY), &ctx->filecfg); + return 0; +} + +static void vfs_lfs_stream_close(void *stream_in) { + VFS_LFSx_CONTEXT_T *ctx = stream_in; + LFSx_API(file_close)(&ctx->lfs, &ctx->file); +} + +static int vfs_lfs_stream_read(void *stream_in, uint8_t *buf, size_t len) { + VFS_LFSx_CONTEXT_T *ctx = stream_in; + LFSx_API(ssize_t) sz = LFSx_API(file_read)(&ctx->lfs, &ctx->file, buf, len); + if (sz < 0) { + return -1; + } + return sz; +} + +const stream_methods_t VFS_LFSx_STREAM_METHODS = { + vfs_lfs_stream_open, + vfs_lfs_stream_close, + vfs_lfs_stream_read, +}; + +#endif // MBOOT_FSLOAD && (MBOOT_VFS_LFS1 || MBOOT_VFS_LFS2) diff --git a/ports/stm32/modmachine.c b/ports/stm32/modmachine.c index 0795d2ccf04b8..9fa00a915186f 100644 --- a/ports/stm32/modmachine.c +++ b/ports/stm32/modmachine.c @@ -37,6 +37,7 @@ #include "extmod/machine_signal.h" #include "extmod/machine_pulse.h" #include "extmod/machine_i2c.h" +#include "extmod/machine_spi.h" #include "lib/utils/pyexec.h" #include "lib/oofatfs/ff.h" #include "extmod/vfs.h" @@ -307,7 +308,7 @@ STATIC mp_obj_t machine_freq(size_t n_args, const mp_obj_t *args) { return mp_obj_new_tuple(MP_ARRAY_SIZE(tuple), tuple); } else { // set - #if defined(STM32F0) || defined(STM32L0) || defined(STM32L4) || defined(STM32WB) + #if defined(STM32F0) || defined(STM32L0) || defined(STM32L4) mp_raise_NotImplementedError(MP_ERROR_TEXT("machine.freq set not supported yet")); #else mp_int_t sysclk = mp_obj_get_int(args[0]); @@ -317,8 +318,13 @@ STATIC mp_obj_t machine_freq(size_t n_args, const mp_obj_t *args) { ahb /= 2; } #endif + #if defined(STM32WB) + mp_int_t apb1 = ahb; + mp_int_t apb2 = ahb; + #else mp_int_t apb1 = ahb / 4; mp_int_t apb2 = ahb / 2; + #endif if (n_args > 1) { ahb = mp_obj_get_int(args[1]); if (n_args > 2) { @@ -341,6 +347,15 @@ STATIC mp_obj_t machine_freq(size_t n_args, const mp_obj_t *args) { } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_freq_obj, 0, 4, machine_freq); +// idle() +// This executies a wfi machine instruction which reduces power consumption +// of the MCU until an interrupt occurs, at which point execution continues. +STATIC mp_obj_t machine_idle(void) { + __WFI(); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_0(machine_idle_obj, machine_idle); + STATIC mp_obj_t machine_lightsleep(size_t n_args, const mp_obj_t *args) { if (n_args != 0) { mp_obj_t args2[2] = {MP_OBJ_NULL, args[0]}; @@ -377,7 +392,7 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { #if MICROPY_HW_ENABLE_RNG { MP_ROM_QSTR(MP_QSTR_rng), MP_ROM_PTR(&pyb_rng_get_obj) }, #endif - { MP_ROM_QSTR(MP_QSTR_idle), MP_ROM_PTR(&pyb_wfi_obj) }, + { MP_ROM_QSTR(MP_QSTR_idle), MP_ROM_PTR(&machine_idle_obj) }, { MP_ROM_QSTR(MP_QSTR_sleep), MP_ROM_PTR(&machine_lightsleep_obj) }, { MP_ROM_QSTR(MP_QSTR_lightsleep), MP_ROM_PTR(&machine_lightsleep_obj) }, { MP_ROM_QSTR(MP_QSTR_deepsleep), MP_ROM_PTR(&machine_deepsleep_obj) }, @@ -386,8 +401,8 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_wake_reason), MP_ROM_PTR(&machine_wake_reason_obj) }, #endif - { MP_ROM_QSTR(MP_QSTR_disable_irq), MP_ROM_PTR(&pyb_disable_irq_obj) }, - { MP_ROM_QSTR(MP_QSTR_enable_irq), MP_ROM_PTR(&pyb_enable_irq_obj) }, + { MP_ROM_QSTR(MP_QSTR_disable_irq), MP_ROM_PTR(&machine_disable_irq_obj) }, + { MP_ROM_QSTR(MP_QSTR_enable_irq), MP_ROM_PTR(&machine_enable_irq_obj) }, { MP_ROM_QSTR(MP_QSTR_time_pulse_us), MP_ROM_PTR(&machine_time_pulse_us_obj) }, @@ -401,9 +416,15 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_RTC), MP_ROM_PTR(&pyb_rtc_type) }, { MP_ROM_QSTR(MP_QSTR_ADC), MP_ROM_PTR(&machine_adc_type) }, #if MICROPY_PY_MACHINE_I2C - { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_i2c_type) }, + #if MICROPY_HW_ENABLE_HW_I2C + { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_hard_i2c_type) }, + #else + { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, + #endif + { MP_ROM_QSTR(MP_QSTR_SoftI2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, #endif { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_hard_spi_type) }, + { MP_ROM_QSTR(MP_QSTR_SoftSPI), MP_ROM_PTR(&mp_machine_soft_spi_type) }, { MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&pyb_uart_type) }, { MP_ROM_QSTR(MP_QSTR_WDT), MP_ROM_PTR(&pyb_wdt_type) }, { MP_ROM_QSTR(MP_QSTR_Timer), MP_ROM_PTR(&machine_timer_type) }, diff --git a/ports/stm32/modmachine.h b/ports/stm32/modmachine.h index f04bfb486bb81..e846208540f72 100644 --- a/ports/stm32/modmachine.h +++ b/ports/stm32/modmachine.h @@ -30,6 +30,7 @@ extern const mp_obj_type_t machine_adc_type; extern const mp_obj_type_t machine_timer_type; +extern const mp_obj_type_t machine_hard_i2c_type; void machine_init(void); void machine_deinit(void); @@ -39,7 +40,14 @@ MP_DECLARE_CONST_FUN_OBJ_0(machine_unique_id_obj); MP_DECLARE_CONST_FUN_OBJ_0(machine_reset_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(machine_bootloader_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(machine_freq_obj); + +MP_DECLARE_CONST_FUN_OBJ_0(machine_idle_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(machine_lightsleep_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(machine_deepsleep_obj); +MP_DECLARE_CONST_FUN_OBJ_0(machine_disable_irq_obj); +MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(machine_enable_irq_obj); + +MP_DECLARE_CONST_FUN_OBJ_0(pyb_irq_stats_obj); + #endif // MICROPY_INCLUDED_STM32_MODMACHINE_H diff --git a/ports/stm32/modpyb.c b/ports/stm32/modpyb.c index 321fb62e9a162..c92090699108e 100644 --- a/ports/stm32/modpyb.c +++ b/ports/stm32/modpyb.c @@ -146,9 +146,9 @@ STATIC const mp_rom_map_elem_t pyb_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_repl_info), MP_ROM_PTR(&pyb_set_repl_info_obj) }, #endif - { MP_ROM_QSTR(MP_QSTR_wfi), MP_ROM_PTR(&pyb_wfi_obj) }, - { MP_ROM_QSTR(MP_QSTR_disable_irq), MP_ROM_PTR(&pyb_disable_irq_obj) }, - { MP_ROM_QSTR(MP_QSTR_enable_irq), MP_ROM_PTR(&pyb_enable_irq_obj) }, + { MP_ROM_QSTR(MP_QSTR_wfi), MP_ROM_PTR(&machine_idle_obj) }, + { MP_ROM_QSTR(MP_QSTR_disable_irq), MP_ROM_PTR(&machine_disable_irq_obj) }, + { MP_ROM_QSTR(MP_QSTR_enable_irq), MP_ROM_PTR(&machine_enable_irq_obj) }, #if IRQ_ENABLE_STATS { MP_ROM_QSTR(MP_QSTR_irq_stats), MP_ROM_PTR(&pyb_irq_stats_obj) }, #endif diff --git a/ports/stm32/modstm.c b/ports/stm32/modstm.c index 418b8bde2a14e..3f4f33979afd5 100644 --- a/ports/stm32/modstm.c +++ b/ports/stm32/modstm.c @@ -30,6 +30,7 @@ #include "py/obj.h" #include "py/objint.h" #include "extmod/machine_mem.h" +#include "rfcore.h" #include "portmodules.h" #if MICROPY_PY_STM @@ -44,6 +45,12 @@ STATIC const mp_rom_map_elem_t stm_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_mem32), MP_ROM_PTR(&machine_mem32_obj) }, #include "genhdr/modstm_const.h" + + #if defined(STM32WB) + { MP_ROM_QSTR(MP_QSTR_rfcore_status), MP_ROM_PTR(&rfcore_status_obj) }, + { MP_ROM_QSTR(MP_QSTR_rfcore_fw_version), MP_ROM_PTR(&rfcore_fw_version_obj) }, + { MP_ROM_QSTR(MP_QSTR_rfcore_sys_hci), MP_ROM_PTR(&rfcore_sys_hci_obj) }, + #endif }; STATIC MP_DEFINE_CONST_DICT(stm_module_globals, stm_module_globals_table); diff --git a/ports/stm32/modutime.c b/ports/stm32/modutime.c index 77ec7468c99b6..4bce45eb33315 100644 --- a/ports/stm32/modutime.c +++ b/ports/stm32/modutime.c @@ -76,7 +76,7 @@ STATIC mp_obj_t time_localtime(size_t n_args, const mp_obj_t *args) { } else { mp_int_t seconds = mp_obj_get_int(args[0]); timeutils_struct_time_t tm; - timeutils_seconds_since_2000_to_struct_time(seconds, &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), @@ -125,13 +125,14 @@ STATIC mp_obj_t time_time(void) { RTC_TimeTypeDef time; HAL_RTC_GetTime(&RTCHandle, &time, RTC_FORMAT_BIN); HAL_RTC_GetDate(&RTCHandle, &date, RTC_FORMAT_BIN); - return mp_obj_new_int(timeutils_seconds_since_2000(2000 + date.Year, date.Month, date.Date, time.Hours, time.Minutes, time.Seconds)); + return mp_obj_new_int(timeutils_seconds_since_epoch(2000 + date.Year, date.Month, date.Date, time.Hours, time.Minutes, time.Seconds)); } MP_DEFINE_CONST_FUN_OBJ_0(time_time_obj, time_time); STATIC const mp_rom_map_elem_t time_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_utime) }, + { MP_ROM_QSTR(MP_QSTR_gmtime), MP_ROM_PTR(&time_localtime_obj) }, { MP_ROM_QSTR(MP_QSTR_localtime), MP_ROM_PTR(&time_localtime_obj) }, { MP_ROM_QSTR(MP_QSTR_mktime), MP_ROM_PTR(&time_mktime_obj) }, { MP_ROM_QSTR(MP_QSTR_time), MP_ROM_PTR(&time_time_obj) }, @@ -143,6 +144,7 @@ STATIC const mp_rom_map_elem_t time_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_ticks_cpu), MP_ROM_PTR(&mp_utime_ticks_cpu_obj) }, { MP_ROM_QSTR(MP_QSTR_ticks_add), MP_ROM_PTR(&mp_utime_ticks_add_obj) }, { MP_ROM_QSTR(MP_QSTR_ticks_diff), MP_ROM_PTR(&mp_utime_ticks_diff_obj) }, + { MP_ROM_QSTR(MP_QSTR_time_ns), MP_ROM_PTR(&mp_utime_time_ns_obj) }, }; STATIC MP_DEFINE_CONST_DICT(time_module_globals, time_module_globals_table); diff --git a/ports/stm32/modbluetooth_hci.c b/ports/stm32/mpbthciport.c similarity index 63% rename from ports/stm32/modbluetooth_hci.c rename to ports/stm32/mpbthciport.c index 4e016eae8451c..a5977ff12c210 100644 --- a/ports/stm32/modbluetooth_hci.c +++ b/ports/stm32/mpbthciport.c @@ -26,17 +26,20 @@ #include "py/runtime.h" #include "py/mphal.h" -#include "extmod/modbluetooth_hci.h" +#include "extmod/mpbthci.h" #include "systick.h" #include "pendsv.h" +#include "lib/utils/mpirq.h" #include "py/obj.h" #if MICROPY_PY_BLUETOOTH +#define DEBUG_printf(...) // printf(__VA_ARGS__) + uint8_t mp_bluetooth_hci_cmd_buf[4 + 256]; -// Must be provided by the stack bindings. +// Must be provided by the stack bindings (e.g. mpnimbleport.c or mpbtstackport.c). extern void mp_bluetooth_hci_poll(void); // Hook for pendsv poller to run this periodically every 128ms @@ -61,34 +64,19 @@ STATIC uint16_t hci_uart_rx_buf_cur; STATIC uint16_t hci_uart_rx_buf_len; STATIC uint8_t hci_uart_rx_buf_data[256]; -int mp_bluetooth_hci_controller_deactivate(void) { - return 0; -} - -int mp_bluetooth_hci_controller_sleep_maybe(void) { - return 0; -} - -bool mp_bluetooth_hci_controller_woken(void) { - return true; -} - -int mp_bluetooth_hci_controller_wakeup(void) { - return 0; -} - -int mp_bluetooth_hci_uart_init(uint32_t port) { +int mp_bluetooth_hci_uart_init(uint32_t port, uint32_t baudrate) { (void)port; - return 0; -} - -int mp_bluetooth_hci_uart_activate(void) { + (void)baudrate; rfcore_ble_init(); hci_uart_rx_buf_cur = 0; hci_uart_rx_buf_len = 0; return 0; } +int mp_bluetooth_hci_uart_deinit(void) { + return 0; +} + int mp_bluetooth_hci_uart_set_baudrate(uint32_t baudrate) { (void)baudrate; return 0; @@ -135,60 +123,83 @@ int mp_bluetooth_hci_uart_readchar(void) { #include "uart.h" pyb_uart_obj_t mp_bluetooth_hci_uart_obj; +mp_irq_obj_t mp_bluetooth_hci_uart_irq_obj; static uint8_t hci_uart_rxbuf[512]; mp_obj_t mp_uart_interrupt(mp_obj_t self_in) { - // New HCI data, schedule mp_bluetooth_hci_poll to make the stack handle it. + // DEBUG_printf("mp_uart_interrupt\n"); + // New HCI data, schedule mp_bluetooth_hci_poll via PENDSV to make the stack handle it. mp_bluetooth_hci_poll_wrapper(0); return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_1(mp_uart_interrupt_obj, mp_uart_interrupt); -int mp_bluetooth_hci_uart_init(uint32_t port) { +int mp_bluetooth_hci_uart_init(uint32_t port, uint32_t baudrate) { + DEBUG_printf("mp_bluetooth_hci_uart_init (stm32)\n"); + // bits (8), stop (1), parity (none) and flow (rts/cts) are assumed to match MYNEWT_VAL_BLE_HCI_UART_ constants in syscfg.h. mp_bluetooth_hci_uart_obj.base.type = &pyb_uart_type; mp_bluetooth_hci_uart_obj.uart_id = port; mp_bluetooth_hci_uart_obj.is_static = true; - mp_bluetooth_hci_uart_obj.timeout = 2; - mp_bluetooth_hci_uart_obj.timeout_char = 2; + // We don't want to block indefinitely, but expect flow control is doing its job. + mp_bluetooth_hci_uart_obj.timeout = 200; + mp_bluetooth_hci_uart_obj.timeout_char = 200; MP_STATE_PORT(pyb_uart_obj_all)[mp_bluetooth_hci_uart_obj.uart_id - 1] = &mp_bluetooth_hci_uart_obj; + + // This also initialises the UART. + mp_bluetooth_hci_uart_set_baudrate(baudrate); + return 0; } -int mp_bluetooth_hci_uart_set_baudrate(uint32_t baudrate) { - uart_init(&mp_bluetooth_hci_uart_obj, baudrate, UART_WORDLENGTH_8B, UART_PARITY_NONE, UART_STOPBITS_1, UART_HWCONTROL_RTS | UART_HWCONTROL_CTS); - uart_set_rxbuf(&mp_bluetooth_hci_uart_obj, sizeof(hci_uart_rxbuf), hci_uart_rxbuf); +int mp_bluetooth_hci_uart_deinit(void) { + DEBUG_printf("mp_bluetooth_hci_uart_deinit (stm32)\n"); + // TODO: deinit mp_bluetooth_hci_uart_obj + return 0; } -int mp_bluetooth_hci_uart_activate(void) { - // Interrupt on RX chunk received (idle) - // Trigger stack poll when this happens - mp_obj_t uart_irq_fn = mp_load_attr(MP_OBJ_FROM_PTR(&mp_bluetooth_hci_uart_obj), MP_QSTR_irq); - mp_obj_t uargs[] = { - MP_OBJ_FROM_PTR(&mp_uart_interrupt_obj), - MP_OBJ_NEW_SMALL_INT(UART_FLAG_IDLE), - mp_const_true, - }; - mp_call_function_n_kw(uart_irq_fn, 3, 0, uargs); +int mp_bluetooth_hci_uart_set_baudrate(uint32_t baudrate) { + DEBUG_printf("mp_bluetooth_hci_uart_set_baudrate(%lu) (stm32)\n", baudrate); + if (!baudrate) { + return -1; + } + + uart_init(&mp_bluetooth_hci_uart_obj, baudrate, UART_WORDLENGTH_8B, UART_PARITY_NONE, UART_STOPBITS_1, UART_HWCONTROL_RTS | UART_HWCONTROL_CTS); + uart_set_rxbuf(&mp_bluetooth_hci_uart_obj, sizeof(hci_uart_rxbuf), hci_uart_rxbuf); - mp_bluetooth_hci_controller_init(); - mp_bluetooth_hci_controller_activate(); + // Add IRQ handler for IDLE (i.e. packet finished). + uart_irq_config(&mp_bluetooth_hci_uart_obj, false); + mp_irq_init(&mp_bluetooth_hci_uart_irq_obj, &uart_irq_methods, MP_OBJ_FROM_PTR(&mp_bluetooth_hci_uart_obj)); + mp_bluetooth_hci_uart_obj.mp_irq_obj = &mp_bluetooth_hci_uart_irq_obj; + mp_bluetooth_hci_uart_obj.mp_irq_trigger = UART_FLAG_IDLE; + mp_bluetooth_hci_uart_irq_obj.handler = MP_OBJ_FROM_PTR(&mp_uart_interrupt_obj); + mp_bluetooth_hci_uart_irq_obj.ishard = true; + uart_irq_config(&mp_bluetooth_hci_uart_obj, true); return 0; } int mp_bluetooth_hci_uart_write(const uint8_t *buf, size_t len) { + // DEBUG_printf("mp_bluetooth_hci_uart_write (stm32)\n"); + mp_bluetooth_hci_controller_wakeup(); - uart_tx_strn(&mp_bluetooth_hci_uart_obj, (void *)buf, len); + int errcode; + uart_tx_data(&mp_bluetooth_hci_uart_obj, (void *)buf, len, &errcode); + if (errcode != 0) { + printf("\nmp_bluetooth_hci_uart_write: failed to write to UART %d\n", errcode); + } return 0; } // This function expects the controller to be in the wake state via a previous call // to mp_bluetooth_hci_controller_woken. int mp_bluetooth_hci_uart_readchar(void) { + // DEBUG_printf("mp_bluetooth_hci_uart_readchar (stm32)\n"); + if (uart_rx_any(&mp_bluetooth_hci_uart_obj)) { + // DEBUG_printf("... available\n"); return uart_rx_char(&mp_bluetooth_hci_uart_obj); } else { return -1; @@ -197,4 +208,33 @@ int mp_bluetooth_hci_uart_readchar(void) { #endif // defined(STM32WB) +// Default (weak) implementation of the HCI controller interface. +// A driver (e.g. cywbt43.c) can override these for controller-specific +// functionality (i.e. power management). + +MP_WEAK int mp_bluetooth_hci_controller_init(void) { + DEBUG_printf("mp_bluetooth_hci_controller_init (default)\n"); + return 0; +} + +MP_WEAK int mp_bluetooth_hci_controller_deinit(void) { + DEBUG_printf("mp_bluetooth_hci_controller_deinit (default)\n"); + return 0; +} + +MP_WEAK int mp_bluetooth_hci_controller_sleep_maybe(void) { + DEBUG_printf("mp_bluetooth_hci_controller_sleep_maybe (default)\n"); + return 0; +} + +MP_WEAK bool mp_bluetooth_hci_controller_woken(void) { + DEBUG_printf("mp_bluetooth_hci_controller_woken (default)\n"); + return true; +} + +MP_WEAK int mp_bluetooth_hci_controller_wakeup(void) { + DEBUG_printf("mp_bluetooth_hci_controller_wakeup (default)\n"); + return 0; +} + #endif // MICROPY_PY_BLUETOOTH diff --git a/ports/stm32/mpbtstackport.c b/ports/stm32/mpbtstackport.c new file mode 100644 index 0000000000000..a031a6a151aff --- /dev/null +++ b/ports/stm32/mpbtstackport.c @@ -0,0 +1,107 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/runtime.h" +#include "py/mperrno.h" +#include "py/mphal.h" + +#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK + +#include "lib/btstack/src/btstack.h" +#include "lib/btstack/platform/embedded/btstack_run_loop_embedded.h" +#include "lib/btstack/platform/embedded/hal_cpu.h" +#include "lib/btstack/platform/embedded/hal_time_ms.h" + +#include "extmod/mpbthci.h" +#include "extmod/btstack/btstack_hci_uart.h" +#include "extmod/btstack/modbluetooth_btstack.h" + +// The IRQ functionality in btstack_run_loop_embedded.c is not used, so the +// following three functions are empty. + +void hal_cpu_disable_irqs(void) { +} + +void hal_cpu_enable_irqs(void) { +} + +void hal_cpu_enable_irqs_and_sleep(void) { +} + +uint32_t hal_time_ms(void) { + return mp_hal_ticks_ms(); +} + +STATIC const hci_transport_config_uart_t hci_transport_config_uart = { + HCI_TRANSPORT_CONFIG_UART, + MICROPY_HW_BLE_UART_BAUDRATE, + 3000000, + 0, + NULL, +}; + +void mp_bluetooth_hci_poll(void) { + if (mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_OFF) { + return; + } + + // Process uart data. + if (mp_bluetooth_btstack_state != MP_BLUETOOTH_BTSTACK_STATE_HALTING) { + mp_bluetooth_btstack_hci_uart_process(); + } + + // Call the BTstack run loop. + btstack_run_loop_embedded_execute_once(); +} + +void mp_bluetooth_btstack_port_init(void) { + static bool run_loop_init = false; + if (!run_loop_init) { + run_loop_init = true; + btstack_run_loop_init(btstack_run_loop_embedded_get_instance()); + } else { + btstack_run_loop_embedded_get_instance()->init(); + } + + // hci_dump_open(NULL, HCI_DUMP_STDOUT); + const hci_transport_t *transport = hci_transport_h4_instance(&mp_bluetooth_btstack_hci_uart_block); + hci_init(transport, &hci_transport_config_uart); + + // TODO: Probably not necessary for BCM (we have our own firmware loader in the cyw43 driver), + // but might be worth investigating for other controllers in the future. + // hci_set_chipset(btstack_chipset_bcm_instance()); +} + +void mp_bluetooth_btstack_port_deinit(void) { + hci_power_control(HCI_POWER_OFF); + hci_close(); +} + +void mp_bluetooth_btstack_port_start(void) { + hci_power_control(HCI_POWER_ON); +} + +#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK diff --git a/ports/stm32/mpbtstackport.h b/ports/stm32/mpbtstackport.h new file mode 100644 index 0000000000000..110f921d9a9f8 --- /dev/null +++ b/ports/stm32/mpbtstackport.h @@ -0,0 +1,34 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jim Mussared + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MICROPY_INCLUDED_STM32_BTSTACK_PORT_H +#define MICROPY_INCLUDED_STM32_BTSTACK_PORT_H + +// To allow MICROPY_HW_BLE_UART_ID to be resolved. + +#include "uart.h" + +#endif // MICROPY_INCLUDED_STM32_BTSTACK_PORT_H diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index 13dce6e96d334..7ea41bb6f4655 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -100,6 +100,7 @@ #define MICROPY_PY_BUILTINS_SLICE_INDICES (1) #define MICROPY_PY_BUILTINS_ROUND_INT (1) #define MICROPY_PY_ALL_SPECIAL_METHODS (1) +#define MICROPY_PY_REVERSE_SPECIAL_METHODS (1) #define MICROPY_PY_BUILTINS_COMPILE (MICROPY_ENABLE_COMPILER) #define MICROPY_PY_BUILTINS_EXECFILE (MICROPY_ENABLE_COMPILER) #define MICROPY_PY_BUILTINS_NOTIMPLEMENTED (1) @@ -120,7 +121,7 @@ #define MICROPY_PY_CMATH (1) #define MICROPY_PY_IO (1) #define MICROPY_PY_IO_IOBASE (1) -#define MICROPY_PY_IO_FILEIO (MICROPY_VFS_FAT) // because mp_type_fileio/textio point to fatfs impl +#define MICROPY_PY_IO_FILEIO (MICROPY_VFS_FAT || MICROPY_VFS_LFS1 || MICROPY_VFS_LFS2) #define MICROPY_PY_SYS_MAXSIZE (1) #define MICROPY_PY_SYS_EXIT (1) #define MICROPY_PY_SYS_STDFILES (1) @@ -182,13 +183,9 @@ #define MICROPY_PY_MACHINE_PULSE (1) #define MICROPY_PY_MACHINE_PIN_MAKE_NEW mp_pin_make_new #define MICROPY_PY_MACHINE_I2C (1) -#if MICROPY_HW_ENABLE_HW_I2C -#define MICROPY_PY_MACHINE_I2C_MAKE_NEW machine_hard_i2c_make_new -#endif #define MICROPY_PY_MACHINE_SPI (1) #define MICROPY_PY_MACHINE_SPI_MSB (SPI_FIRSTBIT_MSB) #define MICROPY_PY_MACHINE_SPI_LSB (SPI_FIRSTBIT_LSB) -#define MICROPY_PY_MACHINE_SPI_MAKE_NEW machine_hard_spi_make_new #define MICROPY_HW_SOFTSPI_MIN_DELAY (0) #define MICROPY_HW_SOFTSPI_MAX_BAUDRATE (HAL_RCC_GetSysClockFreq() / 48) #define MICROPY_PY_UWEBSOCKET (MICROPY_PY_LWIP) @@ -210,9 +207,17 @@ #define MICROPY_FATFS_RPATH (2) #define MICROPY_FATFS_MULTI_PARTITION (1) -// TODO these should be generic, not bound to fatfs +// TODO these should be generic, not bound to a particular FS implementation +#if MICROPY_VFS_FAT #define mp_type_fileio mp_type_vfs_fat_fileio #define mp_type_textio mp_type_vfs_fat_textio +#elif MICROPY_VFS_LFS1 +#define mp_type_fileio mp_type_vfs_lfs1_fileio +#define mp_type_textio mp_type_vfs_lfs1_textio +#elif MICROPY_VFS_LFS2 +#define mp_type_fileio mp_type_vfs_lfs2_fileio +#define mp_type_textio mp_type_vfs_lfs2_textio +#endif // use vfs's functions for import stat and builtin open #define mp_import_stat mp_vfs_import_stat @@ -289,7 +294,8 @@ extern const struct _mp_obj_module_t mp_module_onewire; #if MICROPY_BLUETOOTH_NIMBLE struct _mp_bluetooth_nimble_root_pointers_t; -#define MICROPY_PORT_ROOT_POINTER_BLUETOOTH_NIMBLE void **bluetooth_nimble_memory; struct _mp_bluetooth_nimble_root_pointers_t *bluetooth_nimble_root_pointers; +struct _mp_bluetooth_nimble_malloc_t; +#define MICROPY_PORT_ROOT_POINTER_BLUETOOTH_NIMBLE struct _mp_bluetooth_nimble_malloc_t *bluetooth_nimble_memory; struct _mp_bluetooth_nimble_root_pointers_t *bluetooth_nimble_root_pointers; #else #define MICROPY_PORT_ROOT_POINTER_BLUETOOTH_NIMBLE #endif @@ -401,14 +407,20 @@ static inline mp_uint_t disable_irq(void) { #define MICROPY_THREAD_YIELD() #endif -// The LwIP interface must run at a raised IRQ priority -#define MICROPY_PY_LWIP_ENTER uint32_t atomic_state = raise_irq_pri(IRQ_PRI_PENDSV); -#define MICROPY_PY_LWIP_REENTER atomic_state = raise_irq_pri(IRQ_PRI_PENDSV); -#define MICROPY_PY_LWIP_EXIT restore_irq_pri(atomic_state); +// For regular code that wants to prevent "background tasks" from running. +// These background tasks (LWIP, Bluetooth) run in PENDSV context. +#define MICROPY_PY_PENDSV_ENTER uint32_t atomic_state = raise_irq_pri(IRQ_PRI_PENDSV); +#define MICROPY_PY_PENDSV_REENTER atomic_state = raise_irq_pri(IRQ_PRI_PENDSV); +#define MICROPY_PY_PENDSV_EXIT restore_irq_pri(atomic_state); + +// Prevent the "LWIP task" from running. +#define MICROPY_PY_LWIP_ENTER MICROPY_PY_PENDSV_ENTER +#define MICROPY_PY_LWIP_REENTER MICROPY_PY_PENDSV_REENTER +#define MICROPY_PY_LWIP_EXIT MICROPY_PY_PENDSV_EXIT -// Bluetooth calls must run at a raised IRQ priority -#define MICROPY_PY_BLUETOOTH_ENTER MICROPY_PY_LWIP_ENTER -#define MICROPY_PY_BLUETOOTH_EXIT MICROPY_PY_LWIP_EXIT +// Prevent the "Bluetooth task" from running (either NimBLE or btstack). +#define MICROPY_PY_BLUETOOTH_ENTER MICROPY_PY_PENDSV_ENTER +#define MICROPY_PY_BLUETOOTH_EXIT MICROPY_PY_PENDSV_EXIT // We need an implementation of the log2 function which is not a macro #define MP_NEED_LOG2 (1) diff --git a/ports/stm32/mphalport.h b/ports/stm32/mphalport.h index fe9378a1c7252..a76945db5fea7 100644 --- a/ports/stm32/mphalport.h +++ b/ports/stm32/mphalport.h @@ -4,6 +4,10 @@ extern const unsigned char mp_hal_status_to_errno_table[4]; +static inline int mp_hal_status_to_neg_errno(HAL_StatusTypeDef status) { + return -mp_hal_status_to_errno_table[status]; +} + NORETURN void mp_hal_raise(HAL_StatusTypeDef status); void mp_hal_set_interrupt_char(int c); // -1 to disable diff --git a/ports/stm32/nimble.c b/ports/stm32/mpnimbleport.c similarity index 56% rename from ports/stm32/nimble.c rename to ports/stm32/mpnimbleport.c index 0d349585fb1ae..1d7c095139cde 100644 --- a/ports/stm32/nimble.c +++ b/ports/stm32/mpnimbleport.c @@ -31,58 +31,41 @@ #if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE -#include "transport/uart/ble_hci_uart.h" #include "host/ble_hs.h" +#include "nimble/nimble_npl.h" -#include "extmod/modbluetooth_hci.h" +#include "extmod/mpbthci.h" #include "extmod/nimble/modbluetooth_nimble.h" -#include "extmod/nimble/nimble/nimble_hci_uart.h" - -extern void os_eventq_run_all(void); -extern void os_callout_process(void); +#include "extmod/nimble/hal/hal_uart.h" +// This implements the Nimble "background task". It's called at PENDSV +// priority, either every 128ms or whenever there's UART data available. +// Because it's called via PENDSV, you can implicitly consider that it +// is surrounded by MICROPY_PY_BLUETOOTH_ENTER / MICROPY_PY_BLUETOOTH_EXIT. void mp_bluetooth_hci_poll(void) { - if (mp_bluetooth_nimble_ble_state == MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) { - return; - } - - mp_bluetooth_nimble_hci_uart_process(); - os_callout_process(); - os_eventq_run_all(); -} - -void mp_bluetooth_nimble_port_preinit(void) { - MP_STATE_PORT(bluetooth_nimble_memory) = NULL; - ble_hci_uart_init(); -} + if (mp_bluetooth_nimble_ble_state >= MP_BLUETOOTH_NIMBLE_BLE_STATE_WAITING_FOR_SYNC) { + // Ask NimBLE to process UART data. + mp_bluetooth_nimble_hci_uart_process(); -void mp_bluetooth_nimble_port_postinit(void) { + // Run pending background operations and events, but only after HCI sync. + mp_bluetooth_nimble_os_callout_process(); + mp_bluetooth_nimble_os_eventq_run_all(); + } } -void mp_bluetooth_nimble_port_deinit(void) { - mp_bluetooth_hci_controller_deactivate(); -} +// --- Port-specific helpers for the generic NimBLE bindings. ----------------- -void mp_bluetooth_nimble_port_start(void) { - ble_hs_start(); +void mp_bluetooth_nimble_hci_uart_wfi(void) { + __WFI(); } -void mp_bluetooth_nimble_hci_uart_rx(hal_uart_rx_cb_t rx_cb, void *rx_arg) { - bool host_wake = mp_bluetooth_hci_controller_woken(); - - int chr; - while ((chr = mp_bluetooth_hci_uart_readchar()) >= 0) { - // printf("UART RX: %02x\n", data); - rx_cb(rx_arg, chr); - } - - if (host_wake) { - mp_bluetooth_hci_controller_sleep_maybe(); - } +uint32_t mp_bluetooth_nimble_hci_uart_enter_critical(void) { + MICROPY_PY_BLUETOOTH_ENTER + return atomic_state; } -void mp_bluetooth_nimble_hci_uart_tx_strn(const char *str, uint len) { - mp_bluetooth_hci_uart_write((const uint8_t *)str, len); +void mp_bluetooth_nimble_hci_uart_exit_critical(uint32_t atomic_state) { + MICROPY_PY_BLUETOOTH_EXIT } #endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE diff --git a/ports/stm32/mpnimbleport.h b/ports/stm32/mpnimbleport.h new file mode 100644 index 0000000000000..f3ee597183a04 --- /dev/null +++ b/ports/stm32/mpnimbleport.h @@ -0,0 +1,34 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jim Mussared + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MICROPY_INCLUDED_STM32_NIMBLE_PORT_H +#define MICROPY_INCLUDED_STM32_NIMBLE_PORT_H + +// To allow MICROPY_HW_BLE_UART_ID to be resolved. + +#include "uart.h" + +#endif // MICROPY_INCLUDED_STM32_NIMBLE_PORT_H diff --git a/ports/stm32/pin_defs_stm32.c b/ports/stm32/pin_defs_stm32.c index 0aef5f95fa294..a3f9d039d6320 100644 --- a/ports/stm32/pin_defs_stm32.c +++ b/ports/stm32/pin_defs_stm32.c @@ -8,9 +8,9 @@ uint32_t pin_get_mode(const pin_obj_t *pin) { GPIO_TypeDef *gpio = pin->gpio; uint32_t mode = (gpio->MODER >> (pin->pin * 2)) & 3; - if (mode != GPIO_MODE_ANALOG) { + if (mode == GPIO_MODE_OUTPUT_PP || mode == GPIO_MODE_AF_PP) { if (gpio->OTYPER & pin->pin_mask) { - mode |= 1 << 4; + mode |= 1 << 4; // Converts from xxx_PP to xxx_OD } } return mode; diff --git a/ports/stm32/powerctrl.c b/ports/stm32/powerctrl.c index e11de8b021fd9..946185fba09d3 100644 --- a/ports/stm32/powerctrl.c +++ b/ports/stm32/powerctrl.c @@ -201,7 +201,7 @@ int powerctrl_rcc_clock_config_pll(RCC_ClkInitTypeDef *rcc_init, uint32_t sysclk #endif -#if !defined(STM32F0) && !defined(STM32L0) && !defined(STM32L4) && !defined(STM32WB) +#if !defined(STM32F0) && !defined(STM32L0) && !defined(STM32L4) STATIC uint32_t calc_ahb_div(uint32_t wanted_div) { #if defined(STM32H7) @@ -293,6 +293,8 @@ STATIC uint32_t calc_apb2_div(uint32_t wanted_div) { #endif } +#if defined(STM32F4) || defined(STM32F7) || defined(STM32H7) + int powerctrl_set_sysclk(uint32_t sysclk, uint32_t ahb, uint32_t apb1, uint32_t apb2) { // Return straightaway if the clocks are already at the desired frequency if (sysclk == HAL_RCC_GetSysClockFreq() @@ -455,8 +457,36 @@ int powerctrl_set_sysclk(uint32_t sysclk, uint32_t ahb, uint32_t apb1, uint32_t return 0; } +#elif defined(STM32WB) + +int powerctrl_set_sysclk(uint32_t sysclk, uint32_t ahb, uint32_t apb1, uint32_t apb2) { + // For now it's not supported to change SYSCLK (only bus dividers). + if (sysclk != HAL_RCC_GetSysClockFreq()) { + return -MP_EINVAL; + } + + // Return straightaway if the clocks are already at the desired frequency. + if (ahb == HAL_RCC_GetHCLKFreq() + && apb1 == HAL_RCC_GetPCLK1Freq() + && apb2 == HAL_RCC_GetPCLK2Freq()) { + return 0; + } + + // Calculate and configure the bus clock dividers. + uint32_t cfgr = RCC->CFGR; + cfgr &= ~(7 << RCC_CFGR_PPRE2_Pos | 7 << RCC_CFGR_PPRE1_Pos | 0xf << RCC_CFGR_HPRE_Pos); + cfgr |= calc_ahb_div(sysclk / ahb); + cfgr |= calc_apb1_div(ahb / apb1); + cfgr |= calc_apb2_div(ahb / apb2) << (RCC_CFGR_PPRE2_Pos - RCC_CFGR_PPRE1_Pos); + RCC->CFGR = cfgr; + + return 0; +} + #endif +#endif // !defined(STM32F0) && !defined(STM32L0) && !defined(STM32L4) + void powerctrl_enter_stop_mode(void) { // Disable IRQs so that the IRQ that wakes the device from stop mode is not // executed until after the clocks are reconfigured diff --git a/ports/stm32/powerctrl.h b/ports/stm32/powerctrl.h index 6e5f899a4cd1b..9f223e794a984 100644 --- a/ports/stm32/powerctrl.h +++ b/ports/stm32/powerctrl.h @@ -26,6 +26,7 @@ #ifndef MICROPY_INCLUDED_STM32_POWERCTRL_H #define MICROPY_INCLUDED_STM32_POWERCTRL_H +#include #include void SystemClock_Config(void); diff --git a/ports/stm32/powerctrlboot.c b/ports/stm32/powerctrlboot.c index 749cb5505caaa..880e43e04c2a2 100644 --- a/ports/stm32/powerctrlboot.c +++ b/ports/stm32/powerctrlboot.c @@ -25,6 +25,7 @@ */ #include "py/mphal.h" +#include "irq.h" #include "powerctrl.h" static inline void powerctrl_config_systick(void) { @@ -153,12 +154,23 @@ void SystemClock_Config(void) { #elif defined(STM32WB) +#include "stm32wbxx_ll_hsem.h" + +// This semaphore protected access to the CLK48 configuration. +// CPU1 should hold this semaphore while the USB peripheral is in use. +// See AN5289 and https://github.com/micropython/micropython/issues/6316. +#define CLK48_SEMID (5) + void SystemClock_Config(void) { // Enable the 32MHz external oscillator RCC->CR |= RCC_CR_HSEON; while (!(RCC->CR & RCC_CR_HSERDY)) { } + // Prevent CPU2 from disabling CLK48. + while (LL_HSEM_1StepLock(HSEM, CLK48_SEMID)) { + } + // Use HSE and the PLL to get a 64MHz SYSCLK #define PLLM (HSE_VALUE / 8000000) // VCO input is 8MHz #define PLLN (24) // 24*8MHz = 192MHz diff --git a/ports/stm32/pyb_can.c b/ports/stm32/pyb_can.c index ad2efbef4be1a..224b8f28b0eab 100644 --- a/ports/stm32/pyb_can.c +++ b/ports/stm32/pyb_can.c @@ -432,6 +432,20 @@ STATIC mp_obj_t pyb_can_send(size_t n_args, const mp_obj_t *pos_args, mp_map_t * HAL_StatusTypeDef status; #if MICROPY_HW_ENABLE_FDCAN + uint32_t timeout_ms = args[ARG_timeout].u_int; + uint32_t start = HAL_GetTick(); + while (HAL_FDCAN_GetTxFifoFreeLevel(&self->can) == 0) { + if (timeout_ms == 0) { + mp_raise_OSError(MP_ETIMEDOUT); + } + // Check for the Timeout + if (timeout_ms != HAL_MAX_DELAY) { + if (HAL_GetTick() - start >= timeout_ms) { + mp_raise_OSError(MP_ETIMEDOUT); + } + } + MICROPY_EVENT_POLL_HOOK + } status = HAL_FDCAN_AddMessageToTxFifoQ(&self->can, &tx_msg, tx_data); #else self->can.pTxMsg = &tx_msg; diff --git a/ports/stm32/rfcore.c b/ports/stm32/rfcore.c index 602ef974b262b..1fc0c9531d03b 100644 --- a/ports/stm32/rfcore.c +++ b/ports/stm32/rfcore.c @@ -4,6 +4,7 @@ * The MIT License (MIT) * * Copyright (c) 2019 Damien P. George + * Copyright (c) 2020 Jim Mussared * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -29,26 +30,52 @@ #include "py/mperrno.h" #include "py/mphal.h" +#include "py/runtime.h" #include "rtc.h" #include "rfcore.h" #if defined(STM32WB) +#include "stm32wbxx_ll_ipcc.h" + +#define DEBUG_printf(...) // printf("rfcore: " __VA_ARGS__) + // Define to 1 to print traces of HCI packets #define HCI_TRACE (0) -#define IPCC_CH_BLE (0x01) // BLE HCI command and response -#define IPCC_CH_SYS (0x02) // system HCI command and response -#define IPCC_CH_MM (0x08) // release buffer -#define IPCC_CH_HCI_ACL (0x20) // HCI ACL outgoing data +#define IPCC_CH_BLE (LL_IPCC_CHANNEL_1) // BLE HCI command and response +#define IPCC_CH_SYS (LL_IPCC_CHANNEL_2) // system HCI command and response +#define IPCC_CH_MM (LL_IPCC_CHANNEL_4) // release buffer +#define IPCC_CH_HCI_ACL (LL_IPCC_CHANNEL_6) // HCI ACL outgoing data -#define OGF_VENDOR (0x3f) -#define OCF_WRITE_CONFIG (0x0c) -#define OCF_SET_TX_POWER (0x0f) -#define OCF_BLE_INIT (0x66) +#define OGF_CTLR_BASEBAND (0x03) +#define OCF_CB_RESET (0x03) +#define OCF_CB_SET_EVENT_MASK2 (0x63) + +#define OGF_VENDOR (0x3f) +#define OCF_WRITE_CONFIG (0x0c) +#define OCF_SET_TX_POWER (0x0f) +#define OCF_BLE_INIT (0x66) #define HCI_OPCODE(ogf, ocf) ((ogf) << 10 | (ocf)) +#define HCI_KIND_BT_CMD (0x01) // ...? +#define HCI_KIND_BT_ACL (0x02) // +#define HCI_KIND_BT_EVENT (0x04) // +#define HCI_KIND_VENDOR_RESPONSE (0x11) +#define HCI_KIND_VENDOR_EVENT (0x12) + +#define HCI_EVENT_COMMAND_COMPLETE (0x0E) // +#define HCI_EVENT_COMMAND_STATUS (0x0F) // + +#define SYS_ACK_TIMEOUT_MS (250) +#define BLE_ACK_TIMEOUT_MS (250) + +// AN5185 +#define MAGIC_FUS_ACTIVE 0xA94656B9 +// AN5289 +#define MAGIC_IPCC_MEM_INCORRECT 0x3DE96F61 + typedef struct _tl_list_node_t { volatile struct _tl_list_node_t *next; volatile struct _tl_list_node_t *prev; @@ -61,23 +88,111 @@ typedef struct _parse_hci_info_t { bool was_hci_reset_evt; } parse_hci_info_t; -static volatile uint32_t ipcc_mem_dev_info_tab[8]; -static volatile uint32_t ipcc_mem_ble_tab[4]; -static volatile uint32_t ipcc_mem_sys_tab[2]; -static volatile uint32_t ipcc_mem_memmgr_tab[7]; - -static volatile uint32_t ipcc_mem_sys_cmd_buf[272 / 4]; -static volatile tl_list_node_t ipcc_mem_sys_queue; - -static volatile tl_list_node_t ipcc_mem_memmgr_free_buf_queue; -static volatile uint32_t ipcc_mem_memmgr_ble_spare_evt_buf[272 / 4]; -static volatile uint32_t ipcc_mem_memmgr_sys_spare_evt_buf[272 / 4]; -static volatile uint32_t ipcc_mem_memmgr_evt_pool[6 * 272 / 4]; - -static volatile uint32_t ipcc_mem_ble_cmd_buf[272 / 4]; -static volatile uint32_t ipcc_mem_ble_cs_buf[272 / 4]; -static volatile tl_list_node_t ipcc_mem_ble_evt_queue; -static volatile uint32_t ipcc_mem_ble_hci_acl_data_buf[272 / 4]; +// Version +// [0:3] = Build - 0: Untracked - 15:Released - x: Tracked version +// [4:7] = branch - 0: Mass Market - x: ... +// [8:15] = Subversion +// [16:23] = Version minor +// [24:31] = Version major + +// Memory Size +// [0:7] = Flash (Number of 4k sectors) +// [8:15] = Reserved (Shall be set to 0 - may be used as flash extension) +// [16:23] = SRAM2b (Number of 1k sectors) +// [24:31] = SRAM2a (Number of 1k sectors) + +typedef union __attribute__((packed)) _ipcc_device_info_table_t { + struct { + uint32_t table_state; + uint8_t reserved0; + uint8_t last_fus_state; + uint8_t last_ws_state; + uint8_t ws_type; + uint32_t safeboot_version; + uint32_t fus_version; + uint32_t fus_memorysize; + uint32_t ws_version; + uint32_t ws_memorysize; + uint32_t ws_ble_info; + uint32_t ws_thread_info; + uint32_t reserved1; + uint64_t uid64; + uint16_t device_id; + uint16_t pad; + } fus; + struct { + uint32_t safeboot_version; + uint32_t fus_version; + uint32_t fus_memorysize; + uint32_t fus_info; + uint32_t fw_version; + uint32_t fw_memorysize; + uint32_t fw_infostack; + uint32_t fw_reserved; + } ws; +} ipcc_device_info_table_t; + +typedef struct __attribute__((packed)) _ipcc_ble_table_t { + uint8_t *pcmd_buffer; + uint8_t *pcs_buffer; + tl_list_node_t *pevt_queue; + uint8_t *phci_acl_data_buffer; +} ipcc_ble_table_t; + +// msg +// [0:7] = cmd/evt +// [8:31] = Reserved +typedef struct __attribute__((packed)) _ipcc_sys_table_t { + uint8_t *pcmd_buffer; + tl_list_node_t *sys_queue; +} ipcc_sys_table_t; + +typedef struct __attribute__((packed)) _ipcc_mem_manager_table_t { + uint8_t *spare_ble_buffer; + uint8_t *spare_sys_buffer; + uint8_t *blepool; + uint32_t blepoolsize; + tl_list_node_t *pevt_free_buffer_queue; + uint8_t *traces_evt_pool; + uint32_t tracespoolsize; +} ipcc_mem_manager_table_t; + +typedef struct __attribute__((packed)) _ipcc_ref_table_t { + ipcc_device_info_table_t *p_device_info_table; + ipcc_ble_table_t *p_ble_table; + void *p_thread_table; + ipcc_sys_table_t *p_sys_table; + ipcc_mem_manager_table_t *p_mem_manager_table; + void *p_traces_table; + void *p_mac_802_15_4_table; + void *p_zigbee_table; + void *p_lld_tests_table; + void *p_lld_ble_table; +} ipcc_ref_table_t; + +// The stm32wb55xg.ld script puts .bss.ipcc_mem_* into SRAM2A and .bss_ipcc_membuf_* into SRAM2B. +// It also leaves 64 bytes at the start of SRAM2A for the ref table. + +STATIC ipcc_device_info_table_t ipcc_mem_dev_info_tab; // mem1 +STATIC ipcc_ble_table_t ipcc_mem_ble_tab; // mem1 +STATIC ipcc_sys_table_t ipcc_mem_sys_tab; // mem1 +STATIC ipcc_mem_manager_table_t ipcc_mem_memmgr_tab; // mem1 + +STATIC uint8_t ipcc_membuf_sys_cmd_buf[272]; // mem2 +STATIC tl_list_node_t ipcc_mem_sys_queue; // mem1 + +STATIC tl_list_node_t ipcc_mem_memmgr_free_buf_queue; // mem1 +STATIC uint8_t ipcc_membuf_memmgr_ble_spare_evt_buf[272]; // mem2 +STATIC uint8_t ipcc_membuf_memmgr_sys_spare_evt_buf[272]; // mem2 +STATIC uint8_t ipcc_membuf_memmgr_evt_pool[6 * 272]; // mem2 + +STATIC uint8_t ipcc_membuf_ble_cmd_buf[272]; // mem2 +STATIC uint8_t ipcc_membuf_ble_cs_buf[272]; // mem2 +STATIC tl_list_node_t ipcc_mem_ble_evt_queue; // mem1 +STATIC uint8_t ipcc_membuf_ble_hci_acl_data_buf[272]; // mem2 + +// Set by the RX IRQ handler on incoming HCI payload. +STATIC volatile bool had_ble_irq = false; /******************************************************************************/ // Transport layer linked list @@ -105,173 +220,212 @@ STATIC void tl_list_append(volatile tl_list_node_t *head, volatile tl_list_node_ /******************************************************************************/ // IPCC interface -STATIC uint32_t get_ipccdba(void) { - return *(uint32_t *)(OPTION_BYTE_BASE + 0x68) & 0x3fff; -} - -STATIC volatile void **get_buffer_table(void) { - return (volatile void **)(SRAM2A_BASE + get_ipccdba()); +STATIC volatile ipcc_ref_table_t *get_buffer_table(void) { + // The IPCCDBA option bytes must not be changed without + // making a corresponding change to the linker script. + return (volatile ipcc_ref_table_t *)(SRAM2A_BASE + LL_FLASH_GetIPCCBufferAddr() * 4); } void ipcc_init(uint32_t irq_pri) { + DEBUG_printf("ipcc_init\n"); + // Setup buffer table pointers - volatile void **tab = get_buffer_table(); - tab[0] = &ipcc_mem_dev_info_tab[0]; - tab[1] = &ipcc_mem_ble_tab[0]; - tab[3] = &ipcc_mem_sys_tab[0]; - tab[4] = &ipcc_mem_memmgr_tab[0]; + volatile ipcc_ref_table_t *tab = get_buffer_table(); + tab->p_device_info_table = &ipcc_mem_dev_info_tab; + tab->p_ble_table = &ipcc_mem_ble_tab; + tab->p_sys_table = &ipcc_mem_sys_tab; + tab->p_mem_manager_table = &ipcc_mem_memmgr_tab; // Start IPCC peripheral __HAL_RCC_IPCC_CLK_ENABLE(); - // Enable wanted IRQs - IPCC->C1CR = 0; // IPCC_C1CR_RXOIE; - IPCC->C1MR = 0xffffffff; + // Enable receive IRQ on the BLE channel. + LL_C1_IPCC_EnableIT_RXO(IPCC); + LL_C1_IPCC_DisableReceiveChannel(IPCC, LL_IPCC_CHANNEL_1 | LL_IPCC_CHANNEL_2 | LL_IPCC_CHANNEL_3 | LL_IPCC_CHANNEL_4 | LL_IPCC_CHANNEL_5 | LL_IPCC_CHANNEL_6); + LL_C1_IPCC_EnableReceiveChannel(IPCC, IPCC_CH_BLE); NVIC_SetPriority(IPCC_C1_RX_IRQn, irq_pri); HAL_NVIC_EnableIRQ(IPCC_C1_RX_IRQn); - // Device info table will be populated by FUS/WS + // Device info table will be populated by FUS/WS on CPU2 boot. // Populate system table tl_list_init(&ipcc_mem_sys_queue); - ipcc_mem_sys_tab[0] = (uint32_t)&ipcc_mem_sys_cmd_buf[0]; - ipcc_mem_sys_tab[1] = (uint32_t)&ipcc_mem_sys_queue; + ipcc_mem_sys_tab.pcmd_buffer = ipcc_membuf_sys_cmd_buf; + ipcc_mem_sys_tab.sys_queue = &ipcc_mem_sys_queue; // Populate memory manager table tl_list_init(&ipcc_mem_memmgr_free_buf_queue); - ipcc_mem_memmgr_tab[0] = (uint32_t)&ipcc_mem_memmgr_ble_spare_evt_buf[0]; - ipcc_mem_memmgr_tab[1] = (uint32_t)&ipcc_mem_memmgr_sys_spare_evt_buf[0]; - ipcc_mem_memmgr_tab[2] = (uint32_t)&ipcc_mem_memmgr_evt_pool[0]; - ipcc_mem_memmgr_tab[3] = sizeof(ipcc_mem_memmgr_evt_pool); - ipcc_mem_memmgr_tab[4] = (uint32_t)&ipcc_mem_memmgr_free_buf_queue; - ipcc_mem_memmgr_tab[5] = 0; - ipcc_mem_memmgr_tab[6] = 0; + ipcc_mem_memmgr_tab.spare_ble_buffer = ipcc_membuf_memmgr_ble_spare_evt_buf; + ipcc_mem_memmgr_tab.spare_sys_buffer = ipcc_membuf_memmgr_sys_spare_evt_buf; + ipcc_mem_memmgr_tab.blepool = ipcc_membuf_memmgr_evt_pool; + ipcc_mem_memmgr_tab.blepoolsize = sizeof(ipcc_membuf_memmgr_evt_pool); + ipcc_mem_memmgr_tab.pevt_free_buffer_queue = &ipcc_mem_memmgr_free_buf_queue; + ipcc_mem_memmgr_tab.traces_evt_pool = NULL; + ipcc_mem_memmgr_tab.tracespoolsize = 0; // Populate BLE table tl_list_init(&ipcc_mem_ble_evt_queue); - ipcc_mem_ble_tab[0] = (uint32_t)&ipcc_mem_ble_cmd_buf[0]; - ipcc_mem_ble_tab[1] = (uint32_t)&ipcc_mem_ble_cs_buf[0]; - ipcc_mem_ble_tab[2] = (uint32_t)&ipcc_mem_ble_evt_queue; - ipcc_mem_ble_tab[3] = (uint32_t)&ipcc_mem_ble_hci_acl_data_buf[0]; -} - -STATIC int ipcc_wait_ack(unsigned int ch, uint32_t timeout_ms) { - uint32_t t0 = mp_hal_ticks_ms(); - while (IPCC->C1TOC2SR & ch) { - if (mp_hal_ticks_ms() - t0 > timeout_ms) { - printf("ipcc_wait_ack: timeout\n"); - return -MP_ETIMEDOUT; - } - } - // C2 cleared IPCC flag - return 0; -} - -STATIC int ipcc_wait_msg(unsigned int ch, uint32_t timeout_ms) { - uint32_t t0 = mp_hal_ticks_ms(); - while (!(IPCC->C2TOC1SR & ch)) { - if (mp_hal_ticks_ms() - t0 > timeout_ms) { - printf("ipcc_wait_msg: timeout\n"); - return -MP_ETIMEDOUT; - } - } - // C2 set IPCC flag - return 0; + ipcc_mem_ble_tab.pcmd_buffer = ipcc_membuf_ble_cmd_buf; + ipcc_mem_ble_tab.pcs_buffer = ipcc_membuf_ble_cs_buf; + ipcc_mem_ble_tab.pevt_queue = &ipcc_mem_ble_evt_queue; + ipcc_mem_ble_tab.phci_acl_data_buffer = ipcc_membuf_ble_hci_acl_data_buf; } /******************************************************************************/ // Transport layer HCI interface -STATIC void tl_parse_hci_msg(const uint8_t *buf, parse_hci_info_t *parse) { - const char *kind; - size_t len = 3 + buf[2]; +// The WS firmware doesn't support OCF_CB_SET_EVENT_MASK2, and fails with: +// v1.8.0.0.4 (and below): HCI_EVENT_COMMAND_COMPLETE with a non-zero status +// v1.9.0.0.4 (and above): HCI_EVENT_COMMAND_STATUS with a non-zero status +// In either case we detect the failure response and inject this response +// instead (which is HCI_EVENT_COMMAND_COMPLETE for OCF_CB_SET_EVENT_MASK2 +// with status=0). +STATIC const uint8_t set_event_event_mask2_fix_payload[] = { 0x04, 0x0e, 0x04, 0x01, 0x63, 0x0c, 0x00 }; + +STATIC size_t tl_parse_hci_msg(const uint8_t *buf, parse_hci_info_t *parse) { + const char *info; + #if HCI_TRACE + int applied_set_event_event_mask2_fix = 0; + #endif + size_t len; switch (buf[0]) { - case 0x02: { - // Standard BT HCI ACL packet - kind = "HCI_ACL"; + case HCI_KIND_BT_ACL: { + info = "HCI_ACL"; + + len = 5 + buf[3] + (buf[4] << 8); if (parse != NULL) { parse->cb_fun(parse->cb_env, buf, len); } break; } - case 0x04: { - // Standard BT HCI event packet - kind = "HCI_EVT"; + case HCI_KIND_BT_EVENT: { + info = "HCI_EVT"; + + len = 3 + buf[2]; if (parse != NULL) { - bool fix = false; - if (buf[1] == 0x0e && len == 7 && buf[3] == 0x01 && buf[4] == 0x63 && buf[5] == 0x0c && buf[6] == 0x01) { - len -= 1; - fix = true; + + if (buf[1] == HCI_EVENT_COMMAND_COMPLETE && len == 7) { + uint16_t opcode = (buf[5] << 8) | buf[4]; + uint8_t status = buf[6]; + + if (opcode == HCI_OPCODE(OGF_CTLR_BASEBAND, OCF_CB_SET_EVENT_MASK2) && status != 0) { + // For WS firmware v1.8.0.0.4 and below. Reply with the "everything OK" payload. + parse->cb_fun(parse->cb_env, set_event_event_mask2_fix_payload, sizeof(set_event_event_mask2_fix_payload)); + #if HCI_TRACE + applied_set_event_event_mask2_fix = 18; + #endif + break; // Don't send the original payload. + } + + if (opcode == HCI_OPCODE(OGF_CTLR_BASEBAND, OCF_CB_RESET) && status == 0) { + // Controller acknowledged reset command. + // This will trigger setting the MAC address. + parse->was_hci_reset_evt = true; + } } - parse->cb_fun(parse->cb_env, buf, len); - if (fix) { - len += 1; - uint8_t data = 0x00; // success - parse->cb_fun(parse->cb_env, &data, 1); + + if (buf[1] == HCI_EVENT_COMMAND_STATUS && len == 7) { + uint16_t opcode = (buf[6] << 8) | buf[5]; + uint8_t status = buf[3]; + + if (opcode == HCI_OPCODE(OGF_CTLR_BASEBAND, OCF_CB_SET_EVENT_MASK2) && status != 0) { + // For WS firmware v1.9.0.0.4 and higher. Reply with the "everything OK" payload. + parse->cb_fun(parse->cb_env, set_event_event_mask2_fix_payload, sizeof(set_event_event_mask2_fix_payload)); + #if HCI_TRACE + applied_set_event_event_mask2_fix = 19; + #endif + break; // Don't send the original payload. + } } - // Check for successful HCI_Reset event - parse->was_hci_reset_evt = buf[1] == 0x0e && buf[2] == 0x04 && buf[3] == 0x01 - && buf[4] == 0x03 && buf[5] == 0x0c && buf[6] == 0x00; + + parse->cb_fun(parse->cb_env, buf, len); } break; } - case 0x11: { - // Response packet + case HCI_KIND_VENDOR_RESPONSE: { // assert(buf[1] == 0x0e); - kind = "VEND_RESP"; + info = "VEND_RESP"; + len = 3 + buf[2]; // ??? // uint16_t cmd = buf[4] | buf[5] << 8; // uint8_t status = buf[6]; break; } - case 0x12: { - // Event packet + case HCI_KIND_VENDOR_EVENT: { // assert(buf[1] == 0xff); - kind = "VEND_EVT"; + info = "VEND_EVT"; + len = 3 + buf[2]; // ??? // uint16_t evt = buf[3] | buf[4] << 8; break; } default: - kind = "HCI_UNKNOWN"; + info = "HCI_UNKNOWN"; + len = 0; break; } #if HCI_TRACE - printf("[% 8d] %s(%02x", mp_hal_ticks_ms(), kind, buf[0]); + printf("[% 8d] <%s(%02x", mp_hal_ticks_ms(), info, buf[0]); for (int i = 1; i < len; ++i) { printf(":%02x", buf[i]); } - printf(")\n"); + printf(")"); + if (parse && parse->was_hci_reset_evt) { + printf(" (reset)"); + } + if (applied_set_event_event_mask2_fix) { + printf(" (mask2 fix %d)", applied_set_event_event_mask2_fix); + } + printf("\n"); + #else - (void)kind; + (void)info; #endif + + return len; } -STATIC void tl_check_msg(volatile tl_list_node_t *head, unsigned int ch, parse_hci_info_t *parse) { - if (IPCC->C2TOC1SR & ch) { - // Message available on CH2 - volatile tl_list_node_t *cur = head->next; - bool free = false; - while (cur != head) { - tl_parse_hci_msg((uint8_t *)cur->body, parse); - volatile tl_list_node_t *next = tl_list_unlink(cur); - if ((void *)&ipcc_mem_memmgr_evt_pool[0] <= (void *)cur - && (void *)cur < (void *)&ipcc_mem_memmgr_evt_pool[MP_ARRAY_SIZE(ipcc_mem_memmgr_evt_pool)]) { - // Place memory back in free pool - tl_list_append(&ipcc_mem_memmgr_free_buf_queue, cur); - free = true; - } - cur = next; - } - if (free) { - // Notify change in free pool - IPCC->C1SCR = IPCC_CH_MM << 16; +STATIC void tl_process_msg(volatile tl_list_node_t *head, unsigned int ch, parse_hci_info_t *parse) { + volatile tl_list_node_t *cur = head->next; + bool added_to_free_queue = false; + while (cur != head) { + tl_parse_hci_msg((uint8_t *)cur->body, parse); + + volatile tl_list_node_t *next = tl_list_unlink(cur); + + // If this node is allocated from the memmgr event pool, then place it into the free buffer. + if ((uint8_t *)cur >= ipcc_membuf_memmgr_evt_pool && (uint8_t *)cur < ipcc_membuf_memmgr_evt_pool + sizeof(ipcc_membuf_memmgr_evt_pool)) { + // Place memory back in free pool. + tl_list_append(&ipcc_mem_memmgr_free_buf_queue, cur); + added_to_free_queue = true; } - // Clear receive channel - IPCC->C1SCR = ch; + + cur = next; + } + + if (added_to_free_queue) { + // Notify change in free pool. + LL_C1_IPCC_SetFlag_CHx(IPCC, IPCC_CH_MM); + } +} + +STATIC void tl_check_msg(volatile tl_list_node_t *head, unsigned int ch, parse_hci_info_t *parse) { + if (LL_C2_IPCC_IsActiveFlag_CHx(IPCC, ch)) { + tl_process_msg(head, ch, parse); + + // Clear receive channel. + LL_C1_IPCC_ClearFlag_CHx(IPCC, ch); } } -STATIC void tl_hci_cmd(uint8_t *cmd, unsigned int ch, uint8_t hdr, uint16_t opcode, size_t len, const uint8_t *buf) { +STATIC void tl_check_msg_ble(volatile tl_list_node_t *head, parse_hci_info_t *parse) { + if (had_ble_irq) { + tl_process_msg(head, IPCC_CH_BLE, parse); + + had_ble_irq = false; + } +} + +STATIC void tl_hci_cmd(uint8_t *cmd, unsigned int ch, uint8_t hdr, uint16_t opcode, const uint8_t *buf, size_t len) { tl_list_node_t *n = (tl_list_node_t *)cmd; n->next = n; n->prev = n; @@ -280,31 +434,68 @@ STATIC void tl_hci_cmd(uint8_t *cmd, unsigned int ch, uint8_t hdr, uint16_t opco cmd[10] = opcode >> 8; cmd[11] = len; memcpy(&cmd[12], buf, len); - // IPCC indicate - IPCC->C1SCR = ch << 16; + + #if HCI_TRACE + printf("[% 8d] >HCI(", mp_hal_ticks_ms()); + for (int i = 0; i < len + 4; ++i) { + printf(":%02x", cmd[i + 8]); + } + printf(")\n"); + #endif + + // Indicate that this channel is ready. + LL_C1_IPCC_SetFlag_CHx(IPCC, ch); } -STATIC void tl_sys_wait_resp(const uint8_t *buf, unsigned int ch) { - if (ipcc_wait_ack(ch, 250) == 0) { - tl_parse_hci_msg(buf, NULL); +STATIC ssize_t tl_sys_wait_ack(const uint8_t *buf, mp_int_t timeout_ms) { + uint32_t t0 = mp_hal_ticks_ms(); + + timeout_ms = MAX(SYS_ACK_TIMEOUT_MS, timeout_ms); + + // C2 will clear this bit to acknowledge the request. + while (LL_C1_IPCC_IsActiveFlag_CHx(IPCC, IPCC_CH_SYS)) { + if (mp_hal_ticks_ms() - t0 > timeout_ms) { + printf("tl_sys_wait_ack: timeout\n"); + return -MP_ETIMEDOUT; + } } + + // C1-to-C2 bit cleared, so process the response (just get the length, do + // not parse any further). + return (ssize_t)tl_parse_hci_msg(buf, NULL); } -STATIC void tl_sys_hci_cmd_resp(uint16_t opcode, size_t len, const uint8_t *buf) { - tl_hci_cmd((uint8_t *)&ipcc_mem_sys_cmd_buf, IPCC_CH_SYS, 0x10, opcode, len, buf); - tl_sys_wait_resp((uint8_t *)&ipcc_mem_sys_cmd_buf, IPCC_CH_SYS); +STATIC ssize_t tl_sys_hci_cmd_resp(uint16_t opcode, const uint8_t *buf, size_t len, mp_int_t timeout_ms) { + tl_hci_cmd(ipcc_membuf_sys_cmd_buf, IPCC_CH_SYS, 0x10, opcode, buf, len); + return tl_sys_wait_ack(ipcc_membuf_sys_cmd_buf, timeout_ms); +} + +STATIC int tl_ble_wait_resp(void) { + uint32_t t0 = mp_hal_ticks_ms(); + while (!had_ble_irq) { + if (mp_hal_ticks_ms() - t0 > BLE_ACK_TIMEOUT_MS) { + printf("tl_ble_wait_resp: timeout\n"); + return -MP_ETIMEDOUT; + } + } + + // C2 set IPCC flag. + tl_check_msg_ble(&ipcc_mem_ble_evt_queue, NULL); + return 0; } -STATIC void tl_ble_hci_cmd_resp(uint16_t opcode, size_t len, const uint8_t *buf) { - tl_hci_cmd((uint8_t *)&ipcc_mem_ble_cmd_buf[0], IPCC_CH_BLE, 0x01, opcode, len, buf); - ipcc_wait_msg(IPCC_CH_BLE, 250); - tl_check_msg(&ipcc_mem_ble_evt_queue, IPCC_CH_BLE, NULL); +// Synchronously send a BLE command. +STATIC void tl_ble_hci_cmd_resp(uint16_t opcode, const uint8_t *buf, size_t len) { + tl_hci_cmd(ipcc_membuf_ble_cmd_buf, IPCC_CH_BLE, HCI_KIND_BT_CMD, opcode, buf, len); + tl_ble_wait_resp(); } /******************************************************************************/ // RF core interface void rfcore_init(void) { + DEBUG_printf("rfcore_init\n"); + // Ensure LSE is running rtc_init_finalise(); @@ -361,18 +552,22 @@ static const struct { }; void rfcore_ble_init(void) { + DEBUG_printf("rfcore_ble_init\n"); + // Clear any outstanding messages from ipcc_init tl_check_msg(&ipcc_mem_sys_queue, IPCC_CH_SYS, NULL); - tl_check_msg(&ipcc_mem_ble_evt_queue, IPCC_CH_BLE, NULL); + tl_check_msg_ble(&ipcc_mem_ble_evt_queue, NULL); // Configure and reset the BLE controller - tl_sys_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_BLE_INIT), sizeof(ble_init_params), (const uint8_t *)&ble_init_params); - tl_ble_hci_cmd_resp(HCI_OPCODE(0x03, 0x0003), 0, NULL); + tl_sys_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_BLE_INIT), (const uint8_t *)&ble_init_params, sizeof(ble_init_params), 0); + tl_ble_hci_cmd_resp(HCI_OPCODE(0x03, 0x0003), NULL, 0); } void rfcore_ble_hci_cmd(size_t len, const uint8_t *src) { + DEBUG_printf("rfcore_ble_hci_cmd\n"); + #if HCI_TRACE - printf("[% 8d] HCI_CMD(%02x", mp_hal_ticks_ms(), src[0]); + printf("[% 8d] >HCI_CMD(%02x", mp_hal_ticks_ms(), src[0]); for (int i = 1; i < len; ++i) { printf(":%02x", src[i]); } @@ -381,11 +576,11 @@ void rfcore_ble_hci_cmd(size_t len, const uint8_t *src) { tl_list_node_t *n; uint32_t ch; - if (src[0] == 0x01) { - n = (tl_list_node_t *)&ipcc_mem_ble_cmd_buf[0]; + if (src[0] == HCI_KIND_BT_CMD) { + n = (tl_list_node_t *)&ipcc_membuf_ble_cmd_buf[0]; ch = IPCC_CH_BLE; - } else if (src[0] == 0x02) { - n = (tl_list_node_t *)&ipcc_mem_ble_hci_acl_data_buf[0]; + } else if (src[0] == HCI_KIND_BT_ACL) { + n = (tl_list_node_t *)&ipcc_membuf_ble_hci_acl_data_buf[0]; ch = IPCC_CH_HCI_ACL; } else { printf("** UNEXPECTED HCI HDR: 0x%02x **\n", src[0]); @@ -396,13 +591,13 @@ void rfcore_ble_hci_cmd(size_t len, const uint8_t *src) { n->prev = n; memcpy(n->body, src, len); - // IPCC indicate - IPCC->C1SCR = ch << 16; + // IPCC indicate. + LL_C1_IPCC_SetFlag_CHx(IPCC, ch); } void rfcore_ble_check_msg(int (*cb)(void *, const uint8_t *, size_t), void *env) { parse_hci_info_t parse = { cb, env, false }; - tl_check_msg(&ipcc_mem_ble_evt_queue, IPCC_CH_BLE, &parse); + tl_check_msg_ble(&ipcc_mem_ble_evt_queue, &parse); // Intercept HCI_Reset events and reconfigure the controller following the reset if (parse.was_hci_reset_evt) { @@ -415,9 +610,89 @@ void rfcore_ble_check_msg(int (*cb)(void *, const uint8_t *, size_t), void *env) SWAP_UINT8(buf[2], buf[7]); SWAP_UINT8(buf[3], buf[6]); SWAP_UINT8(buf[4], buf[5]); - tl_ble_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_WRITE_CONFIG), 8, buf); // set BDADDR - tl_ble_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_SET_TX_POWER), 2, (const uint8_t *)"\x00\x06"); // 0 dBm + tl_ble_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_WRITE_CONFIG), buf, 8); // set BDADDR + } +} + +// "level" is 0x00-0x1f, ranging from -40 dBm to +6 dBm (not linear). +void rfcore_ble_set_txpower(uint8_t level) { + uint8_t buf[2] = { 0x00, level }; + tl_ble_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_SET_TX_POWER), buf, 2); +} + +// IPCC IRQ Handlers +void IPCC_C1_TX_IRQHandler(void) { + IRQ_ENTER(IPCC_C1_TX_IRQn); + IRQ_EXIT(IPCC_C1_TX_IRQn); +} + +void IPCC_C1_RX_IRQHandler(void) { + IRQ_ENTER(IPCC_C1_RX_IRQn); + + if (LL_C2_IPCC_IsActiveFlag_CHx(IPCC, IPCC_CH_BLE)) { + had_ble_irq = true; + + LL_C1_IPCC_ClearFlag_CHx(IPCC, IPCC_CH_BLE); + + // Schedule PENDSV to process incoming HCI payload. + extern void mp_bluetooth_hci_poll_wrapper(uint32_t ticks_ms); + mp_bluetooth_hci_poll_wrapper(0); + } + + IRQ_EXIT(IPCC_C1_RX_IRQn); +} + +/******************************************************************************/ +// MicroPython bindings + +STATIC mp_obj_t rfcore_status(void) { + return mp_obj_new_int_from_uint(ipcc_mem_dev_info_tab.fus.table_state); +} +MP_DEFINE_CONST_FUN_OBJ_0(rfcore_status_obj, rfcore_status); + +STATIC mp_obj_t get_version_tuple(uint32_t data) { + mp_obj_t items[] = { + MP_OBJ_NEW_SMALL_INT(data >> 24), MP_OBJ_NEW_SMALL_INT(data >> 16 & 0xFF), MP_OBJ_NEW_SMALL_INT(data >> 8 & 0xFF), MP_OBJ_NEW_SMALL_INT(data >> 4 & 0xF), MP_OBJ_NEW_SMALL_INT(data & 0xF) + }; + return mp_obj_new_tuple(5, items); +} + +STATIC mp_obj_t rfcore_fw_version(mp_obj_t fw_id_in) { + if (ipcc_mem_dev_info_tab.fus.table_state == MAGIC_IPCC_MEM_INCORRECT) { + mp_raise_OSError(MP_EINVAL); + } + mp_int_t fw_id = mp_obj_get_int(fw_id_in); + bool fus_active = ipcc_mem_dev_info_tab.fus.table_state == MAGIC_FUS_ACTIVE; + uint32_t v; + if (fw_id == 0) { + // FUS + v = fus_active ? ipcc_mem_dev_info_tab.fus.fus_version : ipcc_mem_dev_info_tab.ws.fus_version; + } else { + // WS + v = fus_active ? ipcc_mem_dev_info_tab.fus.ws_version : ipcc_mem_dev_info_tab.ws.fw_version; + } + return get_version_tuple(v); +} +MP_DEFINE_CONST_FUN_OBJ_1(rfcore_fw_version_obj, rfcore_fw_version); + +STATIC mp_obj_t rfcore_sys_hci(size_t n_args, const mp_obj_t *args) { + if (ipcc_mem_dev_info_tab.fus.table_state == MAGIC_IPCC_MEM_INCORRECT) { + mp_raise_OSError(MP_EINVAL); + } + mp_int_t ogf = mp_obj_get_int(args[0]); + mp_int_t ocf = mp_obj_get_int(args[1]); + mp_buffer_info_t bufinfo = {0}; + mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_READ); + mp_int_t timeout_ms = 0; + if (n_args >= 4) { + timeout_ms = mp_obj_get_int(args[3]); + } + ssize_t len = tl_sys_hci_cmd_resp(HCI_OPCODE(ogf, ocf), bufinfo.buf, bufinfo.len, timeout_ms); + if (len < 0) { + mp_raise_OSError(-len); } + return mp_obj_new_bytes(ipcc_membuf_sys_cmd_buf, len); } +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(rfcore_sys_hci_obj, 3, 4, rfcore_sys_hci); #endif // defined(STM32WB) diff --git a/ports/stm32/rfcore.h b/ports/stm32/rfcore.h index 138c438f12eae..6a3c85f67dfe8 100644 --- a/ports/stm32/rfcore.h +++ b/ports/stm32/rfcore.h @@ -33,5 +33,10 @@ void rfcore_init(void); void rfcore_ble_init(void); void rfcore_ble_hci_cmd(size_t len, const uint8_t *src); void rfcore_ble_check_msg(int (*cb)(void *, const uint8_t *, size_t), void *env); +void rfcore_ble_set_txpower(uint8_t level); + +MP_DECLARE_CONST_FUN_OBJ_0(rfcore_status_obj); +MP_DECLARE_CONST_FUN_OBJ_1(rfcore_fw_version_obj); +MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(rfcore_sys_hci_obj); #endif // MICROPY_INCLUDED_STM32_RFCORE_H diff --git a/ports/stm32/rtc.c b/ports/stm32/rtc.c index de26352004771..18ecf7750513b 100644 --- a/ports/stm32/rtc.c +++ b/ports/stm32/rtc.c @@ -27,6 +27,7 @@ #include #include "py/runtime.h" +#include "lib/timeutils/timeutils.h" #include "extint.h" #include "rtc.h" #include "irq.h" @@ -442,6 +443,23 @@ STATIC void RTC_CalendarConfig(void) { } } +uint64_t mp_hal_time_ns(void) { + uint64_t ns = 0; + #if MICROPY_HW_ENABLE_RTC + // Get current according to the RTC. + rtc_init_finalise(); + RTC_TimeTypeDef time; + RTC_DateTypeDef date; + HAL_RTC_GetTime(&RTCHandle, &time, RTC_FORMAT_BIN); + HAL_RTC_GetDate(&RTCHandle, &date, RTC_FORMAT_BIN); + ns = timeutils_seconds_since_epoch(2000 + date.Year, date.Month, date.Date, time.Hours, time.Minutes, time.Seconds); + ns *= 1000000000ULL; + uint32_t usec = ((RTC_SYNCH_PREDIV - time.SubSeconds) * (1000000 / 64)) / ((RTC_SYNCH_PREDIV + 1) / 64); + ns += usec * 1000; + #endif + return ns; +} + /******************************************************************************/ // MicroPython bindings diff --git a/ports/stm32/rtc.h b/ports/stm32/rtc.h index d3840c1b73592..5c454b3e3937f 100644 --- a/ports/stm32/rtc.h +++ b/ports/stm32/rtc.h @@ -26,6 +26,8 @@ #ifndef MICROPY_INCLUDED_STM32_RTC_H #define MICROPY_INCLUDED_STM32_RTC_H +#include "py/obj.h" + extern RTC_HandleTypeDef RTCHandle; extern const mp_obj_type_t pyb_rtc_type; diff --git a/ports/stm32/sdcard.c b/ports/stm32/sdcard.c index 9d77722302c99..7d79e9f47b1a8 100644 --- a/ports/stm32/sdcard.c +++ b/ports/stm32/sdcard.c @@ -48,12 +48,14 @@ #if defined(MICROPY_HW_SDMMC2_CK) #define SDIO SDMMC2 +#define SDMMC_IRQHandler SDMMC2_IRQHandler #define SDMMC_CLK_ENABLE() __HAL_RCC_SDMMC2_CLK_ENABLE() #define SDMMC_CLK_DISABLE() __HAL_RCC_SDMMC2_CLK_DISABLE() #define SDMMC_IRQn SDMMC2_IRQn #define SDMMC_DMA dma_SDMMC_2 #else #define SDIO SDMMC1 +#define SDMMC_IRQHandler SDMMC1_IRQHandler #define SDMMC_CLK_ENABLE() __HAL_RCC_SDMMC1_CLK_ENABLE() #define SDMMC_CLK_DISABLE() __HAL_RCC_SDMMC1_CLK_DISABLE() #define SDMMC_IRQn SDMMC1_IRQn @@ -86,8 +88,6 @@ #define SDIO_HARDWARE_FLOW_CONTROL_ENABLE SDMMC_HARDWARE_FLOW_CONTROL_ENABLE #if defined(STM32H7) -#define GPIO_AF12_SDIO GPIO_AF12_SDIO1 -#define SDIO_IRQHandler SDMMC1_IRQHandler #define SDIO_TRANSFER_CLK_DIV SDMMC_NSpeed_CLK_DIV #define SDIO_USE_GPDMA 0 #else @@ -102,6 +102,7 @@ #define SDMMC_CLK_ENABLE() __SDIO_CLK_ENABLE() #define SDMMC_CLK_DISABLE() __SDIO_CLK_DISABLE() #define SDMMC_IRQn SDIO_IRQn +#define SDMMC_IRQHandler SDIO_IRQHandler #define SDMMC_DMA dma_SDIO_0 #define SDIO_USE_GPDMA 1 #define STATIC_AF_SDMMC_CK STATIC_AF_SDIO_CK @@ -398,21 +399,11 @@ STATIC void sdmmc_irq_handler(void) { } } -#if !defined(MICROPY_HW_SDMMC2_CK) -void SDIO_IRQHandler(void) { - IRQ_ENTER(SDIO_IRQn); +void SDMMC_IRQHandler(void) { + IRQ_ENTER(SDMMC_IRQn); sdmmc_irq_handler(); - IRQ_EXIT(SDIO_IRQn); + IRQ_EXIT(SDMMC_IRQn); } -#endif - -#if defined(STM32F7) -void SDMMC2_IRQHandler(void) { - IRQ_ENTER(SDMMC2_IRQn); - sdmmc_irq_handler(); - IRQ_EXIT(SDMMC2_IRQn); -} -#endif STATIC void sdcard_reset_periph(void) { // Fully reset the SDMMC peripheral before calling HAL SD DMA functions. diff --git a/ports/stm32/sdio.c b/ports/stm32/sdio.c index 79cc9c9a323a9..87f550e3a6f5d 100644 --- a/ports/stm32/sdio.c +++ b/ports/stm32/sdio.c @@ -76,7 +76,9 @@ void sdio_init(uint32_t irq_pri) { #endif mp_hal_delay_us(10); + #if defined(STM32F7) __HAL_RCC_DMA2_CLK_ENABLE(); // enable DMA2 peripheral + #endif NVIC_SetPriority(SDMMC1_IRQn, irq_pri); @@ -216,7 +218,9 @@ int sdio_transfer(uint32_t cmd, uint32_t arg, uint32_t *resp) { } #endif + #if defined(STM32F7) DMA2_Stream3->CR = 0; // ensure DMA is reset + #endif SDMMC1->ICR = SDMMC_STATIC_FLAGS; // clear interrupts SDMMC1->ARG = arg; SDMMC1->CMD = cmd | SDMMC_CMD_WAITRESP_0 | SDMMC_CMD_CPSMEN; @@ -296,7 +300,9 @@ int sdio_transfer_cmd53(bool write, uint32_t block_size, uint32_t arg, size_t le SDMMC1->DTIMER = 0x2000000; // about 700ms running at 48MHz SDMMC1->DLEN = (len + block_size - 1) & ~(block_size - 1); + #if defined(STM32F7) DMA2_Stream3->CR = 0; + #endif if (dma) { // prepare DMA so it's ready when the DPSM starts its transfer diff --git a/ports/stm32/servo.c b/ports/stm32/servo.c index a347c6bbd06d4..17084224637ec 100644 --- a/ports/stm32/servo.c +++ b/ports/stm32/servo.c @@ -278,7 +278,7 @@ STATIC mp_obj_t pyb_servo_angle(size_t n_args, const mp_obj_t *args) { return mp_obj_new_int((self->pulse_cur - self->pulse_centre) * 90 / self->pulse_angle_90); } else { #if MICROPY_PY_BUILTINS_FLOAT - self->pulse_dest = self->pulse_centre + (uint16_t)((mp_float_t)self->pulse_angle_90 * mp_obj_get_float(args[1]) / MICROPY_FLOAT_CONST(90.0)); + self->pulse_dest = self->pulse_centre + (int16_t)((mp_float_t)self->pulse_angle_90 * mp_obj_get_float(args[1]) / MICROPY_FLOAT_CONST(90.0)); #else self->pulse_dest = self->pulse_centre + self->pulse_angle_90 * mp_obj_get_int(args[1]) / 90; #endif @@ -308,7 +308,7 @@ STATIC mp_obj_t pyb_servo_speed(size_t n_args, const mp_obj_t *args) { return mp_obj_new_int((self->pulse_cur - self->pulse_centre) * 100 / self->pulse_speed_100); } else { #if MICROPY_PY_BUILTINS_FLOAT - self->pulse_dest = self->pulse_centre + (uint16_t)((mp_float_t)self->pulse_speed_100 * mp_obj_get_float(args[1]) / MICROPY_FLOAT_CONST(100.0)); + self->pulse_dest = self->pulse_centre + (int16_t)((mp_float_t)self->pulse_speed_100 * mp_obj_get_float(args[1]) / MICROPY_FLOAT_CONST(100.0)); #else self->pulse_dest = self->pulse_centre + self->pulse_speed_100 * mp_obj_get_int(args[1]) / 100; #endif diff --git a/ports/stm32/spi.h b/ports/stm32/spi.h index 885fb0bd6fc8c..17f1bf6c4ab90 100644 --- a/ports/stm32/spi.h +++ b/ports/stm32/spi.h @@ -65,7 +65,6 @@ extern const spi_t spi_obj[6]; extern const mp_spi_proto_t spi_proto; extern const mp_obj_type_t pyb_spi_type; -extern const mp_obj_type_t machine_soft_spi_type; extern const mp_obj_type_t machine_hard_spi_type; // A transfer of "len" bytes should take len*8*1000/baudrate milliseconds. diff --git a/ports/stm32/stm32_it.c b/ports/stm32/stm32_it.c index b84f4adfaeb81..8e96da177b583 100644 --- a/ports/stm32/stm32_it.c +++ b/ports/stm32/stm32_it.c @@ -602,7 +602,7 @@ void TIM1_UP_TIM10_IRQHandler(void) { IRQ_EXIT(TIM1_UP_TIM10_IRQn); } -#if defined(STM32L4) +#if defined(STM32L4) || defined(STM32WB) void TIM1_UP_TIM16_IRQHandler(void) { IRQ_ENTER(TIM1_UP_TIM16_IRQn); timer_irq_handler(1); diff --git a/ports/stm32/timer.c b/ports/stm32/timer.c index 491221ec2a7de..9b8c14c0da552 100644 --- a/ports/stm32/timer.c +++ b/ports/stm32/timer.c @@ -477,7 +477,7 @@ STATIC void config_deadtime(pyb_timer_obj_t *self, mp_int_t ticks, mp_int_t brk) deadTimeConfig.DeadTime = compute_dtg_from_ticks(ticks); deadTimeConfig.BreakState = brk == BRK_OFF ? TIM_BREAK_DISABLE : TIM_BREAK_ENABLE; deadTimeConfig.BreakPolarity = brk == BRK_LOW ? TIM_BREAKPOLARITY_LOW : TIM_BREAKPOLARITY_HIGH; - #if defined(STM32F7) || defined(STM32H7) | defined(STM32L4) + #if defined(STM32F7) || defined(STM32H7) || defined(STM32L4) || defined(STM32WB) deadTimeConfig.BreakFilter = 0; deadTimeConfig.Break2State = TIM_BREAK_DISABLE; deadTimeConfig.Break2Polarity = TIM_BREAKPOLARITY_LOW; @@ -769,14 +769,14 @@ STATIC mp_obj_t pyb_timer_init_helper(pyb_timer_obj_t *self, size_t n_args, cons HAL_TIM_Base_Init(&self->tim); #if !defined(STM32L0) #if defined(IS_TIM_ADVANCED_INSTANCE) - if (IS_TIM_ADVANCED_INSTANCE(self->tim.Instance)) { + if (IS_TIM_ADVANCED_INSTANCE(self->tim.Instance)) #elif defined(IS_TIM_BREAK_INSTANCE) - if (IS_TIM_BREAK_INSTANCE(self->tim.Instance)) { + if (IS_TIM_BREAK_INSTANCE(self->tim.Instance)) #else - if (0) { - #endif + if (0) + #endif + { config_deadtime(self, args[ARG_deadtime].u_int, args[ARG_brk].u_int); - } #endif @@ -805,7 +805,7 @@ STATIC const uint32_t tim_instance_table[MICROPY_HW_MAX_TIMER] = { TIM_ENTRY(1, TIM1_UP_TIM10_IRQn), #elif defined(STM32H7) TIM_ENTRY(1, TIM1_UP_IRQn), - #elif defined(STM32L4) + #elif defined(STM32L4) || defined(STM32WB) TIM_ENTRY(1, TIM1_UP_TIM16_IRQn), #endif #endif diff --git a/ports/stm32/uart.c b/ports/stm32/uart.c index 8693813fa4413..b14a18c6ddfa6 100644 --- a/ports/stm32/uart.c +++ b/ports/stm32/uart.c @@ -93,6 +93,28 @@ extern void NORETURN __fatal_error(const char *msg); +typedef struct _pyb_uart_irq_map_t { + uint16_t irq_en; + uint16_t flag; +} pyb_uart_irq_map_t; + +STATIC const pyb_uart_irq_map_t mp_uart_irq_map[] = { + { USART_CR1_IDLEIE, UART_FLAG_IDLE}, // RX idle + { USART_CR1_PEIE, UART_FLAG_PE}, // parity error + { USART_CR1_TXEIE, UART_FLAG_TXE}, // TX register empty + { USART_CR1_TCIE, UART_FLAG_TC}, // TX complete + { USART_CR1_RXNEIE, UART_FLAG_RXNE}, // RX register not empty + #if 0 + // For now only IRQs selected by CR1 are supported + #if defined(STM32F4) + { USART_CR2_LBDIE, UART_FLAG_LBD}, // LIN break detection + #else + { USART_CR2_LBDIE, UART_FLAG_LBDF}, // LIN break detection + #endif + { USART_CR3_CTSIE, UART_FLAG_CTS}, // CTS + #endif +}; + void uart_init0(void) { #if defined(STM32H7) RCC_PeriphCLKInitTypeDef RCC_PeriphClkInit = {0}; @@ -444,6 +466,23 @@ bool uart_init(pyb_uart_obj_t *uart_obj, return true; } +void uart_irq_config(pyb_uart_obj_t *self, bool enable) { + if (self->mp_irq_trigger) { + for (size_t entry = 0; entry < MP_ARRAY_SIZE(mp_uart_irq_map); ++entry) { + if (mp_uart_irq_map[entry].flag & MP_UART_RESERVED_FLAGS) { + continue; + } + if (mp_uart_irq_map[entry].flag & self->mp_irq_trigger) { + if (enable) { + self->uartx->CR1 |= mp_uart_irq_map[entry].irq_en; + } else { + self->uartx->CR1 &= ~mp_uart_irq_map[entry].irq_en; + } + } + } + } +} + void uart_set_rxbuf(pyb_uart_obj_t *self, size_t len, void *buf) { self->read_buf_head = 0; self->read_buf_tail = 0; @@ -864,3 +903,26 @@ void uart_irq_handler(mp_uint_t uart_id) { mp_irq_handler(self->mp_irq_obj); } } + +STATIC mp_uint_t uart_irq_trigger(mp_obj_t self_in, mp_uint_t new_trigger) { + pyb_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + uart_irq_config(self, false); + self->mp_irq_trigger = new_trigger; + uart_irq_config(self, true); + return 0; +} + +STATIC mp_uint_t uart_irq_info(mp_obj_t self_in, mp_uint_t info_type) { + pyb_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (info_type == MP_IRQ_INFO_FLAGS) { + return self->mp_irq_flags; + } else if (info_type == MP_IRQ_INFO_TRIGGERS) { + return self->mp_irq_trigger; + } + return 0; +} + +const mp_irq_methods_t uart_irq_methods = { + .trigger = uart_irq_trigger, + .info = uart_irq_info, +}; diff --git a/ports/stm32/uart.h b/ports/stm32/uart.h index 62676fb91be51..9a38db593d4fc 100644 --- a/ports/stm32/uart.h +++ b/ports/stm32/uart.h @@ -26,7 +26,7 @@ #ifndef MICROPY_INCLUDED_STM32_UART_H #define MICROPY_INCLUDED_STM32_UART_H -struct _mp_irq_obj_t; +#include "lib/utils/mpirq.h" typedef enum { PYB_UART_NONE = 0, @@ -45,6 +45,12 @@ typedef enum { #define CHAR_WIDTH_8BIT (0) #define CHAR_WIDTH_9BIT (1) +// OR-ed IRQ flags which are allowed to be used by the user +#define MP_UART_ALLOWED_FLAGS UART_FLAG_IDLE + +// OR-ed IRQ flags which should not be touched by the user +#define MP_UART_RESERVED_FLAGS UART_FLAG_RXNE + typedef struct _pyb_uart_obj_t { mp_obj_base_t base; USART_TypeDef *uartx; @@ -62,16 +68,18 @@ typedef struct _pyb_uart_obj_t { byte *read_buf; // byte or uint16_t, depending on char size uint16_t mp_irq_trigger; // user IRQ trigger mask uint16_t mp_irq_flags; // user IRQ active IRQ flags - struct _mp_irq_obj_t *mp_irq_obj; // user IRQ object + mp_irq_obj_t *mp_irq_obj; // user IRQ object } pyb_uart_obj_t; extern const mp_obj_type_t pyb_uart_type; +extern const mp_irq_methods_t uart_irq_methods; void uart_init0(void); void uart_deinit_all(void); bool uart_exists(int uart_id); bool uart_init(pyb_uart_obj_t *uart_obj, uint32_t baudrate, uint32_t bits, uint32_t parity, uint32_t stop, uint32_t flow); +void uart_irq_config(pyb_uart_obj_t *self, bool enable); void uart_set_rxbuf(pyb_uart_obj_t *self, size_t len, void *buf); void uart_deinit(pyb_uart_obj_t *uart_obj); void uart_irq_handler(mp_uint_t uart_id); diff --git a/ports/stm32/usb.c b/ports/stm32/usb.c index cd5238172188e..968b2999ced37 100644 --- a/ports/stm32/usb.c +++ b/ports/stm32/usb.c @@ -93,8 +93,29 @@ pyb_usb_storage_medium_t pyb_usb_storage_medium = PYB_USB_STORAGE_MEDIUM_NONE; // Units of FIFO size arrays below are 4x 16-bit words = 8 bytes // There are 512x 16-bit words it total to use here (when using PCD_SNG_BUF) -// EP0(out), EP0(in), MSC/HID(out), MSC/HID(in), unused, CDC_CMD(in), CDC_DATA(out), CDC_DATA(in) -STATIC const uint8_t usbd_fifo_size_cdc1[] = {16, 16, 16, 16, 0, 16, 16, 16}; +STATIC const uint8_t usbd_fifo_size_cdc1[USBD_PMA_NUM_FIFO] = { + 16, 16, 16, 16, // EP0(out), EP0(in), MSC/HID(out), MSC/HID(in) + 0, 16, 16, 16, // unused, CDC_CMD(in), CDC_DATA(out), CDC_DATA(in) + 0, 0, 0, 0, 0, 0, 0, 0, // 8x unused +}; + +#if MICROPY_HW_USB_CDC_NUM >= 2 +STATIC const uint8_t usbd_fifo_size_cdc2[USBD_PMA_NUM_FIFO] = { + 8, 8, 16, 16, // EP0(out), EP0(in), MSC/HID(out), MSC/HID(in) + 0, 8, 12, 12, // unused, CDC_CMD(in), CDC_DATA(out), CDC_DATA(in) + 0, 8, 12, 12, // unused, CDC2_CMD(in), CDC2_DATA(out), CDC2_DATA(in) + 0, 0, 0, 0, // 4x unused +}; + +// RX; EP0(in), MSC/HID, CDC_CMD, CDC_DATA, CDC2_CMD/HID, CDC2_DATA, HID +STATIC const uint8_t usbd_fifo_size_cdc2_msc_hid[USBD_PMA_NUM_FIFO] = { + 8, 8, 16, 16, // EP0(out), EP0(in), MSC/HID(out), MSC/HID(in) + 0, 8, 8, 8, // unused, CDC_CMD(in), CDC_DATA(out), CDC_DATA(in) + 0, 8, 8, 8, // unused, CDC2_CMD(in), CDC2_DATA(out), CDC2_DATA(in) + 8, 8, // HID(out), HID(in) + 0, 0, // 2x unused +}; +#endif #else @@ -200,7 +221,9 @@ void pyb_usb_init0(void) { for (int i = 0; i < MICROPY_HW_USB_CDC_NUM; ++i) { usb_device.usbd_cdc_itf[i].attached_to_repl = false; } + #if MICROPY_HW_USB_HID MP_STATE_PORT(pyb_hid_report_desc) = MP_OBJ_NULL; + #endif pyb_usb_vcp_init0(); } diff --git a/ports/stm32/usbd_cdc_interface.c b/ports/stm32/usbd_cdc_interface.c index a0e19de9750aa..7a09128527cec 100644 --- a/ports/stm32/usbd_cdc_interface.c +++ b/ports/stm32/usbd_cdc_interface.c @@ -171,32 +171,56 @@ int8_t usbd_cdc_control(usbd_cdc_state_t *cdc_in, uint8_t cmd, uint8_t *pbuf, ui return USBD_OK; } +static inline uint16_t usbd_cdc_tx_buffer_mask(uint16_t val) { + return val & (USBD_CDC_TX_DATA_SIZE - 1); +} + +static inline uint16_t usbd_cdc_tx_buffer_size(usbd_cdc_itf_t *cdc) { + return cdc->tx_buf_ptr_in - cdc->tx_buf_ptr_out; +} + +static inline bool usbd_cdc_tx_buffer_empty(usbd_cdc_itf_t *cdc) { + return cdc->tx_buf_ptr_out == cdc->tx_buf_ptr_in; +} + +static inline bool usbd_cdc_tx_buffer_will_be_empty(usbd_cdc_itf_t *cdc) { + return cdc->tx_buf_ptr_out_next == cdc->tx_buf_ptr_in; +} + +static inline bool usbd_cdc_tx_buffer_full(usbd_cdc_itf_t *cdc) { + return usbd_cdc_tx_buffer_size(cdc) == USBD_CDC_TX_DATA_SIZE; +} + +static uint16_t usbd_cdc_tx_send_length(usbd_cdc_itf_t *cdc) { + uint16_t to_end = USBD_CDC_TX_DATA_SIZE - usbd_cdc_tx_buffer_mask(cdc->tx_buf_ptr_out); + return MIN(usbd_cdc_tx_buffer_size(cdc), to_end); +} + +static void usbd_cdc_tx_buffer_put(usbd_cdc_itf_t *cdc, uint8_t data) { + cdc->tx_buf[usbd_cdc_tx_buffer_mask(cdc->tx_buf_ptr_in)] = data; + cdc->tx_buf_ptr_in++; +} + +static uint8_t *usbd_cdc_tx_buffer_getp(usbd_cdc_itf_t *cdc, uint16_t len) { + cdc->tx_buf_ptr_out_next += len; + return &cdc->tx_buf[usbd_cdc_tx_buffer_mask(cdc->tx_buf_ptr_out)]; +} + // Called when the USB IN endpoint is ready to receive more data // (cdc.base.tx_in_progress must be 0) void usbd_cdc_tx_ready(usbd_cdc_state_t *cdc_in) { usbd_cdc_itf_t *cdc = (usbd_cdc_itf_t *)cdc_in; - cdc->tx_buf_ptr_out = cdc->tx_buf_ptr_out_shadow; + cdc->tx_buf_ptr_out = cdc->tx_buf_ptr_out_next; - if (cdc->tx_buf_ptr_out == cdc->tx_buf_ptr_in && !cdc->tx_need_empty_packet) { + if (usbd_cdc_tx_buffer_empty(cdc) && !cdc->tx_need_empty_packet) { // No outstanding data to send return; } - - uint32_t len; - if (cdc->tx_buf_ptr_out > cdc->tx_buf_ptr_in) { // rollback - len = USBD_CDC_TX_DATA_SIZE - cdc->tx_buf_ptr_out; - } else { - len = cdc->tx_buf_ptr_in - cdc->tx_buf_ptr_out; - } - + uint16_t len = usbd_cdc_tx_send_length(cdc); // Should always succeed because cdc.base.tx_in_progress==0 - USBD_CDC_TransmitPacket(&cdc->base, len, &cdc->tx_buf[cdc->tx_buf_ptr_out]); + USBD_CDC_TransmitPacket(&cdc->base, len, usbd_cdc_tx_buffer_getp(cdc, len)); - cdc->tx_buf_ptr_out_shadow += len; - if (cdc->tx_buf_ptr_out_shadow == USBD_CDC_TX_DATA_SIZE) { - cdc->tx_buf_ptr_out_shadow = 0; - } // According to the USB specification, a packet size of 64 bytes (CDC_DATA_FS_MAX_PACKET_SIZE) // gets held at the USB host until the next packet is sent. This is because a @@ -204,7 +228,7 @@ void usbd_cdc_tx_ready(usbd_cdc_state_t *cdc_in) { // the host waits for all data to arrive (ie, waits for a packet < max packet size). // To flush a packet of exactly max packet size, we need to send a zero-size packet. // See eg http://www.cypress.com/?id=4&rID=92719 - cdc->tx_need_empty_packet = (len > 0 && len % usbd_cdc_max_packet(cdc->base.usbd->pdev) == 0 && cdc->tx_buf_ptr_out_shadow == cdc->tx_buf_ptr_in); + cdc->tx_need_empty_packet = (len > 0 && len % usbd_cdc_max_packet(cdc->base.usbd->pdev) == 0 && usbd_cdc_tx_buffer_will_be_empty(cdc)); } // Attempt to queue data on the USB IN endpoint @@ -291,10 +315,7 @@ int8_t usbd_cdc_receive(usbd_cdc_state_t *cdc_in, size_t len) { } int usbd_cdc_tx_half_empty(usbd_cdc_itf_t *cdc) { - int32_t tx_waiting = (int32_t)cdc->tx_buf_ptr_in - (int32_t)cdc->tx_buf_ptr_out; - if (tx_waiting < 0) { - tx_waiting += USBD_CDC_TX_DATA_SIZE; - } + int32_t tx_waiting = usbd_cdc_tx_buffer_size(cdc); return tx_waiting <= USBD_CDC_TX_DATA_SIZE / 2; } @@ -317,8 +338,7 @@ int usbd_cdc_tx(usbd_cdc_itf_t *cdc, const uint8_t *buf, uint32_t len, uint32_t for (uint32_t i = 0; i < len; i++) { // Wait until the device is connected and the buffer has space, with a given timeout uint32_t start = HAL_GetTick(); - while (cdc->connect_state == USBD_CDC_CONNECT_STATE_DISCONNECTED - || ((cdc->tx_buf_ptr_in + 1) & (USBD_CDC_TX_DATA_SIZE - 1)) == cdc->tx_buf_ptr_out) { + while (cdc->connect_state == USBD_CDC_CONNECT_STATE_DISCONNECTED || usbd_cdc_tx_buffer_full(cdc)) { usbd_cdc_try_tx(cdc); // Wraparound of tick is taken care of by 2's complement arithmetic. if (HAL_GetTick() - start >= timeout) { @@ -333,8 +353,7 @@ int usbd_cdc_tx(usbd_cdc_itf_t *cdc, const uint8_t *buf, uint32_t len, uint32_t } // Write data to device buffer - cdc->tx_buf[cdc->tx_buf_ptr_in] = buf[i]; - cdc->tx_buf_ptr_in = (cdc->tx_buf_ptr_in + 1) & (USBD_CDC_TX_DATA_SIZE - 1); + usbd_cdc_tx_buffer_put(cdc, buf[i]); } usbd_cdc_try_tx(cdc); @@ -357,7 +376,7 @@ void usbd_cdc_tx_always(usbd_cdc_itf_t *cdc, const uint8_t *buf, uint32_t len) { // If the buffer is full, wait until it gets drained, with a timeout of 500ms // (wraparound of tick is taken care of by 2's complement arithmetic). uint32_t start = HAL_GetTick(); - while (((cdc->tx_buf_ptr_in + 1) & (USBD_CDC_TX_DATA_SIZE - 1)) == cdc->tx_buf_ptr_out && HAL_GetTick() - start <= 500) { + while (usbd_cdc_tx_buffer_full(cdc) && HAL_GetTick() - start <= 500) { usbd_cdc_try_tx(cdc); if (query_irq() == IRQ_STATE_DISABLED) { // IRQs disabled so buffer will never be drained; exit loop @@ -367,8 +386,7 @@ void usbd_cdc_tx_always(usbd_cdc_itf_t *cdc, const uint8_t *buf, uint32_t len) { } } - cdc->tx_buf[cdc->tx_buf_ptr_in] = buf[i]; - cdc->tx_buf_ptr_in = (cdc->tx_buf_ptr_in + 1) & (USBD_CDC_TX_DATA_SIZE - 1); + usbd_cdc_tx_buffer_put(cdc, buf[i]); } usbd_cdc_try_tx(cdc); } diff --git a/ports/stm32/usbd_cdc_interface.h b/ports/stm32/usbd_cdc_interface.h index 1847b3a6b0eb1..d0509b09f9a99 100644 --- a/ports/stm32/usbd_cdc_interface.h +++ b/ports/stm32/usbd_cdc_interface.h @@ -35,7 +35,7 @@ #define USBD_CDC_RX_DATA_SIZE (1024) // this must be 2 or greater, and a power of 2 #endif #ifndef USBD_CDC_TX_DATA_SIZE -#define USBD_CDC_TX_DATA_SIZE (1024) // I think this can be any value (was 2048) +#define USBD_CDC_TX_DATA_SIZE (1024) // This must be a power of 2 and no greater than 16384 #endif // Values for connect_state @@ -60,7 +60,7 @@ typedef struct _usbd_cdc_itf_t { uint8_t tx_buf[USBD_CDC_TX_DATA_SIZE]; // data for USB IN endpoind is stored in this buffer uint16_t tx_buf_ptr_in; // increment this pointer modulo USBD_CDC_TX_DATA_SIZE when new data is available volatile uint16_t tx_buf_ptr_out; // increment this pointer modulo USBD_CDC_TX_DATA_SIZE when data is drained - uint16_t tx_buf_ptr_out_shadow; // shadow of above + uint16_t tx_buf_ptr_out_next; // next position of above once transmission finished uint8_t tx_need_empty_packet; // used to flush the USB IN endpoint if the last packet was exactly the endpoint packet size volatile uint8_t connect_state; // indicates if we are connected diff --git a/ports/stm32/usbd_conf.h b/ports/stm32/usbd_conf.h index 5237ba3a96e72..83805a6e4d801 100644 --- a/ports/stm32/usbd_conf.h +++ b/ports/stm32/usbd_conf.h @@ -51,7 +51,7 @@ // For MCUs with a device-only USB peripheral #define USBD_PMA_RESERVE (64) -#define USBD_PMA_NUM_FIFO (8) +#define USBD_PMA_NUM_FIFO (16) // Maximum 8 endpoints, 2 FIFOs each // For MCUs with multiple OTG USB peripherals #define USBD_FS_NUM_TX_FIFO (6) diff --git a/ports/stm32/usbd_msc_interface.h b/ports/stm32/usbd_msc_interface.h index 411c707cab7df..9d25a72a3a4ba 100644 --- a/ports/stm32/usbd_msc_interface.h +++ b/ports/stm32/usbd_msc_interface.h @@ -26,8 +26,6 @@ #ifndef MICROPY_INCLUDED_STM32_USBD_MSC_INTERFACE_H #define MICROPY_INCLUDED_STM32_USBD_MSC_INTERFACE_H -#define USBD_MSC_MAX_LUN (2) - extern const USBD_StorageTypeDef usbd_msc_fops; void usbd_msc_init_lu(size_t lu_n, const void *lu_data); diff --git a/ports/stm32/usbdev/class/inc/usbd_cdc_msc_hid.h b/ports/stm32/usbdev/class/inc/usbd_cdc_msc_hid.h index d934f4676c4df..d6c38bbd07e34 100644 --- a/ports/stm32/usbdev/class/inc/usbd_cdc_msc_hid.h +++ b/ports/stm32/usbdev/class/inc/usbd_cdc_msc_hid.h @@ -34,6 +34,9 @@ #define MSC_MEDIA_PACKET (2048) // was 8192; how low can it go whilst still working? #define HID_DATA_FS_MAX_PACKET_SIZE (64) // endpoint IN & OUT packet size +// Maximum number of LUN that can be exposed on the MSC interface +#define USBD_MSC_MAX_LUN (2) + // Need to define here for BOT and SCSI layers #define MSC_IN_EP (0x81) #define MSC_OUT_EP (0x01) @@ -78,8 +81,8 @@ typedef struct { uint8_t scsi_sense_head; uint8_t scsi_sense_tail; - uint16_t scsi_blk_size; - uint32_t scsi_blk_nbr; + uint16_t scsi_blk_size[USBD_MSC_MAX_LUN]; + uint32_t scsi_blk_nbr[USBD_MSC_MAX_LUN]; uint32_t scsi_blk_addr_in_blks; uint32_t scsi_blk_len; diff --git a/ports/stm32/usbdev/class/src/usbd_msc_scsi.c b/ports/stm32/usbdev/class/src/usbd_msc_scsi.c index d0413b758a5d6..2eb716ccde438 100644 --- a/ports/stm32/usbdev/class/src/usbd_msc_scsi.c +++ b/ports/stm32/usbdev/class/src/usbd_msc_scsi.c @@ -247,7 +247,7 @@ static int8_t SCSI_ReadCapacity10(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_ { USBD_MSC_BOT_HandleTypeDef *hmsc = &((usbd_cdc_msc_hid_state_t*)pdev->pClassData)->MSC_BOT_ClassData; - if(hmsc->bdev_ops->GetCapacity(lun, &hmsc->scsi_blk_nbr, &hmsc->scsi_blk_size) != 0) + if(hmsc->bdev_ops->GetCapacity(lun, &hmsc->scsi_blk_nbr[lun], &hmsc->scsi_blk_size[lun]) != 0) { SCSI_SenseCode(pdev, lun, @@ -258,15 +258,17 @@ static int8_t SCSI_ReadCapacity10(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_ else { - hmsc->bot_data[0] = (uint8_t)((hmsc->scsi_blk_nbr - 1) >> 24); - hmsc->bot_data[1] = (uint8_t)((hmsc->scsi_blk_nbr - 1) >> 16); - hmsc->bot_data[2] = (uint8_t)((hmsc->scsi_blk_nbr - 1) >> 8); - hmsc->bot_data[3] = (uint8_t)(hmsc->scsi_blk_nbr - 1); + uint32_t blk_nbr = hmsc->scsi_blk_nbr[lun]; + hmsc->bot_data[0] = (uint8_t)((blk_nbr - 1) >> 24); + hmsc->bot_data[1] = (uint8_t)((blk_nbr - 1) >> 16); + hmsc->bot_data[2] = (uint8_t)((blk_nbr - 1) >> 8); + hmsc->bot_data[3] = (uint8_t)(blk_nbr - 1); - hmsc->bot_data[4] = (uint8_t)(hmsc->scsi_blk_size >> 24); - hmsc->bot_data[5] = (uint8_t)(hmsc->scsi_blk_size >> 16); - hmsc->bot_data[6] = (uint8_t)(hmsc->scsi_blk_size >> 8); - hmsc->bot_data[7] = (uint8_t)(hmsc->scsi_blk_size); + uint32_t blk_size = hmsc->scsi_blk_size[lun]; + hmsc->bot_data[4] = (uint8_t)(blk_size >> 24); + hmsc->bot_data[5] = (uint8_t)(blk_size >> 16); + hmsc->bot_data[6] = (uint8_t)(blk_size >> 8); + hmsc->bot_data[7] = (uint8_t)(blk_size); hmsc->bot_data_length = 8; return 0; @@ -516,7 +518,7 @@ static int8_t SCSI_Read10(USBD_HandleTypeDef *pdev, uint8_t lun , uint8_t *para } hmsc->bot_state = USBD_BOT_DATA_IN; - hmsc->scsi_blk_len *= hmsc->scsi_blk_size; + hmsc->scsi_blk_len *= hmsc->scsi_blk_size[lun]; /* cases 4,5 : Hi <> Dn */ if (hmsc->cbw.dDataLength != hmsc->scsi_blk_len) @@ -596,7 +598,7 @@ static int8_t SCSI_Write10 (USBD_HandleTypeDef *pdev, uint8_t lun , uint8_t *pa return -1; /* error */ } - hmsc->scsi_blk_len *= hmsc->scsi_blk_size; + hmsc->scsi_blk_len *= hmsc->scsi_blk_size[lun]; /* cases 3,11,13 : Hn,Ho <> D0 */ if (hmsc->cbw.dDataLength != hmsc->scsi_blk_len) @@ -670,7 +672,7 @@ static int8_t SCSI_CheckAddressRange (USBD_HandleTypeDef *pdev, uint8_t lun , u { USBD_MSC_BOT_HandleTypeDef *hmsc = &((usbd_cdc_msc_hid_state_t*)pdev->pClassData)->MSC_BOT_ClassData; - if ((blk_offset + blk_nbr) > hmsc->scsi_blk_nbr ) + if ((blk_offset + blk_nbr) > hmsc->scsi_blk_nbr[lun]) { SCSI_SenseCode(pdev, lun, @@ -697,7 +699,7 @@ static int8_t SCSI_ProcessRead (USBD_HandleTypeDef *pdev, uint8_t lun) if( hmsc->bdev_ops->Read(lun , hmsc->bot_data, hmsc->scsi_blk_addr_in_blks, - len / hmsc->scsi_blk_size) < 0) + len / hmsc->scsi_blk_size[lun]) < 0) { SCSI_SenseCode(pdev, @@ -714,7 +716,7 @@ static int8_t SCSI_ProcessRead (USBD_HandleTypeDef *pdev, uint8_t lun) len); - hmsc->scsi_blk_addr_in_blks += len / hmsc->scsi_blk_size; + hmsc->scsi_blk_addr_in_blks += len / hmsc->scsi_blk_size[lun]; hmsc->scsi_blk_len -= len; /* case 6 : Hi = Di */ @@ -744,7 +746,7 @@ static int8_t SCSI_ProcessWrite (USBD_HandleTypeDef *pdev, uint8_t lun) if(hmsc->bdev_ops->Write(lun , hmsc->bot_data, hmsc->scsi_blk_addr_in_blks, - len / hmsc->scsi_blk_size) < 0) + len / hmsc->scsi_blk_size[lun]) < 0) { SCSI_SenseCode(pdev, lun, @@ -754,7 +756,7 @@ static int8_t SCSI_ProcessWrite (USBD_HandleTypeDef *pdev, uint8_t lun) } - hmsc->scsi_blk_addr_in_blks += len / hmsc->scsi_blk_size; + hmsc->scsi_blk_addr_in_blks += len / hmsc->scsi_blk_size[lun]; hmsc->scsi_blk_len -= len; /* case 12 : Ho = Do */ diff --git a/ports/teensy/make-pins.py b/ports/teensy/make-pins.py index ddefae8521c50..4e46a8c244969 100755 --- a/ports/teensy/make-pins.py +++ b/ports/teensy/make-pins.py @@ -370,7 +370,10 @@ def main(): default="build/pins_af.py", ) parser.add_argument( - "-b", "--board", dest="board_filename", help="Specifies the board file", + "-b", + "--board", + dest="board_filename", + help="Specifies the board file", ) parser.add_argument( "-p", diff --git a/ports/teensy/modpyb.c b/ports/teensy/modpyb.c index 26e3f4c43a0dc..f4384a885426e 100644 --- a/ports/teensy/modpyb.c +++ b/ports/teensy/modpyb.c @@ -36,7 +36,6 @@ #include "lib/utils/pyexec.h" #include "gccollect.h" -#include "irq.h" #include "systick.h" #include "led.h" #include "pin.h" @@ -53,6 +52,7 @@ #include "dac.h" #include "usb.h" #include "portmodules.h" +#include "modmachine.h" /// \module pyb - functions related to the pyboard /// @@ -230,6 +230,12 @@ STATIC mp_obj_t pyb_udelay(mp_obj_t usec_in) { } STATIC MP_DEFINE_CONST_FUN_OBJ_1(pyb_udelay_obj, pyb_udelay); +STATIC mp_obj_t pyb_wfi(void) { + __WFI(); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_0(pyb_wfi_obj, pyb_wfi); + STATIC mp_obj_t pyb_stop(void) { printf("stop not currently implemented\n"); return mp_const_none; @@ -285,8 +291,8 @@ STATIC const mp_rom_map_elem_t pyb_module_globals_table[] = { #endif { MP_ROM_QSTR(MP_QSTR_wfi), MP_ROM_PTR(&pyb_wfi_obj) }, - { MP_ROM_QSTR(MP_QSTR_disable_irq), MP_ROM_PTR(&pyb_disable_irq_obj) }, - { MP_ROM_QSTR(MP_QSTR_enable_irq), MP_ROM_PTR(&pyb_enable_irq_obj) }, + { MP_ROM_QSTR(MP_QSTR_disable_irq), MP_ROM_PTR(&machine_disable_irq_obj) }, + { MP_ROM_QSTR(MP_QSTR_enable_irq), MP_ROM_PTR(&machine_enable_irq_obj) }, { MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&pyb_stop_obj) }, { MP_ROM_QSTR(MP_QSTR_standby), MP_ROM_PTR(&pyb_standby_obj) }, diff --git a/ports/unix/Makefile b/ports/unix/Makefile index ec14166149ba8..ff5f355022bc5 100644 --- a/ports/unix/Makefile +++ b/ports/unix/Makefile @@ -14,7 +14,7 @@ include ../../py/mkenv.mk include $(VARIANT_DIR)/mpconfigvariant.mk # use FROZEN_MANIFEST for new projects, others are legacy -FROZEN_MANIFEST ?= manifest.py +FROZEN_MANIFEST ?= variants/manifest.py FROZEN_DIR = FROZEN_MPY_DIR = @@ -47,10 +47,12 @@ ifdef DEBUG COPT ?= -O0 else COPT ?= -Os -COPT += -fdata-sections -ffunction-sections COPT += -DNDEBUG endif +# Remove unused sections. +COPT += -fdata-sections -ffunction-sections + # Always enable symbols -- They're occasionally useful, and don't make it into the # final .bin/.hex/.dfu so the extra size doesn't matter. CFLAGS += -g @@ -132,26 +134,60 @@ CFLAGS_MOD += -DMICROPY_PY_THREAD=1 -DMICROPY_PY_THREAD_GIL=0 LDFLAGS_MOD += $(LIBPTHREAD) endif -# If the variant enables it and we have libusb, enable BTStack support for USB adaptors. +# If the variant enables it, enable modbluetooth. ifeq ($(MICROPY_PY_BLUETOOTH),1) HAVE_LIBUSB := $(shell (which pkg-config > /dev/null && pkg-config --exists libusb-1.0) 2>/dev/null && echo '1') -ifeq ($(HAVE_LIBUSB),1) + +# Only one stack can be enabled. +ifeq ($(MICROPY_BLUETOOTH_NIMBLE),1) +ifeq ($(MICROPY_BLUETOOTH_BTSTACK),1) +$(error Cannot enable both NimBLE and BTstack at the same time) +endif +endif + +# Default to btstack, but a variant (or make command line) can set NimBLE +# explicitly (which is always via H4 UART). +ifneq ($(MICROPY_BLUETOOTH_NIMBLE),1) +ifneq ($(MICROPY_BLUETOOTH_BTSTACK),1) +MICROPY_BLUETOOTH_BTSTACK ?= 1 +endif +endif CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH=1 CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE=1 -CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK=1 +# Runs in a thread, cannot make calls into the VM. +CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK=0 -MICROPY_BLUETOOTH_BTSTACK ?= 1 +ifeq ($(MICROPY_BLUETOOTH_BTSTACK),1) + +# Figure out which BTstack transport to use. +ifeq ($(MICROPY_BLUETOOTH_BTSTACK_H4),1) +ifeq ($(MICROPY_BLUETOOTH_BTSTACK_USB),1) +$(error Cannot enable BTstack support for USB and H4 UART at the same time) +endif +else +ifeq ($(HAVE_LIBUSB),1) +# Default to btstack-over-usb. MICROPY_BLUETOOTH_BTSTACK_USB ?= 1 +else +# Fallback to HCI controller via a H4 UART (e.g. Zephyr on nRF) over a /dev/tty serial port. +MICROPY_BLUETOOTH_BTSTACK_H4 ?= 1 +endif +endif -ifeq ($(MICROPY_BLUETOOTH_BTSTACK),1) +# BTstack is enabled. GIT_SUBMODULES += lib/btstack - include $(TOP)/extmod/btstack/btstack.mk -endif + +else + +# NimBLE is enabled. +GIT_SUBMODULES += lib/mynewt-nimble +include $(TOP)/extmod/nimble/nimble.mk endif + endif ifeq ($(MICROPY_PY_FFI),1) @@ -198,7 +234,11 @@ SRC_C = \ alloc.c \ coverage.c \ fatfs_port.c \ - btstack_usb.c \ + mpbthciport.c \ + mpbtstackport_common.c \ + mpbtstackport_h4.c \ + mpbtstackport_usb.c \ + mpnimbleport.c \ $(SRC_MOD) \ $(wildcard $(VARIANT_DIR)/*.c) diff --git a/ports/unix/fatfs_port.c b/ports/unix/fatfs_port.c index 30f1959f52063..9e0f444ce2576 100644 --- a/ports/unix/fatfs_port.c +++ b/ports/unix/fatfs_port.c @@ -1,5 +1,13 @@ +#include #include "lib/oofatfs/ff.h" DWORD get_fattime(void) { - return 0; + time_t now = time(NULL); + struct tm *tm = localtime(&now); + return ((1900 + tm->tm_year - 1980) << 25) + | ((tm->tm_mon + 1) << 21) + | (tm->tm_mday << 16) + | (tm->tm_hour << 11) + | (tm->tm_min << 5) + | (tm->tm_sec / 2); } diff --git a/ports/unix/main.c b/ports/unix/main.c index c38b7b0c287a9..0fe492a554cbd 100644 --- a/ports/unix/main.c +++ b/ports/unix/main.c @@ -133,7 +133,7 @@ STATIC int execute_from_lexer(int source_kind, const void *source, mp_parse_inpu // allow to print the parse tree in the coverage build if (mp_verbose_flag >= 3) { printf("----------------\n"); - mp_parse_node_print(parse_tree.root, 0); + mp_parse_node_print(&mp_plat_print, parse_tree.root, 0); printf("----------------\n"); } #endif diff --git a/ports/unix/manifest_coverage.py b/ports/unix/manifest_coverage.py deleted file mode 100644 index 0c32d08578e94..0000000000000 --- a/ports/unix/manifest_coverage.py +++ /dev/null @@ -1,2 +0,0 @@ -freeze_as_str('coverage-frzstr') -freeze_as_mpy('coverage-frzmpy') diff --git a/ports/unix/modos.c b/ports/unix/modos.c index 82b1b11425679..5e719c5736c1c 100644 --- a/ports/unix/modos.c +++ b/ports/unix/modos.c @@ -58,15 +58,15 @@ STATIC mp_obj_t mod_os_stat(mp_obj_t path_in) { mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL)); t->items[0] = MP_OBJ_NEW_SMALL_INT(sb.st_mode); - t->items[1] = MP_OBJ_NEW_SMALL_INT(sb.st_ino); - t->items[2] = MP_OBJ_NEW_SMALL_INT(sb.st_dev); - t->items[3] = MP_OBJ_NEW_SMALL_INT(sb.st_nlink); - t->items[4] = MP_OBJ_NEW_SMALL_INT(sb.st_uid); - t->items[5] = MP_OBJ_NEW_SMALL_INT(sb.st_gid); + t->items[1] = mp_obj_new_int_from_uint(sb.st_ino); + t->items[2] = mp_obj_new_int_from_uint(sb.st_dev); + t->items[3] = mp_obj_new_int_from_uint(sb.st_nlink); + t->items[4] = mp_obj_new_int_from_uint(sb.st_uid); + t->items[5] = mp_obj_new_int_from_uint(sb.st_gid); t->items[6] = mp_obj_new_int_from_uint(sb.st_size); - t->items[7] = MP_OBJ_NEW_SMALL_INT(sb.st_atime); - t->items[8] = MP_OBJ_NEW_SMALL_INT(sb.st_mtime); - t->items[9] = MP_OBJ_NEW_SMALL_INT(sb.st_ctime); + t->items[7] = mp_obj_new_int_from_uint(sb.st_atime); + t->items[8] = mp_obj_new_int_from_uint(sb.st_mtime); + t->items[9] = mp_obj_new_int_from_uint(sb.st_ctime); return MP_OBJ_FROM_PTR(t); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_os_stat_obj, mod_os_stat); diff --git a/ports/unix/modtime.c b/ports/unix/modtime.c index 84c83da268627..e08409e20e3e9 100644 --- a/ports/unix/modtime.c +++ b/ports/unix/modtime.c @@ -132,7 +132,7 @@ STATIC mp_obj_t mod_time_sleep(mp_obj_t arg) { } STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_time_sleep_obj, mod_time_sleep); -STATIC mp_obj_t mod_time_localtime(size_t n_args, const mp_obj_t *args) { +STATIC mp_obj_t mod_time_gm_local_time(size_t n_args, const mp_obj_t *args, struct tm *(*time_func)(const time_t *timep)) { time_t t; if (n_args == 0) { t = time(NULL); @@ -144,7 +144,7 @@ STATIC mp_obj_t mod_time_localtime(size_t n_args, const mp_obj_t *args) { t = mp_obj_get_int(args[0]); #endif } - struct tm *tm = localtime(&t); + struct tm *tm = time_func(&t); mp_obj_t ret = mp_obj_new_tuple(9, NULL); @@ -165,6 +165,15 @@ STATIC mp_obj_t mod_time_localtime(size_t n_args, const mp_obj_t *args) { return ret; } + +STATIC mp_obj_t mod_time_gmtime(size_t n_args, const mp_obj_t *args) { + return mod_time_gm_local_time(n_args, args, gmtime); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_time_gmtime_obj, 0, 1, mod_time_gmtime); + +STATIC mp_obj_t mod_time_localtime(size_t n_args, const mp_obj_t *args) { + return mod_time_gm_local_time(n_args, args, localtime); +} STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_time_localtime_obj, 0, 1, mod_time_localtime); STATIC mp_obj_t mod_time_mktime(mp_obj_t tuple) { @@ -210,6 +219,8 @@ STATIC const mp_rom_map_elem_t mp_module_time_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_ticks_cpu), MP_ROM_PTR(&mp_utime_ticks_cpu_obj) }, { MP_ROM_QSTR(MP_QSTR_ticks_add), MP_ROM_PTR(&mp_utime_ticks_add_obj) }, { MP_ROM_QSTR(MP_QSTR_ticks_diff), MP_ROM_PTR(&mp_utime_ticks_diff_obj) }, + { MP_ROM_QSTR(MP_QSTR_time_ns), MP_ROM_PTR(&mp_utime_time_ns_obj) }, + { MP_ROM_QSTR(MP_QSTR_gmtime), MP_ROM_PTR(&mod_time_gmtime_obj) }, { MP_ROM_QSTR(MP_QSTR_localtime), MP_ROM_PTR(&mod_time_localtime_obj) }, { MP_ROM_QSTR(MP_QSTR_mktime), MP_ROM_PTR(&mod_time_mktime_obj) }, }; diff --git a/ports/unix/mpbthciport.c b/ports/unix/mpbthciport.c new file mode 100644 index 0000000000000..316a8831fe97b --- /dev/null +++ b/ports/unix/mpbthciport.c @@ -0,0 +1,235 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jim Mussared + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/runtime.h" +#include "py/mperrno.h" +#include "py/mphal.h" + +#if MICROPY_PY_BLUETOOTH && (MICROPY_BLUETOOTH_NIMBLE || (MICROPY_BLUETOOTH_BTSTACK && MICROPY_BLUETOOTH_BTSTACK_H4)) + +#if !MICROPY_PY_THREAD +#error Unix HCI UART requires MICROPY_PY_THREAD +#endif + +#include "extmod/modbluetooth.h" +#include "extmod/mpbthci.h" + +#include +#include + +#include +#include +#include +#include + +#define DEBUG_printf(...) // printf(__VA_ARGS__) +#define DEBUG_HCI_DUMP (0) + +uint8_t mp_bluetooth_hci_cmd_buf[4 + 256]; + +// Must be provided by the stack bindings (e.g. mpnimbleport.c or mpbtstackport.c). +extern bool mp_bluetooth_hci_poll(void); + +STATIC const useconds_t UART_POLL_INTERVAL_US = 1000; + +STATIC int uart_fd = -1; +STATIC pthread_t hci_poll_thread_id; + +STATIC void *hci_poll_thread(void *arg) { + (void)arg; + + // This will return false when the stack is shutdown. + while (mp_bluetooth_hci_poll()) { + usleep(UART_POLL_INTERVAL_US); + } + + return NULL; +} + +STATIC int configure_uart(void) { + struct termios toptions; + + // Get existing config. + if (tcgetattr(uart_fd, &toptions) < 0) { + DEBUG_printf("Couldn't get term attributes"); + return -1; + } + + // Raw mode (disable all processing). + cfmakeraw(&toptions); + + // 8N1, no parity. + toptions.c_cflag &= ~CSTOPB; + toptions.c_cflag |= CS8; + toptions.c_cflag &= ~PARENB; + + // Enable receiver, ignore modem control lines + toptions.c_cflag |= CREAD | CLOCAL; + + // Blocking, single-byte reads. + toptions.c_cc[VMIN] = 1; + toptions.c_cc[VTIME] = 0; + + // Enable HW RTS/CTS flow control. + toptions.c_iflag &= ~(IXON | IXOFF | IXANY); + toptions.c_cflag |= CRTSCTS; + + // 1Mbit (TODO: make this configurable). + speed_t brate = B1000000; + cfsetospeed(&toptions, brate); + cfsetispeed(&toptions, brate); + + // Apply immediately. + if (tcsetattr(uart_fd, TCSANOW, &toptions) < 0) { + DEBUG_printf("Couldn't set term attributes"); + + close(uart_fd); + uart_fd = -1; + + return -1; + } + + return 0; +} + +// HCI UART bindings. +int mp_bluetooth_hci_uart_init(uint32_t port, uint32_t baudrate) { + (void)port; + (void)baudrate; + + DEBUG_printf("mp_bluetooth_hci_uart_init (unix)\n"); + + char uart_device_name[256] = "/dev/ttyUSB0"; + + char *path = getenv("MICROPYBTUART"); + if (path != NULL) { + strcpy(uart_device_name, path); + } + DEBUG_printf("mp_bluetooth_hci_uart_init: Using HCI UART: %s\n", uart_device_name); + + int flags = O_RDWR | O_NOCTTY | O_NONBLOCK; + uart_fd = open(uart_device_name, flags); + if (uart_fd == -1) { + printf("mp_bluetooth_hci_uart_init: Unable to open port %s\n", uart_device_name); + return -1; + } + + if (configure_uart()) { + return -1; + } + + // Create a thread to run the polling loop. + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + pthread_create(&hci_poll_thread_id, &attr, &hci_poll_thread, NULL); + + return 0; +} + +int mp_bluetooth_hci_uart_deinit(void) { + DEBUG_printf("mp_bluetooth_hci_uart_deinit\n"); + + if (uart_fd == -1) { + return 0; + } + + // Wait for the poll loop to terminate when the state is set to OFF. + pthread_join(hci_poll_thread_id, NULL); + + // Close the UART. + close(uart_fd); + uart_fd = -1; + + return 0; +} + +int mp_bluetooth_hci_uart_set_baudrate(uint32_t baudrate) { + (void)baudrate; + DEBUG_printf("mp_bluetooth_hci_uart_set_baudrate\n"); + return 0; +} + +int mp_bluetooth_hci_uart_readchar(void) { + // DEBUG_printf("mp_bluetooth_hci_uart_readchar\n"); + + if (uart_fd == -1) { + return -1; + } + + uint8_t c; + ssize_t bytes_read = read(uart_fd, &c, 1); + + if (bytes_read == 1) { + #if DEBUG_HCI_DUMP + printf("[% 8ld] RX: %02x\n", mp_hal_ticks_ms(), c); + #endif + return c; + } else { + return -1; + } +} + +int mp_bluetooth_hci_uart_write(const uint8_t *buf, size_t len) { + // DEBUG_printf("mp_bluetooth_hci_uart_write\n"); + + if (uart_fd == -1) { + return 0; + } + + #if DEBUG_HCI_DUMP + printf("[% 8ld] TX: %02x", mp_hal_ticks_ms(), buf[0]); + for (size_t i = 1; i < len; ++i) { + printf(":%02x", buf[i]); + } + printf("\n"); + #endif + + return write(uart_fd, buf, len); +} + +// No-op implementations of HCI controller interface. +int mp_bluetooth_hci_controller_init(void) { + return 0; +} + +int mp_bluetooth_hci_controller_deinit(void) { + return 0; +} + +int mp_bluetooth_hci_controller_sleep_maybe(void) { + return 0; +} + +bool mp_bluetooth_hci_controller_woken(void) { + return true; +} + +int mp_bluetooth_hci_controller_wakeup(void) { + return 0; +} + +#endif // MICROPY_PY_BLUETOOTH && (MICROPY_BLUETOOTH_NIMBLE || (MICROPY_BLUETOOTH_BTSTACK && MICROPY_BLUETOOTH_BTSTACK_H4)) diff --git a/ports/unix/mpbtstackport.h b/ports/unix/mpbtstackport.h new file mode 100644 index 0000000000000..c82e8bd812d7a --- /dev/null +++ b/ports/unix/mpbtstackport.h @@ -0,0 +1,44 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jim Mussared + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MICROPY_INCLUDED_UNIX_BTSTACK_PORT_H +#define MICROPY_INCLUDED_UNIX_BTSTACK_PORT_H + +#define MICROPY_HW_BLE_UART_ID (0) +#define MICROPY_HW_BLE_UART_BAUDRATE (1000000) + +bool mp_bluetooth_hci_poll(void); + +#if MICROPY_BLUETOOTH_BTSTACK_H4 +void mp_bluetooth_hci_poll_h4(void); +void mp_bluetooth_btstack_port_init_h4(void); +#endif + +#if MICROPY_BLUETOOTH_BTSTACK_USB +void mp_bluetooth_btstack_port_init_usb(void); +#endif + +#endif // MICROPY_INCLUDED_UNIX_BTSTACK_PORT_H diff --git a/ports/unix/mpbtstackport_common.c b/ports/unix/mpbtstackport_common.c new file mode 100644 index 0000000000000..621e661f9eba3 --- /dev/null +++ b/ports/unix/mpbtstackport_common.c @@ -0,0 +1,96 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jim Mussared + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/runtime.h" +#include "py/mperrno.h" +#include "py/mphal.h" + +#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK + +#include "lib/btstack/src/btstack.h" + +#include "lib/btstack/platform/embedded/btstack_run_loop_embedded.h" +#include "lib/btstack/platform/embedded/hal_cpu.h" +#include "lib/btstack/platform/embedded/hal_time_ms.h" + +#include "extmod/btstack/modbluetooth_btstack.h" + +#include "mpbtstackport.h" + +// Called by the UART polling thread in mpbthciport.c, or by the USB polling thread in mpbtstackport_usb.c. +bool mp_bluetooth_hci_poll(void) { + if (mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_STARTING || mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_ACTIVE || mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_HALTING) { + // Pretend like we're running in IRQ context (i.e. other things can't be running at the same time). + mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); + #if MICROPY_BLUETOOTH_BTSTACK_H4 + mp_bluetooth_hci_poll_h4(); + #endif + btstack_run_loop_embedded_execute_once(); + MICROPY_END_ATOMIC_SECTION(atomic_state); + + return true; + } + + return false; +} + +// The IRQ functionality in btstack_run_loop_embedded.c is not used, so the +// following three functions are empty. + +void hal_cpu_disable_irqs(void) { +} + +void hal_cpu_enable_irqs(void) { +} + +void hal_cpu_enable_irqs_and_sleep(void) { +} + +uint32_t hal_time_ms(void) { + return mp_hal_ticks_ms(); +} + +void mp_bluetooth_btstack_port_init(void) { + static bool run_loop_init = false; + if (!run_loop_init) { + run_loop_init = true; + btstack_run_loop_init(btstack_run_loop_embedded_get_instance()); + } else { + btstack_run_loop_embedded_get_instance()->init(); + } + + // hci_dump_open(NULL, HCI_DUMP_STDOUT); + + #if MICROPY_BLUETOOTH_BTSTACK_H4 + mp_bluetooth_btstack_port_init_h4(); + #endif + + #if MICROPY_BLUETOOTH_BTSTACK_USB + mp_bluetooth_btstack_port_init_usb(); + #endif +} + +#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK diff --git a/ports/unix/mpbtstackport_h4.c b/ports/unix/mpbtstackport_h4.c new file mode 100644 index 0000000000000..4fdc20c22b69b --- /dev/null +++ b/ports/unix/mpbtstackport_h4.c @@ -0,0 +1,80 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jim Mussared + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "py/runtime.h" +#include "py/mperrno.h" +#include "py/mphal.h" + +#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK && MICROPY_BLUETOOTH_BTSTACK_H4 + +#include "lib/btstack/chipset/zephyr/btstack_chipset_zephyr.h" + +#include "extmod/btstack/btstack_hci_uart.h" +#include "extmod/btstack/modbluetooth_btstack.h" + +#include "mpbtstackport.h" + +#define DEBUG_printf(...) // printf(__VA_ARGS__) + +STATIC hci_transport_config_uart_t hci_transport_config_uart = { + HCI_TRANSPORT_CONFIG_UART, + 1000000, // initial baudrate + 0, // main baudrate + 1, // flow control + NULL, // device name +}; + +void mp_bluetooth_hci_poll_h4(void) { + if (mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_STARTING || mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_ACTIVE) { + mp_bluetooth_btstack_hci_uart_process(); + } +} + +void mp_bluetooth_btstack_port_init_h4(void) { + DEBUG_printf("mp_bluetooth_btstack_port_init_h4\n"); + + const hci_transport_t *transport = hci_transport_h4_instance(&mp_bluetooth_btstack_hci_uart_block); + hci_init(transport, &hci_transport_config_uart); + + hci_set_chipset(btstack_chipset_zephyr_instance()); +} + +void mp_bluetooth_btstack_port_deinit(void) { + DEBUG_printf("mp_bluetooth_btstack_port_deinit\n"); + + hci_power_control(HCI_POWER_OFF); + hci_close(); +} + +void mp_bluetooth_btstack_port_start(void) { + DEBUG_printf("mp_bluetooth_btstack_port_start\n"); + + hci_power_control(HCI_POWER_ON); +} + +#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK && MICROPY_BLUETOOTH_BTSTACK_H4 diff --git a/ports/unix/btstack_usb.c b/ports/unix/mpbtstackport_usb.c similarity index 52% rename from ports/unix/btstack_usb.c rename to ports/unix/mpbtstackport_usb.c index da9d72fe15258..28d2c8c543f71 100644 --- a/ports/unix/btstack_usb.c +++ b/ports/unix/mpbtstackport_usb.c @@ -31,7 +31,7 @@ #include "py/mperrno.h" #include "py/mphal.h" -#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK +#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK && MICROPY_BLUETOOTH_BTSTACK_USB #include "lib/btstack/src/btstack.h" #include "lib/btstack/platform/embedded/btstack_run_loop_embedded.h" @@ -40,83 +40,34 @@ #include "extmod/btstack/modbluetooth_btstack.h" +#include "mpbtstackport.h" + #if !MICROPY_PY_THREAD #error Unix btstack requires MICROPY_PY_THREAD #endif STATIC const useconds_t USB_POLL_INTERVAL_US = 1000; -STATIC const uint8_t read_static_address_command_complete_prefix[] = { 0x0e, 0x1b, 0x01, 0x09, 0xfc }; - -STATIC uint8_t local_addr[6] = {0}; -STATIC uint8_t static_address[6] = {0}; -STATIC volatile bool have_addr = false; -STATIC bool using_static_address = false; - -STATIC btstack_packet_callback_registration_t hci_event_callback_registration; - -STATIC void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { - (void)channel; - (void)size; - if (packet_type != HCI_EVENT_PACKET) { - return; - } - switch (hci_event_packet_get_type(packet)) { - case BTSTACK_EVENT_STATE: - if (btstack_event_state_get_state(packet) != HCI_STATE_WORKING) { - return; +void mp_bluetooth_btstack_port_init_usb(void) { + // MICROPYBTUSB can be a ':'' or '-' separated port list. + char *path = getenv("MICROPYBTUSB"); + if (path != NULL) { + uint8_t usb_path[7] = {0}; + size_t usb_path_len = 0; + + while (usb_path_len < MP_ARRAY_SIZE(usb_path)) { + char *delimiter; + usb_path[usb_path_len++] = strtol(path, &delimiter, 16); + if (!delimiter || (*delimiter != ':' && *delimiter != '-')) { + break; } - gap_local_bd_addr(local_addr); - if (using_static_address) { - memcpy(local_addr, static_address, sizeof(local_addr)); - } - have_addr = true; - break; - case HCI_EVENT_COMMAND_COMPLETE: - if (memcmp(packet, read_static_address_command_complete_prefix, sizeof(read_static_address_command_complete_prefix)) == 0) { - reverse_48(&packet[7], static_address); - gap_random_address_set(static_address); - using_static_address = true; - have_addr = true; - } - break; - default: - break; - } -} - -// The IRQ functionality in btstack_run_loop_embedded.c is not used, so the -// following three functions are empty. - -void hal_cpu_disable_irqs(void) { -} - -void hal_cpu_enable_irqs(void) { -} - -void hal_cpu_enable_irqs_and_sleep(void) { -} - -uint32_t hal_time_ms(void) { - return mp_hal_ticks_ms(); -} + path = delimiter + 1; + } -void mp_bluetooth_btstack_port_init(void) { - static bool run_loop_init = false; - if (!run_loop_init) { - run_loop_init = true; - btstack_run_loop_init(btstack_run_loop_embedded_get_instance()); - } else { - btstack_run_loop_embedded_get_instance()->init(); + hci_transport_usb_set_path(usb_path_len, usb_path); } - // TODO: allow setting USB device path via cmdline/env var. - - // hci_dump_open(NULL, HCI_DUMP_STDOUT); hci_init(hci_transport_usb_instance(), NULL); - - hci_event_callback_registration.callback = &packet_handler; - hci_add_event_handler(&hci_event_callback_registration); } STATIC pthread_t bstack_thread_id; @@ -126,9 +77,12 @@ void mp_bluetooth_btstack_port_deinit(void) { // Wait for the poll loop to terminate when the state is set to OFF. pthread_join(bstack_thread_id, NULL); - have_addr = false; } + +// Provided by mpbstackport_common.c. +extern bool mp_bluetooth_hci_poll(void); + STATIC void *btstack_thread(void *arg) { (void)arg; hci_power_control(HCI_POWER_ON); @@ -139,16 +93,15 @@ STATIC void *btstack_thread(void *arg) { // in modbluetooth_btstack.c setting the state back to OFF. // Or, if a timeout results in it being set to TIMEOUT. - while (mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_STARTING || mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_ACTIVE) { - btstack_run_loop_embedded_execute_once(); + while (true) { + if (!mp_bluetooth_hci_poll()) { + break; + } // The USB transport schedules events to the run loop at 1ms intervals, // and the implementation currently polls rather than selects. usleep(USB_POLL_INTERVAL_US); } - - hci_close(); - return NULL; } @@ -160,13 +113,4 @@ void mp_bluetooth_btstack_port_start(void) { pthread_create(&bstack_thread_id, &attr, &btstack_thread, NULL); } -void mp_hal_get_mac(int idx, uint8_t buf[6]) { - if (idx == MP_HAL_MAC_BDADDR) { - if (!have_addr) { - mp_raise_OSError(MP_ENODEV); - } - memcpy(buf, local_addr, sizeof(local_addr)); - } -} - -#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK +#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK && MICROPY_BLUETOOTH_BTSTACK_USB diff --git a/ports/unix/mpconfigport.h b/ports/unix/mpconfigport.h index f3c61c18f100d..17f4895573ed5 100644 --- a/ports/unix/mpconfigport.h +++ b/ports/unix/mpconfigport.h @@ -87,6 +87,7 @@ #define MICROPY_VFS_POSIX_FILE (1) #define MICROPY_PY_FUNCTION_ATTRS (1) #define MICROPY_PY_DESCRIPTORS (1) +#define MICROPY_PY_DELATTR_SETATTR (1) #define MICROPY_PY_BUILTINS_STR_UNICODE (1) #define MICROPY_PY_BUILTINS_STR_CENTER (1) #define MICROPY_PY_BUILTINS_STR_PARTITION (1) @@ -176,6 +177,9 @@ #define MICROPY_ERROR_PRINTER (&mp_stderr_print) #define MICROPY_PY_STR_BYTES_CMP_WARN (1) +// VFS stat functions should return time values relative to 1970/1/1 +#define MICROPY_EPOCH_IS_1970 (1) + extern const struct _mp_print_t mp_stderr_print; #if !(defined(MICROPY_GCREGS_SETJMP) || defined(__x86_64__) || defined(__i386__) || defined(__thumb2__) || defined(__thumb__) || defined(__arm__)) @@ -306,9 +310,16 @@ void mp_unix_mark_exec(void); #define MP_STATE_PORT MP_STATE_VM -#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK +#if MICROPY_PY_BLUETOOTH +#if MICROPY_BLUETOOTH_BTSTACK struct _mp_bluetooth_btstack_root_pointers_t; #define MICROPY_BLUETOOTH_ROOT_POINTERS struct _mp_bluetooth_btstack_root_pointers_t *bluetooth_btstack_root_pointers; +#endif +#if MICROPY_BLUETOOTH_NIMBLE +struct _mp_bluetooth_nimble_root_pointers_t; +struct _mp_bluetooth_nimble_malloc_t; +#define MICROPY_BLUETOOTH_ROOT_POINTERS struct _mp_bluetooth_nimble_malloc_t *bluetooth_nimble_memory; struct _mp_bluetooth_nimble_root_pointers_t *bluetooth_nimble_root_pointers; +#endif #else #define MICROPY_BLUETOOTH_ROOT_POINTERS #endif @@ -348,7 +359,7 @@ struct _mp_bluetooth_btstack_root_pointers_t; #endif #if MICROPY_PY_THREAD -#define MICROPY_BEGIN_ATOMIC_SECTION() (mp_thread_unix_begin_atomic_section(), 0) +#define MICROPY_BEGIN_ATOMIC_SECTION() (mp_thread_unix_begin_atomic_section(), 0xffffffff) #define MICROPY_END_ATOMIC_SECTION(x) (void)x; mp_thread_unix_end_atomic_section() #endif diff --git a/ports/unix/mpnimbleport.c b/ports/unix/mpnimbleport.c new file mode 100644 index 0000000000000..8961910098c42 --- /dev/null +++ b/ports/unix/mpnimbleport.c @@ -0,0 +1,84 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jim Mussared + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/runtime.h" +#include "py/mperrno.h" +#include "py/mphal.h" + +#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE + +#include "nimble/nimble_npl.h" + +#include "extmod/nimble/modbluetooth_nimble.h" +#include "extmod/nimble/hal/hal_uart.h" + +#define DEBUG_printf(...) // printf(__VA_ARGS__) + +// Called by the UART polling thread in mpbthciport.c. +bool mp_bluetooth_hci_poll(void) { + // DEBUG_printf("mp_bluetooth_hci_poll (unix nimble) %d\n", mp_bluetooth_nimble_ble_state); + + if (mp_bluetooth_nimble_ble_state == MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) { + DEBUG_printf("mp_bluetooth_hci_poll (unix nimble) -- shutdown\n"); + return false; + } + + if (mp_bluetooth_nimble_ble_state >= MP_BLUETOOTH_NIMBLE_BLE_STATE_WAITING_FOR_SYNC) { + + // Pretend like we're running in IRQ context (i.e. other things can't be running at the same time). + mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); + + // Ask NimBLE to process UART data. + mp_bluetooth_nimble_hci_uart_process(); + + // Run pending background operations and events, but only after HCI sync. + mp_bluetooth_nimble_os_callout_process(); + mp_bluetooth_nimble_os_eventq_run_all(); + + MICROPY_END_ATOMIC_SECTION(atomic_state); + } + + return true; +} + +// Extra port-specific helpers. +void mp_bluetooth_nimble_hci_uart_wfi(void) { + // DEBUG_printf("mp_bluetooth_nimble_hci_uart_wfi\n"); + // TODO: this should do a select() on the uart_fd. +} + +uint32_t mp_bluetooth_nimble_hci_uart_enter_critical(void) { + // DEBUG_printf("mp_bluetooth_nimble_hci_uart_enter_critical\n"); + MICROPY_PY_BLUETOOTH_ENTER + return atomic_state; // Always 0xffffffff +} + +void mp_bluetooth_nimble_hci_uart_exit_critical(uint32_t atomic_state) { + MICROPY_PY_BLUETOOTH_EXIT + // DEBUG_printf("mp_bluetooth_nimble_hci_uart_exit_critical\n"); +} + +#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE diff --git a/ports/unix/mpnimbleport.h b/ports/unix/mpnimbleport.h new file mode 100644 index 0000000000000..a2935e6fdf44a --- /dev/null +++ b/ports/unix/mpnimbleport.h @@ -0,0 +1,33 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jim Mussared + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MICROPY_INCLUDED_UNIX_NIMBLE_PORT_H +#define MICROPY_INCLUDED_UNIX_NIMBLE_PORT_H + +#define MICROPY_HW_BLE_UART_ID (0) +#define MICROPY_HW_BLE_UART_BAUDRATE (1000000) + +#endif // MICROPY_INCLUDED_UNIX_NIMBLE_PORT_H diff --git a/ports/unix/mpthreadport.c b/ports/unix/mpthreadport.c index 711cf2a6bbffa..de0f5923bafe9 100644 --- a/ports/unix/mpthreadport.c +++ b/ports/unix/mpthreadport.c @@ -65,7 +65,7 @@ STATIC pthread_key_t tls_key; // The mutex is used for any code in this port that needs to be thread safe. // Specifically for thread management, access to the linked list is one example. // But also, e.g. scheduler state. -STATIC pthread_mutex_t thread_mutex = PTHREAD_MUTEX_INITIALIZER; +STATIC pthread_mutex_t thread_mutex; STATIC thread_t *thread; // this is used to synchronise the signal handler of the thread @@ -111,6 +111,13 @@ void mp_thread_init(void) { pthread_key_create(&tls_key, NULL); pthread_setspecific(tls_key, &mp_state_ctx.thread); + // Needs to be a recursive mutex to emulate the behavior of + // BEGIN_ATOMIC_SECTION on bare metal. + pthread_mutexattr_t thread_mutex_attr; + pthread_mutexattr_init(&thread_mutex_attr); + pthread_mutexattr_settype(&thread_mutex_attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&thread_mutex, &thread_mutex_attr); + // create first entry in linked list of all threads thread = malloc(sizeof(thread_t)); thread->id = pthread_self(); diff --git a/ports/unix/unix_mphal.c b/ports/unix/unix_mphal.c index 42c22f7b50482..3fe2fa9fec21d 100644 --- a/ports/unix/unix_mphal.c +++ b/ports/unix/unix_mphal.c @@ -214,3 +214,9 @@ mp_uint_t mp_hal_ticks_us(void) { return tv.tv_sec * 1000000 + tv.tv_usec; #endif } + +uint64_t mp_hal_time_ns(void) { + struct timeval tv; + gettimeofday(&tv, NULL); + return (uint64_t)tv.tv_sec * 1000000000ULL + (uint64_t)tv.tv_usec * 1000ULL; +} diff --git a/ports/unix/coverage-frzmpy/frzmpy1.py b/ports/unix/variants/coverage/frzmpy/frzmpy1.py similarity index 100% rename from ports/unix/coverage-frzmpy/frzmpy1.py rename to ports/unix/variants/coverage/frzmpy/frzmpy1.py diff --git a/ports/unix/coverage-frzmpy/frzmpy2.py b/ports/unix/variants/coverage/frzmpy/frzmpy2.py similarity index 100% rename from ports/unix/coverage-frzmpy/frzmpy2.py rename to ports/unix/variants/coverage/frzmpy/frzmpy2.py diff --git a/ports/unix/coverage-frzmpy/frzmpy_pkg1/__init__.py b/ports/unix/variants/coverage/frzmpy/frzmpy_pkg1/__init__.py similarity index 100% rename from ports/unix/coverage-frzmpy/frzmpy_pkg1/__init__.py rename to ports/unix/variants/coverage/frzmpy/frzmpy_pkg1/__init__.py diff --git a/ports/unix/coverage-frzmpy/frzmpy_pkg2/mod.py b/ports/unix/variants/coverage/frzmpy/frzmpy_pkg2/mod.py similarity index 100% rename from ports/unix/coverage-frzmpy/frzmpy_pkg2/mod.py rename to ports/unix/variants/coverage/frzmpy/frzmpy_pkg2/mod.py diff --git a/ports/unix/coverage-frzmpy/frzqstr.py b/ports/unix/variants/coverage/frzmpy/frzqstr.py similarity index 100% rename from ports/unix/coverage-frzmpy/frzqstr.py rename to ports/unix/variants/coverage/frzmpy/frzqstr.py diff --git a/ports/unix/coverage-frzstr/frzstr1.py b/ports/unix/variants/coverage/frzstr/frzstr1.py similarity index 100% rename from ports/unix/coverage-frzstr/frzstr1.py rename to ports/unix/variants/coverage/frzstr/frzstr1.py diff --git a/ports/unix/coverage-frzstr/frzstr_pkg1/__init__.py b/ports/unix/variants/coverage/frzstr/frzstr_pkg1/__init__.py similarity index 100% rename from ports/unix/coverage-frzstr/frzstr_pkg1/__init__.py rename to ports/unix/variants/coverage/frzstr/frzstr_pkg1/__init__.py diff --git a/ports/unix/coverage-frzstr/frzstr_pkg2/mod.py b/ports/unix/variants/coverage/frzstr/frzstr_pkg2/mod.py similarity index 100% rename from ports/unix/coverage-frzstr/frzstr_pkg2/mod.py rename to ports/unix/variants/coverage/frzstr/frzstr_pkg2/mod.py diff --git a/ports/unix/variants/coverage/manifest.py b/ports/unix/variants/coverage/manifest.py new file mode 100644 index 0000000000000..6111050884222 --- /dev/null +++ b/ports/unix/variants/coverage/manifest.py @@ -0,0 +1,2 @@ +freeze_as_str("frzstr") +freeze_as_mpy("frzmpy") diff --git a/ports/unix/variants/coverage/mpconfigvariant.h b/ports/unix/variants/coverage/mpconfigvariant.h index 802c2fe5f7203..942117608fab1 100644 --- a/ports/unix/variants/coverage/mpconfigvariant.h +++ b/ports/unix/variants/coverage/mpconfigvariant.h @@ -30,6 +30,7 @@ #define MICROPY_VFS (1) #define MICROPY_PY_UOS_VFS (1) +#define MICROPY_DEBUG_PARSE_RULE_NAME (1) #define MICROPY_OPT_MATH_FACTORIAL (1) #define MICROPY_FLOAT_HIGH_QUALITY_HASH (1) #define MICROPY_ENABLE_SCHEDULER (1) @@ -39,6 +40,7 @@ #define MICROPY_WARNINGS_CATEGORY (1) #define MICROPY_MODULE_GETATTR (1) #define MICROPY_PY_DELATTR_SETATTR (1) +#define MICROPY_PY_ALL_INPLACE_SPECIAL_METHODS (1) #define MICROPY_PY_REVERSE_SPECIAL_METHODS (1) #define MICROPY_PY_BUILTINS_MEMORYVIEW_ITEMSIZE (1) #define MICROPY_PY_BUILTINS_NEXT2 (1) diff --git a/ports/unix/variants/coverage/mpconfigvariant.mk b/ports/unix/variants/coverage/mpconfigvariant.mk index ddb5027a97ef7..f11d0b0d28f75 100644 --- a/ports/unix/variants/coverage/mpconfigvariant.mk +++ b/ports/unix/variants/coverage/mpconfigvariant.mk @@ -11,10 +11,9 @@ CFLAGS += \ LDFLAGS += -fprofile-arcs -ftest-coverage -FROZEN_MANIFEST = manifest_coverage.py +FROZEN_MANIFEST ?= $(VARIANT_DIR)/manifest.py MICROPY_ROM_TEXT_COMPRESSION = 1 MICROPY_VFS_FAT = 1 MICROPY_VFS_LFS1 = 1 MICROPY_VFS_LFS2 = 1 -MICROPY_PY_BLUETOOTH = 1 diff --git a/ports/unix/variants/dev/manifest.py b/ports/unix/variants/dev/manifest.py new file mode 100644 index 0000000000000..92a681116a687 --- /dev/null +++ b/ports/unix/variants/dev/manifest.py @@ -0,0 +1,3 @@ +include("$(PORT_DIR)/variants/manifest.py") + +include("$(MPY_DIR)/extmod/uasyncio/manifest.py") diff --git a/ports/unix/variants/dev/mpconfigvariant.h b/ports/unix/variants/dev/mpconfigvariant.h index eb65134717c6d..7c3e84cc44e11 100644 --- a/ports/unix/variants/dev/mpconfigvariant.h +++ b/ports/unix/variants/dev/mpconfigvariant.h @@ -24,9 +24,22 @@ * THE SOFTWARE. */ -#define MICROPY_REPL_EMACS_WORDS_MOVE (1) -#define MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE (1) -#define MICROPY_ENABLE_SCHEDULER (1) +#define MICROPY_READER_VFS (1) +#define MICROPY_REPL_EMACS_WORDS_MOVE (1) +#define MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE (1) +#define MICROPY_ENABLE_SCHEDULER (1) +#define MICROPY_VFS (1) +#define MICROPY_VFS_POSIX (1) -#define MICROPY_PY_SYS_SETTRACE (1) -#define MICROPY_PY_URANDOM_EXTRA_FUNCS (1) +#define MICROPY_PY_SYS_SETTRACE (1) +#define MICROPY_PY_UOS_VFS (1) +#define MICROPY_PY_URANDOM_EXTRA_FUNCS (1) + +#ifndef MICROPY_PY_UASYNCIO +#define MICROPY_PY_UASYNCIO (1) +#endif + +// Use vfs's functions for import stat and builtin open. +#define mp_import_stat mp_vfs_import_stat +#define mp_builtin_open mp_vfs_open +#define mp_builtin_open_obj mp_vfs_open_obj diff --git a/ports/unix/variants/dev/mpconfigvariant.mk b/ports/unix/variants/dev/mpconfigvariant.mk index bbd30b6238248..91bd28da9bd85 100644 --- a/ports/unix/variants/dev/mpconfigvariant.mk +++ b/ports/unix/variants/dev/mpconfigvariant.mk @@ -1,5 +1,10 @@ PROG ?= micropython-dev +FROZEN_MANIFEST ?= $(VARIANT_DIR)/manifest.py + MICROPY_ROM_TEXT_COMPRESSION = 1 +MICROPY_VFS_FAT = 1 +MICROPY_VFS_LFS1 = 1 +MICROPY_VFS_LFS2 = 1 -MICROPY_PY_BLUETOOTH = 1 +MICROPY_PY_BLUETOOTH ?= 1 diff --git a/ports/unix/variants/fast/mpconfigvariant.h b/ports/unix/variants/fast/mpconfigvariant.h index 6d73275fdebfc..8a531b056ab71 100644 --- a/ports/unix/variants/fast/mpconfigvariant.h +++ b/ports/unix/variants/fast/mpconfigvariant.h @@ -32,5 +32,3 @@ // 91 is a magic number proposed by @dpgeorge, which make pystone run ~ at tie // with CPython 3.4. #define MICROPY_MODULE_DICT_SIZE (91) - -#include "variants/DEV/mpconfigvariant.h" diff --git a/ports/unix/variants/fast/mpconfigvariant.mk b/ports/unix/variants/fast/mpconfigvariant.mk index d67f7c8f38388..595e5756457c9 100644 --- a/ports/unix/variants/fast/mpconfigvariant.mk +++ b/ports/unix/variants/fast/mpconfigvariant.mk @@ -1,6 +1,6 @@ # build synthetically fast interpreter for benchmarking -COPT += "-fno-crossjumping -O2" +COPT += -fno-crossjumping -O2 PROG = micropython-fast diff --git a/ports/unix/variants/freedos/mpconfigvariant.h b/ports/unix/variants/freedos/mpconfigvariant.h index 338b34492de7f..562c783ca35fb 100644 --- a/ports/unix/variants/freedos/mpconfigvariant.h +++ b/ports/unix/variants/freedos/mpconfigvariant.h @@ -36,5 +36,3 @@ #undef _DIRENT_HAVE_D_INO #define MICROPY_USE_INTERNAL_ERRNO (1) - -#include "variants/DEV/mpconfigvariant.h" diff --git a/ports/unix/manifest.py b/ports/unix/variants/manifest.py similarity index 100% rename from ports/unix/manifest.py rename to ports/unix/variants/manifest.py diff --git a/ports/windows/.appveyor.yml b/ports/windows/.appveyor.yml index 2e0dbdea5d116..4d2d6bd11dc87 100644 --- a/ports/windows/.appveyor.yml +++ b/ports/windows/.appveyor.yml @@ -1,3 +1,7 @@ +image: Visual Studio 2013 +clone_depth: 1 +skip_tags: true + environment: # Python version used MICROPY_CPYTHON3: c:/python34/python.exe @@ -34,7 +38,15 @@ test_script: - ps: | cd (Join-Path $env:APPVEYOR_BUILD_FOLDER 'tests') & $env:MICROPY_CPYTHON3 run-tests + if ($LASTEXITCODE -ne 0) { + & $env:MICROPY_CPYTHON3 run-tests --print-failures + throw "Test failure" + } & $env:MICROPY_CPYTHON3 run-tests --via-mpy -d basics float micropython + if ($LASTEXITCODE -ne 0) { + & $env:MICROPY_CPYTHON3 run-tests --print-failures + throw "Test failure" + } # After the build/test phase for the MSVC build completes, # build and test with mingw-w64, release versions only. @@ -65,16 +77,11 @@ after_test: } & $env:MICROPY_CPYTHON3 $testArgs if ($LASTEXITCODE -ne 0) { - throw "$env:MSYSTEM tests exited with code $LASTEXITCODE" + & $env:MICROPY_CPYTHON3 run-tests --print-failures + throw "Test failure" } & $env:MICROPY_CPYTHON3 ($testArgs + @('--via-mpy', '-d', 'basics', 'float', 'micropython')) if ($LASTEXITCODE -ne 0) { - throw "$env:MSYSTEM mpy-cross tests exited with code $LASTEXITCODE" + & $env:MICROPY_CPYTHON3 run-tests --print-failures + throw "Test failure" } - -skip_tags: true - -deploy: off - -nuget: - disable_publish_on_pr: true diff --git a/ports/windows/Makefile b/ports/windows/Makefile index b2d11872ed3a6..3bfbc18304818 100644 --- a/ports/windows/Makefile +++ b/ports/windows/Makefile @@ -58,4 +58,9 @@ SRC_QSTR += $(SRC_C) # SRC_QSTR SRC_QSTR_AUTO_DEPS += +ifneq ($(FROZEN_MANIFEST),) +CFLAGS += -DMICROPY_QSTR_EXTRA_POOL=mp_qstr_frozen_const_pool -DMICROPY_MODULE_FROZEN_MPY=1 -DMPZ_DIG_SIZE=16 +MPY_CROSS_FLAGS += -mcache-lookup-bc +endif + include $(TOP)/py/mkrules.mk diff --git a/ports/windows/README.md b/ports/windows/README.md index f1bd405513b99..8d907d1b7243a 100644 --- a/ports/windows/README.md +++ b/ports/windows/README.md @@ -3,46 +3,73 @@ It is based on Unix port, and expected to remain so. The port requires additional testing, debugging, and patches. Please consider to contribute. +All gcc-based builds use the gcc compiler from [Mingw-w64](mingw-w64.org), +which is the advancement of the original mingw project. The latter is +getting obsolete and is not actively supported by MicroPython. + +Build instruction assume you're in the ports/windows directory. Building on Debian/Ubuntu Linux system --------------------------------------- - sudo apt-get install gcc-mingw-w64 + sudo apt-get install python3 build-essential gcc-mingw-w64 + make -C ../../mpy-cross make CROSS_COMPILE=i686-w64-mingw32- -If for some reason the mingw-w64 crosscompiler is not available, you can try -mingw32 instead, but it comes with a really old gcc which may produce some -spurious errors (you may need to disable -Werror): - - sudo apt-get install mingw32 mingw32-binutils mingw32-runtime - make CROSS_COMPILE=i586-mingw32msvc- - Building under Cygwin --------------------- -Install following packages using cygwin's setup.exe: +Install Cygwin, then install following packages using Cygwin's setup.exe: * mingw64-i686-gcc-core * mingw64-x86_64-gcc-core * make +Also install the python3 package, or install Python globally for Windows (see below). + Build using: + make -C ../../mpy-cross CROSS_COMPILE=i686-w64-mingw32- make CROSS_COMPILE=i686-w64-mingw32- Or for 64bit: + make -C ../../mpy-cross CROSS_COMPILE=x86_64-w64-mingw32- make CROSS_COMPILE=x86_64-w64-mingw32- +Building under MSYS2 +-------------------- + +Install MSYS2 from http://repo.msys2.org/distrib, start the msys2.exe shell and +install the build tools: + + pacman -Syuu + pacman -S make mingw-w64-x86_64-gcc pkg-config python3 + +Start the mingw64.exe shell and build: + + make -C ../../mpy-cross STRIP=echo SIZE=echo + make + + Building using MS Visual Studio 2013 (or higher) ------------------------------------------------ -In the IDE, open `micropython.vcxproj` and build. +Install Python. There are several ways to do this, for example: download and install the +latest Python 3 release from https://www.python.org/downloads/windows or from +https://docs.conda.io/en/latest/miniconda.html, +or open the Microsoft Store app and search for Python and install it. + +Install Visual Studio and the C++ toolset (for recent versions: install +the free Visual Studio Community edition and the *Desktop development with C++* workload). + +In the IDE, open `micropython-cross.vcxproj` and `micropython.vcxproj` and build. To build from the command line: + msbuild ../../mpy-cross/mpy-cross.vcxproj msbuild micropython.vcxproj __Stack usage__ @@ -57,6 +84,19 @@ There are several ways to deal with this: See [issue 2927](https://github.com/micropython/micropython/issues/2927) for more information. +Running the tests +----------------- + +This is similar for all ports: + + cd ../../tests + python3 ./run-tests + +Depending on the combination of platform and Python version used it might be +needed to first set the MICROPY_MICROPYTHON environment variable to +the full path of micropython.exe. + + Running on Linux using Wine --------------------------- diff --git a/ports/windows/micropython.vcxproj b/ports/windows/micropython.vcxproj index f70fe96158d07..73a837a8408ab 100644 --- a/ports/windows/micropython.vcxproj +++ b/ports/windows/micropython.vcxproj @@ -104,7 +104,7 @@ - + diff --git a/ports/windows/mpconfigport.h b/ports/windows/mpconfigport.h index 84c196e11fa9c..faf10752ee203 100644 --- a/ports/windows/mpconfigport.h +++ b/ports/windows/mpconfigport.h @@ -123,6 +123,9 @@ #define MICROPY_WARNINGS (1) #define MICROPY_PY_STR_BYTES_CMP_WARN (1) +// VFS stat functions should return time values relative to 1970/1/1 +#define MICROPY_EPOCH_IS_1970 (1) + extern const struct _mp_print_t mp_stderr_print; #ifdef _MSC_VER @@ -227,6 +230,16 @@ extern const struct _mp_obj_module_t mp_module_time; #define MP_SSIZE_MAX _I32_MAX #endif +// VC++ 12.0 fixes +#if (_MSC_VER <= 1800) +#define MICROPY_PY_MATH_ATAN2_FIX_INFNAN (1) +#define MICROPY_PY_MATH_FMOD_FIX_INFNAN (1) +#ifdef _WIN64 +#define MICROPY_PY_MATH_MODF_FIX_NEGZERO (1) +#else +#define MICROPY_PY_MATH_POW_FIX_NAN (1) +#endif +#endif // CL specific definitions diff --git a/ports/windows/msvc/genhdr.targets b/ports/windows/msvc/genhdr.targets index 327f922e4a162..3af0ea263699b 100644 --- a/ports/windows/msvc/genhdr.targets +++ b/ports/windows/msvc/genhdr.targets @@ -55,7 +55,7 @@ using(var outFile = System.IO.File.CreateText(OutputFile)) { - + $([System.String]::new('%(FullPath)').Replace('$(PyBaseDir)', '$(DestDir)qstr\')) @@ -101,8 +101,8 @@ using(var outFile = System.IO.File.CreateText(OutputFile)) { $(QstrGen).tmp - - + + @@ -115,6 +115,17 @@ using(var outFile = System.IO.File.CreateText(OutputFile)) { + + + + + MICROPY_MODULE_FROZEN_MPY=1;MICROPY_QSTR_EXTRA_POOL=mp_qstr_frozen_const_pool;%(PreprocessorDefinitions) + + + + + + diff --git a/ports/windows/windows_mphal.c b/ports/windows/windows_mphal.c index 8ed087358f567..b442b59147e4e 100644 --- a/ports/windows/windows_mphal.c +++ b/ports/windows/windows_mphal.c @@ -255,3 +255,9 @@ mp_uint_t mp_hal_ticks_cpu(void) { return value.LowPart; #endif } + +uint64_t mp_hal_time_ns(void) { + struct timeval tv; + gettimeofday(&tv, NULL); + return (uint64_t)tv.tv_sec * 1000000000ULL + (uint64_t)tv.tv_usec * 1000ULL; +} diff --git a/ports/zephyr/CMakeLists.txt b/ports/zephyr/CMakeLists.txt index 017b0689cef58..50cd031d4dd08 100644 --- a/ports/zephyr/CMakeLists.txt +++ b/ports/zephyr/CMakeLists.txt @@ -1,6 +1,6 @@ -cmake_minimum_required(VERSION 3.8) +cmake_minimum_required(VERSION 3.13.1) -include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) project(NONE) target_sources(app PRIVATE src/zephyr_start.c src/zephyr_getchar.c) diff --git a/ports/zephyr/Makefile b/ports/zephyr/Makefile index 4fe6552846aab..169aebc26772e 100644 --- a/ports/zephyr/Makefile +++ b/ports/zephyr/Makefile @@ -52,6 +52,7 @@ SRC_C = main.c \ uart_core.c \ zephyr_storage.c \ lib/timeutils/timeutils.c \ + lib/utils/mpirq.c \ lib/utils/stdout_helpers.c \ lib/utils/printf.c \ lib/utils/pyexec.c \ diff --git a/ports/zephyr/README.md b/ports/zephyr/README.md index 6bcbccd8b36a3..d69030dc2c712 100644 --- a/ports/zephyr/README.md +++ b/ports/zephyr/README.md @@ -1,10 +1,11 @@ MicroPython port to Zephyr RTOS =============================== -This is an work-in-progress port of MicroPython to Zephyr RTOS +This is a work-in-progress port of MicroPython to Zephyr RTOS (http://zephyrproject.org). -This port requires Zephyr version 1.8 or higher. All boards supported +This port requires Zephyr version 2.4.0, and may also work on higher +versions. All boards supported by Zephyr (with standard level of features support, like UART console) should work with MicroPython (but not all were tested). @@ -12,12 +13,14 @@ Features supported at this time: * REPL (interactive prompt) over Zephyr UART console. * `utime` module for time measurements and delays. -* `machine.Pin` class for GPIO control. +* `machine.Pin` class for GPIO control, with IRQ support. * `machine.I2C` class for I2C control. * `usocket` module for networking (IPv4/IPv6). * "Frozen modules" support to allow to bundle Python modules together with firmware. Including complete applications, including with run-on-boot capability. +* virtual filesystem with FAT and littlefs formats, backed by either + DiskAccess or FlashArea (flash map). Over time, bindings for various Zephyr subsystems may be added. @@ -28,17 +31,35 @@ Building Follow to Zephyr web site for Getting Started instruction of installing Zephyr SDK, getting Zephyr source code, and setting up development environment. (Direct link: -https://www.zephyrproject.org/doc/getting_started/getting_started.html). +https://docs.zephyrproject.org/latest/getting_started/index.html). You may want to build Zephyr's own sample applications to make sure your setup is correct. -To build MicroPython port, in the port subdirectory (zephyr/), run: +If you already have Zephyr installed but are having issues building the +MicroPython port then try installing the correct version of Zephyr via: - make BOARD= + $ west init zephyrproject -m https://github.com/zephyrproject-rtos/zephyr --mr v2.4.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 v2.4.0 + $ west update + +With Zephyr installed you may then need to configure your environment, +for example by sourcing `zephyrproject/zephyr/zephyr-env.sh`. + +Once Zephyr is ready to use you can build the MicroPython port. +In the port subdirectory `ports/zephyr/` run: + + $ make BOARD= If you don't specify BOARD, the default is `qemu_x86` (x86 target running -in QEMU emulator). Consult Zephyr documentation above for the list of -supported boards. +in QEMU emulator). Consult the Zephyr documentation above for the list of +supported boards. Board configuration files appearing in `ports/zephyr/boards/` +correspond to boards that have been tested with MicroPython and may have +additional options enabled, like filesystem support. Running @@ -66,6 +87,10 @@ cf. for example QEMU networking requirements above; real hardware boards generally should not have any special requirements, unless there're known issues). +For example, to deploy firmware on the FRDM-K64F board run: + + $ make BOARD=frdm_k64f flash + Quick example ------------- @@ -89,6 +114,19 @@ reference materials). To execute the above sample, copy it to clipboard, in MicroPython REPL enter "paste mode" using Ctrl+E, paste clipboard, press Ctrl+D to finish paste mode and start execution. +To respond to Pin change IRQs, on a FRDM-K64F board run: + + from machine import Pin + + SW2 = Pin(("GPIO_2", 6), Pin.IN) + SW3 = Pin(("GPIO_0", 4), Pin.IN) + + SW2.irq(lambda t: print("SW2 changed")) + SW3.irq(lambda t: print("SW3 changed")) + + while True: + pass + Example of using I2C to scan for I2C slaves: from machine import I2C diff --git a/ports/zephyr/machine_i2c.c b/ports/zephyr/machine_i2c.c index f00c1d34225ac..ec4b8620a5014 100644 --- a/ports/zephyr/machine_i2c.c +++ b/ports/zephyr/machine_i2c.c @@ -37,21 +37,22 @@ #include "py/mphal.h" #include "py/mperrno.h" #include "extmod/machine_i2c.h" - -STATIC const mp_obj_type_t machine_hard_i2c_type; +#include "modmachine.h" typedef struct _machine_hard_i2c_obj_t { mp_obj_base_t base; - struct device *dev; + const struct device *dev; bool restart; } machine_hard_i2c_obj_t; STATIC void machine_hard_i2c_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { machine_hard_i2c_obj_t *self = self_in; - mp_printf(print, "%s", self->dev->config->name); + mp_printf(print, "%s", self->dev->name); } mp_obj_t machine_hard_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + MP_MACHINE_I2C_CHECK_FOR_LEGACY_SOFTI2C_CONSTRUCTION(n_args, n_kw, all_args); + enum { ARG_id, ARG_scl, ARG_sda, ARG_freq, ARG_timeout }; static const mp_arg_t allowed_args[] = { { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_OBJ }, @@ -65,7 +66,7 @@ mp_obj_t machine_hard_i2c_make_new(const mp_obj_type_t *type, size_t n_args, siz mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); const char *dev_name = mp_obj_str_get_str(args[ARG_id].u_obj); - struct device *dev = device_get_binding(dev_name); + const struct device *dev = device_get_binding(dev_name); if (dev == NULL) { mp_raise_ValueError(MP_ERROR_TEXT("device not found")); @@ -97,7 +98,7 @@ STATIC int machine_hard_i2c_transfer_single(mp_obj_base_t *self_in, uint16_t add struct i2c_msg msg; int ret; - msg.buf = (u8_t *)buf; + msg.buf = (uint8_t *)buf; msg.len = len; msg.flags = 0; @@ -127,7 +128,7 @@ STATIC const mp_machine_i2c_p_t machine_hard_i2c_p = { .transfer_single = machine_hard_i2c_transfer_single, }; -STATIC const mp_obj_type_t machine_hard_i2c_type = { +const mp_obj_type_t machine_hard_i2c_type = { { &mp_type_type }, .name = MP_QSTR_I2C, .print = machine_hard_i2c_print, diff --git a/ports/zephyr/machine_pin.c b/ports/zephyr/machine_pin.c index ad5f54baafb92..34c822c2f05a5 100644 --- a/ports/zephyr/machine_pin.c +++ b/ports/zephyr/machine_pin.c @@ -35,10 +35,50 @@ #include "py/runtime.h" #include "py/gc.h" #include "py/mphal.h" +#include "lib/utils/mpirq.h" #include "modmachine.h" +#if MICROPY_PY_MACHINE + +typedef struct _machine_pin_irq_obj_t { + mp_irq_obj_t base; + struct _machine_pin_irq_obj_t *next; + struct gpio_callback callback; +} machine_pin_irq_obj_t; + +STATIC const mp_irq_methods_t machine_pin_irq_methods; const mp_obj_base_t machine_pin_obj_template = {&machine_pin_type}; +void machine_pin_deinit(void) { + for (machine_pin_irq_obj_t *irq = MP_STATE_PORT(machine_pin_irq_list); irq != NULL; irq = irq->next) { + machine_pin_obj_t *pin = MP_OBJ_TO_PTR(irq->base.parent); + gpio_pin_interrupt_configure(pin->port, pin->pin, GPIO_INT_DISABLE); + gpio_remove_callback(pin->port, &irq->callback); + } + MP_STATE_PORT(machine_pin_irq_list) = NULL; +} + +STATIC void gpio_callback_handler(const struct device *port, struct gpio_callback *cb, gpio_port_pins_t pins) { + machine_pin_irq_obj_t *irq = CONTAINER_OF(cb, machine_pin_irq_obj_t, callback); + + #if MICROPY_STACK_CHECK + // This callback executes in an ISR context so the stack-limit check must be changed to + // use the ISR stack for the duration of this function (so that hard IRQ callbacks work). + char *orig_stack_top = MP_STATE_THREAD(stack_top); + size_t orig_stack_limit = MP_STATE_THREAD(stack_limit); + MP_STATE_THREAD(stack_top) = (void *)&irq; + MP_STATE_THREAD(stack_limit) = CONFIG_ISR_STACK_SIZE - 512; + #endif + + mp_irq_handler(&irq->base); + + #if MICROPY_STACK_CHECK + // Restore original stack-limit checking values. + MP_STATE_THREAD(stack_top) = orig_stack_top; + MP_STATE_THREAD(stack_limit) = orig_stack_limit; + #endif +} + STATIC void machine_pin_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { machine_pin_obj_t *self = self_in; mp_printf(print, "", self->port, self->pin); @@ -92,7 +132,7 @@ mp_obj_t mp_pin_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, mp_obj_get_array_fixed_n(args[0], 2, &items); const char *drv_name = mp_obj_str_get_str(items[0]); int wanted_pin = mp_obj_get_int(items[1]); - struct device *wanted_port = device_get_binding(drv_name); + const struct device *wanted_port = device_get_binding(drv_name); if (!wanted_port) { mp_raise_ValueError(MP_ERROR_TEXT("invalid port")); } @@ -151,6 +191,62 @@ STATIC mp_obj_t machine_pin_on(mp_obj_t self_in) { } STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_pin_on_obj, machine_pin_on); +// pin.irq(handler=None, trigger=IRQ_FALLING|IRQ_RISING, hard=False) +STATIC mp_obj_t machine_pin_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_handler, ARG_trigger, ARG_hard }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_handler, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_trigger, MP_ARG_INT, {.u_int = GPIO_INT_EDGE_BOTH} }, + { MP_QSTR_hard, MP_ARG_BOOL, {.u_bool = false} }, + }; + machine_pin_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if (self->irq == NULL) { + machine_pin_irq_obj_t *irq; + for (irq = MP_STATE_PORT(machine_pin_irq_list); irq != NULL; irq = irq->next) { + machine_pin_obj_t *irq_pin = MP_OBJ_TO_PTR(irq->base.parent); + if (irq_pin->port == self->port && irq_pin->pin == self->pin) { + break; + } + } + if (irq == NULL) { + irq = m_new_obj(machine_pin_irq_obj_t); + mp_irq_init(&irq->base, &machine_pin_irq_methods, MP_OBJ_FROM_PTR(self)); + irq->next = MP_STATE_PORT(machine_pin_irq_list); + gpio_init_callback(&irq->callback, gpio_callback_handler, BIT(self->pin)); + int ret = gpio_add_callback(self->port, &irq->callback); + if (ret != 0) { + mp_raise_OSError(-ret); + } + MP_STATE_PORT(machine_pin_irq_list) = irq; + } + self->irq = irq; + } + + if (n_args > 1 || kw_args->used != 0) { + // configure irq + int ret = gpio_pin_interrupt_configure(self->port, self->pin, GPIO_INT_DISABLE); + if (ret != 0) { + mp_raise_OSError(-ret); + } + + self->irq->base.handler = args[ARG_handler].u_obj; + self->irq->base.ishard = args[ARG_hard].u_bool; + + if (args[ARG_handler].u_obj != mp_const_none) { + ret = gpio_pin_interrupt_configure(self->port, self->pin, args[ARG_trigger].u_int); + if (ret != 0) { + mp_raise_OSError(-ret); + } + } + } + + return MP_OBJ_FROM_PTR(self->irq); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_pin_irq_obj, 1, machine_pin_irq); + STATIC mp_uint_t machine_pin_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) { (void)errcode; machine_pin_obj_t *self = self_in; @@ -172,12 +268,15 @@ STATIC const mp_rom_map_elem_t machine_pin_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&machine_pin_value_obj) }, { MP_ROM_QSTR(MP_QSTR_off), MP_ROM_PTR(&machine_pin_off_obj) }, { MP_ROM_QSTR(MP_QSTR_on), MP_ROM_PTR(&machine_pin_on_obj) }, + { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&machine_pin_irq_obj) }, // class constants { MP_ROM_QSTR(MP_QSTR_IN), MP_ROM_INT(GPIO_INPUT) }, { MP_ROM_QSTR(MP_QSTR_OUT), MP_ROM_INT(GPIO_OUTPUT) }, { MP_ROM_QSTR(MP_QSTR_PULL_UP), MP_ROM_INT(GPIO_PULL_UP) }, { MP_ROM_QSTR(MP_QSTR_PULL_DOWN), MP_ROM_INT(GPIO_PULL_DOWN) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_RISING), MP_ROM_INT(GPIO_INT_EDGE_RISING) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_FALLING), MP_ROM_INT(GPIO_INT_EDGE_FALLING) }, }; STATIC MP_DEFINE_CONST_DICT(machine_pin_locals_dict, machine_pin_locals_dict_table); @@ -195,3 +294,32 @@ const mp_obj_type_t machine_pin_type = { .protocol = &machine_pin_pin_p, .locals_dict = (mp_obj_t)&machine_pin_locals_dict, }; + +STATIC mp_uint_t machine_pin_irq_trigger(mp_obj_t self_in, mp_uint_t new_trigger) { + machine_pin_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (new_trigger == 0) { + new_trigger = GPIO_INT_DISABLE; + } + int ret = gpio_pin_interrupt_configure(self->port, self->pin, new_trigger); + if (ret != 0) { + mp_raise_OSError(-ret); + } + return 0; +} + +STATIC mp_uint_t machine_pin_irq_info(mp_obj_t self_in, mp_uint_t info_type) { + machine_pin_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (info_type == MP_IRQ_INFO_FLAGS) { + return gpio_get_pending_int(self->port); + } else if (info_type == MP_IRQ_INFO_TRIGGERS) { + return 0; // TODO + } + return 0; +} + +STATIC const mp_irq_methods_t machine_pin_irq_methods = { + .trigger = machine_pin_irq_trigger, + .info = machine_pin_irq_info, +}; + +#endif // MICROPY_PY_MACHINE diff --git a/ports/zephyr/main.c b/ports/zephyr/main.c index e304dfd25824d..74785cc830420 100644 --- a/ports/zephyr/main.c +++ b/ports/zephyr/main.c @@ -38,6 +38,8 @@ #include #endif +#include + #include "py/mperrno.h" #include "py/compile.h" #include "py/runtime.h" @@ -51,6 +53,7 @@ #include "extmod/vfs.h" #endif +#include "modmachine.h" #include "modzephyr.h" #ifdef TEST @@ -100,8 +103,8 @@ STATIC void vfs_init(void) { mp_obj_t args[] = { mp_obj_new_str(CONFIG_DISK_SDHC_VOLUME_NAME, strlen(CONFIG_DISK_SDHC_VOLUME_NAME)) }; bdev = zephyr_disk_access_type.make_new(&zephyr_disk_access_type, ARRAY_SIZE(args), 0, args); mount_point_str = "/sd"; - #elif defined(CONFIG_FLASH_MAP) && defined(DT_FLASH_AREA_STORAGE_ID) - mp_obj_t args[] = { MP_OBJ_NEW_SMALL_INT(DT_FLASH_AREA_STORAGE_ID), MP_OBJ_NEW_SMALL_INT(4096) }; + #elif defined(CONFIG_FLASH_MAP) && FLASH_AREA_LABEL_EXISTS(storage) + mp_obj_t args[] = { MP_OBJ_NEW_SMALL_INT(FLASH_AREA_ID(storage)), MP_OBJ_NEW_SMALL_INT(4096) }; bdev = zephyr_flash_area_type.make_new(&zephyr_flash_area_type, ARRAY_SIZE(args), 0, args); mount_point_str = "/flash"; #endif @@ -162,6 +165,11 @@ int real_main(void) { } printf("soft reboot\n"); + + #if MICROPY_PY_MACHINE + machine_pin_deinit(); + #endif + goto soft_reset; return 0; diff --git a/ports/zephyr/make-minimal b/ports/zephyr/make-minimal index 1fc143e4d6af0..d7dddc33427df 100755 --- a/ports/zephyr/make-minimal +++ b/ports/zephyr/make-minimal @@ -11,6 +11,8 @@ make \ CONF_FILE=prj_minimal.conf \ CFLAGS_EXTRA='-DMP_CONFIGFILE=""' \ + MICROPY_VFS_FAT=0 \ + MICROPY_VFS_LFS2=0 \ FROZEN_DIR= \ QEMU_NET=0 \ "$@" diff --git a/ports/zephyr/modmachine.c b/ports/zephyr/modmachine.c index 72078cf9deabc..29e6c889c4cef 100644 --- a/ports/zephyr/modmachine.c +++ b/ports/zephyr/modmachine.c @@ -60,7 +60,7 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { #endif { MP_ROM_QSTR(MP_QSTR_reset_cause), MP_ROM_PTR(&machine_reset_cause_obj) }, - { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_i2c_type) }, + { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_hard_i2c_type) }, { MP_ROM_QSTR(MP_QSTR_Pin), MP_ROM_PTR(&machine_pin_type) }, { MP_ROM_QSTR(MP_QSTR_Signal), MP_ROM_PTR(&machine_signal_type) }, diff --git a/ports/zephyr/modmachine.h b/ports/zephyr/modmachine.h index 84e4d10a885ae..b67da55338cd7 100644 --- a/ports/zephyr/modmachine.h +++ b/ports/zephyr/modmachine.h @@ -4,13 +4,17 @@ #include "py/obj.h" extern const mp_obj_type_t machine_pin_type; +extern const mp_obj_type_t machine_hard_i2c_type; MP_DECLARE_CONST_FUN_OBJ_0(machine_info_obj); typedef struct _machine_pin_obj_t { mp_obj_base_t base; - struct device *port; + const struct device *port; uint32_t pin; + struct _machine_pin_irq_obj_t *irq; } machine_pin_obj_t; +void machine_pin_deinit(void); + #endif // MICROPY_INCLUDED_ZEPHYR_MODMACHINE_H diff --git a/ports/zephyr/modzephyr.c b/ports/zephyr/modzephyr.c index d4ee610b204c6..71b44d7df1611 100644 --- a/ports/zephyr/modzephyr.c +++ b/ports/zephyr/modzephyr.c @@ -30,7 +30,7 @@ #include #include -#include +#include #include "modzephyr.h" #include "py/runtime.h" @@ -45,27 +45,12 @@ STATIC mp_obj_t mod_current_tid(void) { } STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_current_tid_obj, mod_current_tid); -#ifdef CONFIG_THREAD_STACK_INFO -extern k_tid_t const _main_thread; -extern k_tid_t const _idle_thread; - -static void thread_stack_dump(const struct k_thread *thread, void *user_data) { - const char *th_name = k_thread_name_get((k_tid_t)thread); - - if (th_name == NULL) { - static char tid[9]; - snprintf(tid, sizeof(tid), "%08x", (int)thread); - th_name = tid; - } - - stack_analyze(th_name, (char *)thread->stack_info.start, thread->stack_info.size); -} - -STATIC mp_obj_t mod_stacks_analyze(void) { - k_thread_foreach(thread_stack_dump, NULL); +#ifdef CONFIG_THREAD_ANALYZER +STATIC mp_obj_t mod_thread_analyze(void) { + thread_analyzer_print(); return mp_const_none; } -STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_stacks_analyze_obj, mod_stacks_analyze); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_thread_analyze_obj, mod_thread_analyze); #endif #ifdef CONFIG_NET_SHELL @@ -84,8 +69,8 @@ STATIC const mp_rom_map_elem_t mp_module_time_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_zephyr) }, { MP_ROM_QSTR(MP_QSTR_is_preempt_thread), MP_ROM_PTR(&mod_is_preempt_thread_obj) }, { MP_ROM_QSTR(MP_QSTR_current_tid), MP_ROM_PTR(&mod_current_tid_obj) }, - #ifdef CONFIG_THREAD_STACK_INFO - { MP_ROM_QSTR(MP_QSTR_stacks_analyze), MP_ROM_PTR(&mod_stacks_analyze_obj) }, + #ifdef CONFIG_THREAD_ANALYZER + { MP_ROM_QSTR(MP_QSTR_thread_analyze), MP_ROM_PTR(&mod_thread_analyze_obj) }, #endif #ifdef CONFIG_NET_SHELL diff --git a/ports/zephyr/modzsensor.c b/ports/zephyr/modzsensor.c index b01ce2693c232..01f05aacd64a0 100644 --- a/ports/zephyr/modzsensor.c +++ b/ports/zephyr/modzsensor.c @@ -35,7 +35,7 @@ typedef struct _mp_obj_sensor_t { mp_obj_base_t base; - struct device *dev; + const struct device *dev; } mp_obj_sensor_t; STATIC mp_obj_t sensor_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { diff --git a/ports/zephyr/mpconfigport.h b/ports/zephyr/mpconfigport.h index 7044c175d030c..6bfd9ff884f1b 100644 --- a/ports/zephyr/mpconfigport.h +++ b/ports/zephyr/mpconfigport.h @@ -61,7 +61,6 @@ #define MICROPY_PY_MICROPYTHON_MEM_INFO (1) #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_I2C (1) -#define MICROPY_PY_MACHINE_I2C_MAKE_NEW machine_hard_i2c_make_new #define MICROPY_PY_MACHINE_PIN_MAKE_NEW mp_pin_make_new #define MICROPY_MODULE_WEAK_LINKS (1) #define MICROPY_PY_STRUCT (0) @@ -81,6 +80,7 @@ #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_LONGLONG) #define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT) #define MICROPY_PY_BUILTINS_COMPLEX (0) +#define MICROPY_ENABLE_SCHEDULER (1) #define MICROPY_VFS (1) #define MICROPY_READER_VFS (MICROPY_VFS) @@ -119,7 +119,8 @@ typedef long mp_off_t; #define MP_STATE_PORT MP_STATE_VM #define MICROPY_PORT_ROOT_POINTERS \ - const char *readline_hist[8]; + const char *readline_hist[8]; \ + void *machine_pin_irq_list; /* Linked list of pin irq objects */ extern const struct _mp_obj_module_t mp_module_machine; extern const struct _mp_obj_module_t mp_module_time; @@ -169,3 +170,6 @@ extern const struct _mp_obj_module_t mp_module_zsensor; // extra built in names to add to the global namespace #define MICROPY_PORT_BUILTINS \ { MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&mp_builtin_open_obj) }, + +#define MICROPY_BEGIN_ATOMIC_SECTION irq_lock +#define MICROPY_END_ATOMIC_SECTION irq_unlock diff --git a/ports/zephyr/mphalport.h b/ports/zephyr/mphalport.h index 594f6a1f69830..b3154b649bbda 100644 --- a/ports/zephyr/mphalport.h +++ b/ports/zephyr/mphalport.h @@ -21,10 +21,19 @@ static inline void mp_hal_delay_us(mp_uint_t delay) { } static inline void mp_hal_delay_ms(mp_uint_t delay) { - k_sleep(delay); + k_msleep(delay); +} + +static inline uint64_t mp_hal_time_ns(void) { + // Not currently implemented. + return 0; } #define mp_hal_delay_us_fast(us) (mp_hal_delay_us(us)) + +// C-level pin API is not currently implemented +#define MP_HAL_PIN_FMT "%d" +#define mp_hal_pin_name(p) (0) #define mp_hal_pin_od_low(p) (mp_raise_NotImplementedError("mp_hal_pin_od_low")) #define mp_hal_pin_od_high(p) (mp_raise_NotImplementedError("mp_hal_pin_od_high")) #define mp_hal_pin_open_drain(p) (mp_raise_NotImplementedError("mp_hal_pin_open_drain")) diff --git a/ports/zephyr/prj.conf b/ports/zephyr/prj.conf index 993dfdc26f9ec..50cfa005025dc 100644 --- a/ports/zephyr/prj.conf +++ b/ports/zephyr/prj.conf @@ -11,7 +11,7 @@ CONFIG_CONSOLE_GETCHAR_BUFSIZE=128 CONFIG_CONSOLE_PUTCHAR_BUFSIZE=128 CONFIG_NEWLIB_LIBC=y -CONFIG_FLOAT=y +CONFIG_FPU=y CONFIG_MAIN_STACK_SIZE=4736 # Enable sensor subsystem (doesn't add code if not used). @@ -50,10 +50,9 @@ CONFIG_NET_DHCPV4=y # Diagnostics and debugging # Required for zephyr.stack_analyze() -CONFIG_INIT_STACKS=y -CONFIG_THREAD_MONITOR=y +CONFIG_THREAD_ANALYZER=y +CONFIG_THREAD_ANALYZER_USE_PRINTK=y CONFIG_THREAD_NAME=y -CONFIG_THREAD_STACK_INFO=y # Required for usocket.pkt_get_info() CONFIG_NET_BUF_POOL_USAGE=y diff --git a/ports/zephyr/prj_minimal.conf b/ports/zephyr/prj_minimal.conf index d9c2894e6f2de..8b2104925ab67 100644 --- a/ports/zephyr/prj_minimal.conf +++ b/ports/zephyr/prj_minimal.conf @@ -1,5 +1,5 @@ CONFIG_NEWLIB_LIBC=y -CONFIG_FLOAT=y +CONFIG_FPU=y CONFIG_MAIN_STACK_SIZE=4096 CONFIG_UART_INTERRUPT_DRIVEN=y diff --git a/ports/zephyr/uart_core.c b/ports/zephyr/uart_core.c index fef9f00ab1625..44bdeb5c2784c 100644 --- a/ports/zephyr/uart_core.c +++ b/ports/zephyr/uart_core.c @@ -49,11 +49,11 @@ void mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) { while (len--) { char c = *str++; while (console_putchar(c) == -1) { - k_sleep(1); + k_msleep(1); } } #else - static struct device *uart_console_dev; + static const struct device *uart_console_dev; if (uart_console_dev == NULL) { uart_console_dev = device_get_binding(CONFIG_UART_CONSOLE_ON_DEV_NAME); } diff --git a/ports/zephyr/zephyr_storage.c b/ports/zephyr/zephyr_storage.c index c533cb8fd721b..1c25b32771e66 100644 --- a/ports/zephyr/zephyr_storage.c +++ b/ports/zephyr/zephyr_storage.c @@ -146,7 +146,7 @@ typedef struct _zephyr_flash_area_obj_t { const struct flash_area *area; int block_size; int block_count; - u8_t id; + uint8_t id; } zephyr_flash_area_obj_t; STATIC void zephyr_flash_area_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { @@ -245,8 +245,8 @@ STATIC const mp_rom_map_elem_t zephyr_flash_area_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_readblocks), MP_ROM_PTR(&zephyr_flash_area_readblocks_obj) }, { MP_ROM_QSTR(MP_QSTR_writeblocks), MP_ROM_PTR(&zephyr_flash_area_writeblocks_obj) }, { MP_ROM_QSTR(MP_QSTR_ioctl), MP_ROM_PTR(&zephyr_flash_area_ioctl_obj) }, - #ifdef DT_FLASH_AREA_STORAGE_ID - { MP_ROM_QSTR(MP_QSTR_STORAGE), MP_ROM_INT(DT_FLASH_AREA_STORAGE_ID) }, + #if FLASH_AREA_LABEL_EXISTS(storage) + { MP_ROM_QSTR(MP_QSTR_STORAGE), MP_ROM_INT(FLASH_AREA_ID(storage)) }, #endif }; STATIC MP_DEFINE_CONST_DICT(zephyr_flash_area_locals_dict, zephyr_flash_area_locals_dict_table); diff --git a/py/asmarm.c b/py/asmarm.c index 72b37f73a02f5..e91421578b274 100644 --- a/py/asmarm.c +++ b/py/asmarm.c @@ -303,6 +303,11 @@ void asm_arm_lsl_reg_reg(asm_arm_t *as, uint rd, uint rs) { emit_al(as, 0x1a00010 | (rd << 12) | (rs << 8) | rd); } +void asm_arm_lsr_reg_reg(asm_arm_t *as, uint rd, uint rs) { + // mov rd, rd, lsr rs + emit_al(as, 0x1a00030 | (rd << 12) | (rs << 8) | rd); +} + void asm_arm_asr_reg_reg(asm_arm_t *as, uint rd, uint rs) { // mov rd, rd, asr rs emit_al(as, 0x1a00050 | (rd << 12) | (rs << 8) | rd); diff --git a/py/asmarm.h b/py/asmarm.h index 825fd884005ff..46da661faa9ba 100644 --- a/py/asmarm.h +++ b/py/asmarm.h @@ -101,6 +101,7 @@ void asm_arm_orr_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm); void asm_arm_mov_reg_local_addr(asm_arm_t *as, uint rd, int local_num); void asm_arm_mov_reg_pcrel(asm_arm_t *as, uint reg_dest, uint label); void asm_arm_lsl_reg_reg(asm_arm_t *as, uint rd, uint rs); +void asm_arm_lsr_reg_reg(asm_arm_t *as, uint rd, uint rs); void asm_arm_asr_reg_reg(asm_arm_t *as, uint rd, uint rs); // memory @@ -187,6 +188,7 @@ void asm_arm_bx_reg(asm_arm_t *as, uint reg_src); #define ASM_MOV_REG_PCREL(as, reg_dest, label) asm_arm_mov_reg_pcrel((as), (reg_dest), (label)) #define ASM_LSL_REG_REG(as, reg_dest, reg_shift) asm_arm_lsl_reg_reg((as), (reg_dest), (reg_shift)) +#define ASM_LSR_REG_REG(as, reg_dest, reg_shift) asm_arm_lsr_reg_reg((as), (reg_dest), (reg_shift)) #define ASM_ASR_REG_REG(as, reg_dest, reg_shift) asm_arm_asr_reg_reg((as), (reg_dest), (reg_shift)) #define ASM_OR_REG_REG(as, reg_dest, reg_src) asm_arm_orr_reg_reg_reg((as), (reg_dest), (reg_dest), (reg_src)) #define ASM_XOR_REG_REG(as, reg_dest, reg_src) asm_arm_eor_reg_reg_reg((as), (reg_dest), (reg_dest), (reg_src)) diff --git a/py/asmthumb.h b/py/asmthumb.h index 6d0ee4b76e50e..17b694a74dfdd 100644 --- a/py/asmthumb.h +++ b/py/asmthumb.h @@ -80,12 +80,19 @@ void asm_thumb_exit(asm_thumb_t *as); #define ASM_THUMB_OP_IT (0xbf00) #define ASM_THUMB_OP_ITE_EQ (0xbf0c) +#define ASM_THUMB_OP_ITE_NE (0xbf14) #define ASM_THUMB_OP_ITE_CS (0xbf2c) +#define ASM_THUMB_OP_ITE_CC (0xbf34) #define ASM_THUMB_OP_ITE_MI (0xbf4c) +#define ASM_THUMB_OP_ITE_PL (0xbf54) #define ASM_THUMB_OP_ITE_VS (0xbf6c) +#define ASM_THUMB_OP_ITE_VC (0xbf74) #define ASM_THUMB_OP_ITE_HI (0xbf8c) +#define ASM_THUMB_OP_ITE_LS (0xbf94) #define ASM_THUMB_OP_ITE_GE (0xbfac) +#define ASM_THUMB_OP_ITE_LT (0xbfb4) #define ASM_THUMB_OP_ITE_GT (0xbfcc) +#define ASM_THUMB_OP_ITE_LE (0xbfd4) #define ASM_THUMB_OP_NOP (0xbf00) #define ASM_THUMB_OP_WFI (0xbf30) @@ -345,6 +352,7 @@ void asm_thumb_bl_ind(asm_thumb_t *as, uint fun_id, uint reg_temp); // convenien #define ASM_MOV_REG_PCREL(as, rlo_dest, label) asm_thumb_mov_reg_pcrel((as), (rlo_dest), (label)) #define ASM_LSL_REG_REG(as, reg_dest, reg_shift) asm_thumb_format_4((as), ASM_THUMB_FORMAT_4_LSL, (reg_dest), (reg_shift)) +#define ASM_LSR_REG_REG(as, reg_dest, reg_shift) asm_thumb_format_4((as), ASM_THUMB_FORMAT_4_LSR, (reg_dest), (reg_shift)) #define ASM_ASR_REG_REG(as, reg_dest, reg_shift) asm_thumb_format_4((as), ASM_THUMB_FORMAT_4_ASR, (reg_dest), (reg_shift)) #define ASM_OR_REG_REG(as, reg_dest, reg_src) asm_thumb_format_4((as), ASM_THUMB_FORMAT_4_ORR, (reg_dest), (reg_src)) #define ASM_XOR_REG_REG(as, reg_dest, reg_src) asm_thumb_format_4((as), ASM_THUMB_FORMAT_4_EOR, (reg_dest), (reg_src)) diff --git a/py/asmx64.c b/py/asmx64.c index 723671d5a3558..fd64eaf98b1c1 100644 --- a/py/asmx64.c +++ b/py/asmx64.c @@ -67,6 +67,7 @@ // #define OPCODE_SHR_RM32_BY_I8 (0xc1) /* /5 */ // #define OPCODE_SAR_RM32_BY_I8 (0xc1) /* /7 */ #define OPCODE_SHL_RM64_CL (0xd3) /* /4 */ +#define OPCODE_SHR_RM64_CL (0xd3) /* /5 */ #define OPCODE_SAR_RM64_CL (0xd3) /* /7 */ // #define OPCODE_CMP_I32_WITH_RM32 (0x81) /* /7 */ // #define OPCODE_CMP_I8_WITH_RM32 (0x83) /* /7 */ @@ -382,6 +383,10 @@ void asm_x64_shl_r64_cl(asm_x64_t *as, int dest_r64) { asm_x64_generic_r64_r64(as, dest_r64, 4, OPCODE_SHL_RM64_CL); } +void asm_x64_shr_r64_cl(asm_x64_t *as, int dest_r64) { + asm_x64_generic_r64_r64(as, dest_r64, 5, OPCODE_SHR_RM64_CL); +} + void asm_x64_sar_r64_cl(asm_x64_t *as, int dest_r64) { asm_x64_generic_r64_r64(as, dest_r64, 7, OPCODE_SAR_RM64_CL); } diff --git a/py/asmx64.h b/py/asmx64.h index 28b1bd255fd21..1a4987f5cbbaf 100644 --- a/py/asmx64.h +++ b/py/asmx64.h @@ -61,10 +61,13 @@ // condition codes, used for jcc and setcc (despite their j-name!) #define ASM_X64_CC_JB (0x2) // below, unsigned +#define ASM_X64_CC_JAE (0x3) // above or equal, unsigned #define ASM_X64_CC_JZ (0x4) #define ASM_X64_CC_JE (0x4) #define ASM_X64_CC_JNZ (0x5) #define ASM_X64_CC_JNE (0x5) +#define ASM_X64_CC_JBE (0x6) // below or equal, unsigned +#define ASM_X64_CC_JA (0x7) // above, unsigned #define ASM_X64_CC_JL (0xc) // less, signed #define ASM_X64_CC_JGE (0xd) // greater or equal, signed #define ASM_X64_CC_JLE (0xe) // less or equal, signed @@ -98,6 +101,7 @@ void asm_x64_and_r64_r64(asm_x64_t *as, int dest_r64, int src_r64); void asm_x64_or_r64_r64(asm_x64_t *as, int dest_r64, int src_r64); void asm_x64_xor_r64_r64(asm_x64_t *as, int dest_r64, int src_r64); void asm_x64_shl_r64_cl(asm_x64_t *as, int dest_r64); +void asm_x64_shr_r64_cl(asm_x64_t *as, int dest_r64); void asm_x64_sar_r64_cl(asm_x64_t *as, int dest_r64); void asm_x64_add_r64_r64(asm_x64_t *as, int dest_r64, int src_r64); void asm_x64_sub_r64_r64(asm_x64_t *as, int dest_r64, int src_r64); @@ -190,6 +194,7 @@ void asm_x64_call_ind(asm_x64_t *as, size_t fun_id, int temp_r32); #define ASM_MOV_REG_PCREL(as, reg_dest, label) asm_x64_mov_reg_pcrel((as), (reg_dest), (label)) #define ASM_LSL_REG(as, reg) asm_x64_shl_r64_cl((as), (reg)) +#define ASM_LSR_REG(as, reg) asm_x64_shr_r64_cl((as), (reg)) #define ASM_ASR_REG(as, reg) asm_x64_sar_r64_cl((as), (reg)) #define ASM_OR_REG_REG(as, reg_dest, reg_src) asm_x64_or_r64_r64((as), (reg_dest), (reg_src)) #define ASM_XOR_REG_REG(as, reg_dest, reg_src) asm_x64_xor_r64_r64((as), (reg_dest), (reg_src)) diff --git a/py/asmx86.c b/py/asmx86.c index 96de372aec0c2..4b0f8047f6eb7 100644 --- a/py/asmx86.c +++ b/py/asmx86.c @@ -67,6 +67,7 @@ // #define OPCODE_SHR_RM32_BY_I8 (0xc1) /* /5 */ // #define OPCODE_SAR_RM32_BY_I8 (0xc1) /* /7 */ #define OPCODE_SHL_RM32_CL (0xd3) /* /4 */ +#define OPCODE_SHR_RM32_CL (0xd3) /* /5 */ #define OPCODE_SAR_RM32_CL (0xd3) /* /7 */ // #define OPCODE_CMP_I32_WITH_RM32 (0x81) /* /7 */ // #define OPCODE_CMP_I8_WITH_RM32 (0x83) /* /7 */ @@ -259,6 +260,10 @@ void asm_x86_shl_r32_cl(asm_x86_t *as, int dest_r32) { asm_x86_generic_r32_r32(as, dest_r32, 4, OPCODE_SHL_RM32_CL); } +void asm_x86_shr_r32_cl(asm_x86_t *as, int dest_r32) { + asm_x86_generic_r32_r32(as, dest_r32, 5, OPCODE_SHR_RM32_CL); +} + void asm_x86_sar_r32_cl(asm_x86_t *as, int dest_r32) { asm_x86_generic_r32_r32(as, dest_r32, 7, OPCODE_SAR_RM32_CL); } diff --git a/py/asmx86.h b/py/asmx86.h index 4855cd7ee4e8a..8f1b06d220e83 100644 --- a/py/asmx86.h +++ b/py/asmx86.h @@ -63,10 +63,13 @@ // condition codes, used for jcc and setcc (despite their j-name!) #define ASM_X86_CC_JB (0x2) // below, unsigned +#define ASM_X86_CC_JAE (0x3) // above or equal, unsigned #define ASM_X86_CC_JZ (0x4) #define ASM_X86_CC_JE (0x4) #define ASM_X86_CC_JNZ (0x5) #define ASM_X86_CC_JNE (0x5) +#define ASM_X86_CC_JBE (0x6) // below or equal, unsigned +#define ASM_X86_CC_JA (0x7) // above, unsigned #define ASM_X86_CC_JL (0xc) // less, signed #define ASM_X86_CC_JGE (0xd) // greater or equal, signed #define ASM_X86_CC_JLE (0xe) // less or equal, signed @@ -93,6 +96,7 @@ void asm_x86_and_r32_r32(asm_x86_t *as, int dest_r32, int src_r32); void asm_x86_or_r32_r32(asm_x86_t *as, int dest_r32, int src_r32); void asm_x86_xor_r32_r32(asm_x86_t *as, int dest_r32, int src_r32); void asm_x86_shl_r32_cl(asm_x86_t *as, int dest_r32); +void asm_x86_shr_r32_cl(asm_x86_t *as, int dest_r32); void asm_x86_sar_r32_cl(asm_x86_t *as, int dest_r32); void asm_x86_add_r32_r32(asm_x86_t *as, int dest_r32, int src_r32); void asm_x86_sub_r32_r32(asm_x86_t *as, int dest_r32, int src_r32); @@ -185,6 +189,7 @@ void asm_x86_call_ind(asm_x86_t *as, size_t fun_id, mp_uint_t n_args, int temp_r #define ASM_MOV_REG_PCREL(as, reg_dest, label) asm_x86_mov_reg_pcrel((as), (reg_dest), (label)) #define ASM_LSL_REG(as, reg) asm_x86_shl_r32_cl((as), (reg)) +#define ASM_LSR_REG(as, reg) asm_x86_shr_r32_cl((as), (reg)) #define ASM_ASR_REG(as, reg) asm_x86_sar_r32_cl((as), (reg)) #define ASM_OR_REG_REG(as, reg_dest, reg_src) asm_x86_or_r32_r32((as), (reg_dest), (reg_src)) #define ASM_XOR_REG_REG(as, reg_dest, reg_src) asm_x86_xor_r32_r32((as), (reg_dest), (reg_src)) diff --git a/py/asmxtensa.h b/py/asmxtensa.h index 5eb40daf7887c..43f1b608edd1c 100644 --- a/py/asmxtensa.h +++ b/py/asmxtensa.h @@ -243,6 +243,10 @@ static inline void asm_xtensa_op_sll(asm_xtensa_t *as, uint reg_dest, uint reg_s asm_xtensa_op24(as, ASM_XTENSA_ENCODE_RRR(0, 1, 10, reg_dest, reg_src, 0)); } +static inline void asm_xtensa_op_srl(asm_xtensa_t *as, uint reg_dest, uint reg_src) { + asm_xtensa_op24(as, ASM_XTENSA_ENCODE_RRR(0, 1, 9, reg_dest, 0, reg_src)); +} + static inline void asm_xtensa_op_sra(asm_xtensa_t *as, uint reg_dest, uint reg_src) { asm_xtensa_op24(as, ASM_XTENSA_ENCODE_RRR(0, 1, 11, reg_dest, 0, reg_src)); } @@ -372,6 +376,11 @@ void asm_xtensa_call_ind_win(asm_xtensa_t *as, uint idx); asm_xtensa_op_ssl((as), (reg_shift)); \ asm_xtensa_op_sll((as), (reg_dest), (reg_dest)); \ } while (0) +#define ASM_LSR_REG_REG(as, reg_dest, reg_shift) \ + do { \ + asm_xtensa_op_ssr((as), (reg_shift)); \ + asm_xtensa_op_srl((as), (reg_dest), (reg_dest)); \ + } while (0) #define ASM_ASR_REG_REG(as, reg_dest, reg_shift) \ do { \ asm_xtensa_op_ssr((as), (reg_shift)); \ diff --git a/py/bc.h b/py/bc.h index 0c19c516595ac..16f314e19936f 100644 --- a/py/bc.h +++ b/py/bc.h @@ -226,10 +226,10 @@ const byte *mp_decode_uint_skip(const byte *ptr); mp_vm_return_kind_t mp_execute_bytecode(mp_code_state_t *code_state, volatile mp_obj_t inject_exc); mp_code_state_t *mp_obj_fun_bc_prepare_codestate(mp_obj_t func, size_t n_args, size_t n_kw, const mp_obj_t *args); void mp_setup_code_state(mp_code_state_t *code_state, size_t n_args, size_t n_kw, const mp_obj_t *args); -void mp_bytecode_print(const void *descr, const byte *code, mp_uint_t len, const mp_uint_t *const_table); -void mp_bytecode_print2(const byte *code, size_t len, const mp_uint_t *const_table); -const byte *mp_bytecode_print_str(const byte *ip); -#define mp_bytecode_print_inst(code, const_table) mp_bytecode_print2(code, 1, const_table) +void mp_bytecode_print(const mp_print_t *print, const void *descr, const byte *code, mp_uint_t len, const mp_uint_t *const_table); +void mp_bytecode_print2(const mp_print_t *print, const byte *code, size_t len, const mp_uint_t *const_table); +const byte *mp_bytecode_print_str(const mp_print_t *print, const byte *ip); +#define mp_bytecode_print_inst(print, code, const_table) mp_bytecode_print2(print, code, 1, const_table) // Helper macros to access pointer with least significant bits holding flags #define MP_TAGPTR_PTR(x) ((void *)((uintptr_t)(x) & ~((uintptr_t)3))) diff --git a/py/compile.c b/py/compile.c index 22f47ee0d295f..d1a4d65c8bb51 100644 --- a/py/compile.c +++ b/py/compile.c @@ -3,7 +3,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2013-2015 Damien P. George + * Copyright (c) 2013-2020 Damien P. George * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -1798,7 +1798,8 @@ STATIC void compile_async_for_stmt(compiler_t *comp, mp_parse_node_struct_t *pns uint try_finally_label = comp_next_label(comp); compile_node(comp, pns->nodes[1]); // iterator - compile_await_object_method(comp, MP_QSTR___aiter__); + EMIT_ARG(load_method, MP_QSTR___aiter__, false); + EMIT_ARG(call_method, 0, 0, 0); compile_store_id(comp, context); START_BREAK_CONTINUE_BLOCK @@ -1981,7 +1982,8 @@ STATIC void compile_async_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { #endif STATIC void compile_expr_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { - if (MP_PARSE_NODE_IS_NULL(pns->nodes[1])) { + mp_parse_node_t pn_rhs = pns->nodes[1]; + if (MP_PARSE_NODE_IS_NULL(pn_rhs)) { if (comp->is_repl && comp->scope_cur->kind == SCOPE_MODULE) { // for REPL, evaluate then print the expression compile_load_id(comp, MP_QSTR___repl_print__); @@ -1999,10 +2001,26 @@ STATIC void compile_expr_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { EMIT(pop_top); // discard last result since this is a statement and leaves nothing on the stack } } - } else if (MP_PARSE_NODE_IS_STRUCT(pns->nodes[1])) { - mp_parse_node_struct_t *pns1 = (mp_parse_node_struct_t *)pns->nodes[1]; + } else if (MP_PARSE_NODE_IS_STRUCT(pn_rhs)) { + mp_parse_node_struct_t *pns1 = (mp_parse_node_struct_t *)pn_rhs; int kind = MP_PARSE_NODE_STRUCT_KIND(pns1); - if (kind == PN_expr_stmt_augassign) { + if (kind == PN_annassign) { + // the annotation is in pns1->nodes[0] and is ignored + if (MP_PARSE_NODE_IS_NULL(pns1->nodes[1])) { + // an annotation of the form "x: y" + // inside a function this declares "x" as a local + if (comp->scope_cur->kind == SCOPE_FUNCTION) { + if (MP_PARSE_NODE_IS_ID(pns->nodes[0])) { + qstr lhs = MP_PARSE_NODE_LEAF_ARG(pns->nodes[0]); + scope_find_or_add_id(comp->scope_cur, lhs, ID_INFO_KIND_LOCAL); + } + } + } else { + // an assigned annotation of the form "x: y = z" + pn_rhs = pns1->nodes[1]; + goto plain_assign; + } + } else if (kind == PN_expr_stmt_augassign) { c_assign(comp, pns->nodes[0], ASSIGN_AUG_LOAD); // lhs load for aug assign compile_node(comp, pns1->nodes[1]); // rhs assert(MP_PARSE_NODE_IS_TOKEN(pns1->nodes[0])); @@ -2027,10 +2045,10 @@ STATIC void compile_expr_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { } else { plain_assign: #if MICROPY_COMP_DOUBLE_TUPLE_ASSIGN - if (MP_PARSE_NODE_IS_STRUCT_KIND(pns->nodes[1], PN_testlist_star_expr) + if (MP_PARSE_NODE_IS_STRUCT_KIND(pn_rhs, PN_testlist_star_expr) && MP_PARSE_NODE_IS_STRUCT_KIND(pns->nodes[0], PN_testlist_star_expr)) { mp_parse_node_struct_t *pns0 = (mp_parse_node_struct_t *)pns->nodes[0]; - pns1 = (mp_parse_node_struct_t *)pns->nodes[1]; + pns1 = (mp_parse_node_struct_t *)pn_rhs; uint32_t n_pns0 = MP_PARSE_NODE_STRUCT_NUM_NODES(pns0); // Can only optimise a tuple-to-tuple assignment when all of the following hold: // - equal number of items in LHS and RHS tuples @@ -2070,7 +2088,7 @@ STATIC void compile_expr_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { } #endif - compile_node(comp, pns->nodes[1]); // rhs + compile_node(comp, pn_rhs); // rhs c_assign(comp, pns->nodes[0], ASSIGN_STORE); // lhs store } } else { @@ -2108,6 +2126,27 @@ STATIC void compile_lambdef(compiler_t *comp, mp_parse_node_struct_t *pns) { compile_funcdef_lambdef(comp, this_scope, pns->nodes[0], PN_varargslist); } +#if MICROPY_PY_ASSIGN_EXPR +STATIC void compile_namedexpr_helper(compiler_t *comp, mp_parse_node_t pn_name, mp_parse_node_t pn_expr) { + if (!MP_PARSE_NODE_IS_ID(pn_name)) { + compile_syntax_error(comp, (mp_parse_node_t)pn_name, MP_ERROR_TEXT("can't assign to expression")); + } + compile_node(comp, pn_expr); + EMIT(dup_top); + scope_t *old_scope = comp->scope_cur; + if (SCOPE_IS_COMP_LIKE(comp->scope_cur->kind)) { + // Use parent's scope for assigned value so it can "escape" + comp->scope_cur = comp->scope_cur->parent; + } + compile_store_id(comp, MP_PARSE_NODE_LEAF_ARG(pn_name)); + comp->scope_cur = old_scope; +} + +STATIC void compile_namedexpr(compiler_t *comp, mp_parse_node_struct_t *pns) { + compile_namedexpr_helper(comp, pns->nodes[0], pns->nodes[1]); +} +#endif + STATIC void compile_or_and_test(compiler_t *comp, mp_parse_node_struct_t *pns) { bool cond = MP_PARSE_NODE_STRUCT_KIND(pns) == PN_or_test; uint l_end = comp_next_label(comp); @@ -2353,6 +2392,12 @@ STATIC void compile_trailer_paren_helper(compiler_t *comp, mp_parse_node_t pn_ar star_flags |= MP_EMIT_STAR_FLAG_DOUBLE; dblstar_args_node = pns_arg; } else if (MP_PARSE_NODE_STRUCT_KIND(pns_arg) == PN_argument) { + #if MICROPY_PY_ASSIGN_EXPR + if (MP_PARSE_NODE_IS_STRUCT_KIND(pns_arg->nodes[1], PN_argument_3)) { + compile_namedexpr_helper(comp, pns_arg->nodes[0], ((mp_parse_node_struct_t *)pns_arg->nodes[1])->nodes[0]); + n_positional++; + } else + #endif if (!MP_PARSE_NODE_IS_STRUCT_KIND(pns_arg->nodes[1], PN_comp_for)) { if (!MP_PARSE_NODE_IS_ID(pns_arg->nodes[0])) { compile_syntax_error(comp, (mp_parse_node_t)pns_arg, MP_ERROR_TEXT("LHS of keyword arg must be an id")); @@ -3083,7 +3128,7 @@ STATIC void compile_scope(compiler_t *comp, scope_t *scope, pass_kind_t pass) { EMIT_ARG(load_const_tok, MP_TOKEN_KW_NONE); } EMIT(return_value); - } else if (scope->kind == SCOPE_LIST_COMP || scope->kind == SCOPE_DICT_COMP || scope->kind == SCOPE_SET_COMP || scope->kind == SCOPE_GEN_EXPR) { + } else if (SCOPE_IS_COMP_LIKE(scope->kind)) { // a bit of a hack at the moment assert(MP_PARSE_NODE_IS_STRUCT(scope->pn)); diff --git a/py/dynruntime.h b/py/dynruntime.h index 9f2803c53520f..eb8301284e1fc 100644 --- a/py/dynruntime.h +++ b/py/dynruntime.h @@ -114,7 +114,7 @@ static inline void *m_realloc_dyn(void *ptr, size_t new_num_bytes) { #define mp_obj_cast_to_native_base(o, t) (mp_obj_cast_to_native_base_dyn((o), (t))) #define mp_obj_get_int(o) (mp_fun_table.native_from_obj(o, MP_NATIVE_TYPE_INT)) #define mp_obj_get_int_truncated(o) (mp_fun_table.native_from_obj(o, MP_NATIVE_TYPE_UINT)) -#define mp_obj_str_get_str(s) ((void *)mp_fun_table.native_from_obj(s, MP_NATIVE_TYPE_PTR)) +#define mp_obj_str_get_str(s) (mp_obj_str_get_data_dyn((s), NULL)) #define mp_obj_str_get_data(o, len) (mp_obj_str_get_data_dyn((o), (len))) #define mp_get_buffer_raise(o, bufinfo, fl) (mp_fun_table.get_buffer_raise((o), (bufinfo), (fl))) #define mp_get_stream_raise(s, flags) (mp_fun_table.get_stream_raise((s), (flags))) @@ -149,7 +149,9 @@ static inline mp_obj_t mp_obj_cast_to_native_base_dyn(mp_obj_t self_in, mp_const static inline void *mp_obj_str_get_data_dyn(mp_obj_t o, size_t *l) { mp_buffer_info_t bufinfo; mp_get_buffer_raise(o, &bufinfo, MP_BUFFER_READ); - *l = bufinfo.len; + if (l != NULL) { + *l = bufinfo.len; + } return bufinfo.buf; } @@ -161,9 +163,15 @@ static inline mp_obj_t mp_obj_len_dyn(mp_obj_t o) { /******************************************************************************/ // General runtime functions -#define mp_load_name(qst) (mp_fun_table.load_name(qst)) -#define mp_load_global(qst) (mp_fun_table.load_global(qst)) -#define mp_store_global(qst, obj) (mp_fun_table.store_global((qst), (obj))) +#define mp_load_name(qst) (mp_fun_table.load_name((qst))) +#define mp_load_global(qst) (mp_fun_table.load_global((qst))) +#define mp_load_attr(base, attr) (mp_fun_table.load_attr((base), (attr))) +#define mp_load_method(base, attr, dest) (mp_fun_table.load_method((base), (attr), (dest))) +#define mp_load_super_method(attr, dest) (mp_fun_table.load_super_method((attr), (dest))) +#define mp_store_name(qst, obj) (mp_fun_table.store_name((qst), (obj))) +#define mp_store_global(qst, obj) (mp_fun_table.store_global((qst), (obj))) +#define mp_store_attr(base, attr, val) (mp_fun_table.store_attr((base), (attr), (val))) + #define mp_unary_op(op, obj) (mp_fun_table.unary_op((op), (obj))) #define mp_binary_op(op, lhs, rhs) (mp_fun_table.binary_op((op), (lhs), (rhs))) @@ -191,6 +199,13 @@ static inline mp_obj_t mp_obj_len_dyn(mp_obj_t o) { #define MP_DYNRUNTIME_MAKE_FUNCTION(f) \ (mp_make_function_from_raw_code((rc.fun_data = (f), &rc), MP_OBJ_NULL, MP_OBJ_NULL)) +#define mp_import_name(name, fromlist, level) \ + (mp_fun_table.import_name((name), (fromlist), (level))) +#define mp_import_from(module, name) \ + (mp_fun_table.import_from((module), (name))) +#define mp_import_all(module) \ + (mp_fun_table.import_all((module)) + /******************************************************************************/ // Exceptions diff --git a/py/emitglue.c b/py/emitglue.c index 98320c426bc63..0ef708a3f3f56 100644 --- a/py/emitglue.c +++ b/py/emitglue.c @@ -92,7 +92,7 @@ void mp_emit_glue_assign_bytecode(mp_raw_code_t *rc, const byte *code, #endif #if MICROPY_DEBUG_PRINTERS if (mp_verbose_flag >= 2) { - mp_bytecode_print(rc, code, len, const_table); + mp_bytecode_print(&mp_plat_print, rc, code, len, const_table); } #endif } diff --git a/py/emitnative.c b/py/emitnative.c index 6c8e9feebaee9..2a657b6964daa 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -2289,7 +2289,8 @@ STATIC void emit_native_binary_op(emit_t *emit, mp_binary_op_t op) { DEBUG_printf("binary_op(" UINT_FMT ")\n", op); vtype_kind_t vtype_lhs = peek_vtype(emit, 1); vtype_kind_t vtype_rhs = peek_vtype(emit, 0); - if (vtype_lhs == VTYPE_INT && vtype_rhs == VTYPE_INT) { + if ((vtype_lhs == VTYPE_INT || vtype_lhs == VTYPE_UINT) + && (vtype_rhs == VTYPE_INT || vtype_rhs == VTYPE_UINT)) { // for integers, inplace and normal ops are equivalent, so use just normal ops if (MP_BINARY_OP_INPLACE_OR <= op && op <= MP_BINARY_OP_INPLACE_POWER) { op += MP_BINARY_OP_OR - MP_BINARY_OP_INPLACE_OR; @@ -2306,9 +2307,13 @@ STATIC void emit_native_binary_op(emit_t *emit, mp_binary_op_t op) { if (op == MP_BINARY_OP_LSHIFT) { ASM_LSL_REG(emit->as, REG_RET); } else { - ASM_ASR_REG(emit->as, REG_RET); + if (vtype_lhs == VTYPE_UINT) { + ASM_LSR_REG(emit->as, REG_RET); + } else { + ASM_ASR_REG(emit->as, REG_RET); + } } - emit_post_push_reg(emit, VTYPE_INT, REG_RET); + emit_post_push_reg(emit, vtype_lhs, REG_RET); return; } #endif @@ -2316,6 +2321,10 @@ STATIC void emit_native_binary_op(emit_t *emit, mp_binary_op_t op) { // special cases for floor-divide and module because we dispatch to helper functions if (op == MP_BINARY_OP_FLOOR_DIVIDE || op == MP_BINARY_OP_MODULO) { emit_pre_pop_reg_reg(emit, &vtype_rhs, REG_ARG_2, &vtype_lhs, REG_ARG_1); + if (vtype_lhs != VTYPE_INT) { + EMIT_NATIVE_VIPER_TYPE_ERROR(emit, + MP_ERROR_TEXT("div/mod not implemented for uint"), mp_binary_op_method_name[op]); + } if (op == MP_BINARY_OP_FLOOR_DIVIDE) { emit_call(emit, MP_F_SMALL_INT_FLOOR_DIVIDE); } else { @@ -2334,31 +2343,35 @@ STATIC void emit_native_binary_op(emit_t *emit, mp_binary_op_t op) { if (op == MP_BINARY_OP_LSHIFT) { ASM_LSL_REG_REG(emit->as, REG_ARG_2, reg_rhs); } else { - ASM_ASR_REG_REG(emit->as, REG_ARG_2, reg_rhs); + if (vtype_lhs == VTYPE_UINT) { + ASM_LSR_REG_REG(emit->as, REG_ARG_2, reg_rhs); + } else { + ASM_ASR_REG_REG(emit->as, REG_ARG_2, reg_rhs); + } } - emit_post_push_reg(emit, VTYPE_INT, REG_ARG_2); + emit_post_push_reg(emit, vtype_lhs, REG_ARG_2); return; } #endif if (op == MP_BINARY_OP_OR) { ASM_OR_REG_REG(emit->as, REG_ARG_2, reg_rhs); - emit_post_push_reg(emit, VTYPE_INT, REG_ARG_2); + emit_post_push_reg(emit, vtype_lhs, REG_ARG_2); } else if (op == MP_BINARY_OP_XOR) { ASM_XOR_REG_REG(emit->as, REG_ARG_2, reg_rhs); - emit_post_push_reg(emit, VTYPE_INT, REG_ARG_2); + emit_post_push_reg(emit, vtype_lhs, REG_ARG_2); } else if (op == MP_BINARY_OP_AND) { ASM_AND_REG_REG(emit->as, REG_ARG_2, reg_rhs); - emit_post_push_reg(emit, VTYPE_INT, REG_ARG_2); + emit_post_push_reg(emit, vtype_lhs, REG_ARG_2); } else if (op == MP_BINARY_OP_ADD) { ASM_ADD_REG_REG(emit->as, REG_ARG_2, reg_rhs); - emit_post_push_reg(emit, VTYPE_INT, REG_ARG_2); + emit_post_push_reg(emit, vtype_lhs, REG_ARG_2); } else if (op == MP_BINARY_OP_SUBTRACT) { ASM_SUB_REG_REG(emit->as, REG_ARG_2, reg_rhs); - emit_post_push_reg(emit, VTYPE_INT, REG_ARG_2); + emit_post_push_reg(emit, vtype_lhs, REG_ARG_2); } else if (op == MP_BINARY_OP_MULTIPLY) { ASM_MUL_REG_REG(emit->as, REG_ARG_2, reg_rhs); - emit_post_push_reg(emit, VTYPE_INT, REG_ARG_2); + emit_post_push_reg(emit, vtype_lhs, REG_ARG_2); } else if (MP_BINARY_OP_LESS <= op && op <= MP_BINARY_OP_NOT_EQUAL) { // comparison ops are (in enum order): // MP_BINARY_OP_LESS @@ -2367,11 +2380,26 @@ STATIC void emit_native_binary_op(emit_t *emit, mp_binary_op_t op) { // MP_BINARY_OP_LESS_EQUAL // MP_BINARY_OP_MORE_EQUAL // MP_BINARY_OP_NOT_EQUAL + + if (vtype_lhs != vtype_rhs) { + EMIT_NATIVE_VIPER_TYPE_ERROR(emit, MP_ERROR_TEXT("comparison of int and uint")); + } + + size_t op_idx = op - MP_BINARY_OP_LESS + (vtype_lhs == VTYPE_UINT ? 0 : 6); + need_reg_single(emit, REG_RET, 0); #if N_X64 asm_x64_xor_r64_r64(emit->as, REG_RET, REG_RET); asm_x64_cmp_r64_with_r64(emit->as, reg_rhs, REG_ARG_2); - static byte ops[6] = { + static byte ops[6 + 6] = { + // unsigned + ASM_X64_CC_JB, + ASM_X64_CC_JA, + ASM_X64_CC_JE, + ASM_X64_CC_JBE, + ASM_X64_CC_JAE, + ASM_X64_CC_JNE, + // signed ASM_X64_CC_JL, ASM_X64_CC_JG, ASM_X64_CC_JE, @@ -2379,11 +2407,19 @@ STATIC void emit_native_binary_op(emit_t *emit, mp_binary_op_t op) { ASM_X64_CC_JGE, ASM_X64_CC_JNE, }; - asm_x64_setcc_r8(emit->as, ops[op - MP_BINARY_OP_LESS], REG_RET); + asm_x64_setcc_r8(emit->as, ops[op_idx], REG_RET); #elif N_X86 asm_x86_xor_r32_r32(emit->as, REG_RET, REG_RET); asm_x86_cmp_r32_with_r32(emit->as, reg_rhs, REG_ARG_2); - static byte ops[6] = { + static byte ops[6 + 6] = { + // unsigned + ASM_X86_CC_JB, + ASM_X86_CC_JA, + ASM_X86_CC_JE, + ASM_X86_CC_JBE, + ASM_X86_CC_JAE, + ASM_X86_CC_JNE, + // signed ASM_X86_CC_JL, ASM_X86_CC_JG, ASM_X86_CC_JE, @@ -2391,24 +2427,39 @@ STATIC void emit_native_binary_op(emit_t *emit, mp_binary_op_t op) { ASM_X86_CC_JGE, ASM_X86_CC_JNE, }; - asm_x86_setcc_r8(emit->as, ops[op - MP_BINARY_OP_LESS], REG_RET); + asm_x86_setcc_r8(emit->as, ops[op_idx], REG_RET); #elif N_THUMB asm_thumb_cmp_rlo_rlo(emit->as, REG_ARG_2, reg_rhs); - static uint16_t ops[6] = { - ASM_THUMB_OP_ITE_GE, - ASM_THUMB_OP_ITE_GT, + static uint16_t ops[6 + 6] = { + // unsigned + ASM_THUMB_OP_ITE_CC, + ASM_THUMB_OP_ITE_HI, ASM_THUMB_OP_ITE_EQ, + ASM_THUMB_OP_ITE_LS, + ASM_THUMB_OP_ITE_CS, + ASM_THUMB_OP_ITE_NE, + // signed + ASM_THUMB_OP_ITE_LT, ASM_THUMB_OP_ITE_GT, - ASM_THUMB_OP_ITE_GE, ASM_THUMB_OP_ITE_EQ, + ASM_THUMB_OP_ITE_LE, + ASM_THUMB_OP_ITE_GE, + ASM_THUMB_OP_ITE_NE, }; - static byte ret[6] = { 0, 1, 1, 0, 1, 0, }; - asm_thumb_op16(emit->as, ops[op - MP_BINARY_OP_LESS]); - asm_thumb_mov_rlo_i8(emit->as, REG_RET, ret[op - MP_BINARY_OP_LESS]); - asm_thumb_mov_rlo_i8(emit->as, REG_RET, ret[op - MP_BINARY_OP_LESS] ^ 1); + asm_thumb_op16(emit->as, ops[op_idx]); + asm_thumb_mov_rlo_i8(emit->as, REG_RET, 1); + asm_thumb_mov_rlo_i8(emit->as, REG_RET, 0); #elif N_ARM asm_arm_cmp_reg_reg(emit->as, REG_ARG_2, reg_rhs); - static uint ccs[6] = { + static uint ccs[6 + 6] = { + // unsigned + ASM_ARM_CC_CC, + ASM_ARM_CC_HI, + ASM_ARM_CC_EQ, + ASM_ARM_CC_LS, + ASM_ARM_CC_CS, + ASM_ARM_CC_NE, + // signed ASM_ARM_CC_LT, ASM_ARM_CC_GT, ASM_ARM_CC_EQ, @@ -2416,9 +2467,17 @@ STATIC void emit_native_binary_op(emit_t *emit, mp_binary_op_t op) { ASM_ARM_CC_GE, ASM_ARM_CC_NE, }; - asm_arm_setcc_reg(emit->as, REG_RET, ccs[op - MP_BINARY_OP_LESS]); + asm_arm_setcc_reg(emit->as, REG_RET, ccs[op_idx]); #elif N_XTENSA || N_XTENSAWIN - static uint8_t ccs[6] = { + static uint8_t ccs[6 + 6] = { + // unsigned + ASM_XTENSA_CC_LTU, + 0x80 | ASM_XTENSA_CC_LTU, // for GTU we'll swap args + ASM_XTENSA_CC_EQ, + 0x80 | ASM_XTENSA_CC_GEU, // for LEU we'll swap args + ASM_XTENSA_CC_GEU, + ASM_XTENSA_CC_NE, + // signed ASM_XTENSA_CC_LT, 0x80 | ASM_XTENSA_CC_LT, // for GT we'll swap args ASM_XTENSA_CC_EQ, @@ -2426,7 +2485,7 @@ STATIC void emit_native_binary_op(emit_t *emit, mp_binary_op_t op) { ASM_XTENSA_CC_GE, ASM_XTENSA_CC_NE, }; - uint8_t cc = ccs[op - MP_BINARY_OP_LESS]; + uint8_t cc = ccs[op_idx]; if ((cc & 0x80) == 0) { asm_xtensa_setcc_reg_reg_reg(emit->as, cc, REG_RET, REG_ARG_2, reg_rhs); } else { diff --git a/py/grammar.h b/py/grammar.h index c3d30cdf7c467..285fbded2fdae 100644 --- a/py/grammar.h +++ b/py/grammar.h @@ -3,7 +3,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2013-2015 Damien P. George + * Copyright (c) 2013-2020 Damien P. George * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -30,6 +30,11 @@ // - zero_or_more is implemented using opt_rule around a one_or_more rule // - don't put opt_rule in arguments of or rule; instead, wrap the call to this or rule in opt_rule +// Generic sub-rules used by multiple rules below. + +DEF_RULE_NC(generic_colon_test, and_ident(2), tok(DEL_COLON), rule(test)) +DEF_RULE_NC(generic_equal_test, and_ident(2), tok(DEL_EQUAL), rule(test)) + // # Start symbols for the grammar: // # single_input is a single interactive statement; // # file_input is a module or sequence of commands read from an input file; @@ -71,19 +76,16 @@ DEF_RULE_NC(funcdefrettype, and_ident(2), tok(DEL_MINUS_MORE), rule(test)) // note: typedargslist lets through more than is allowed, compiler does further checks DEF_RULE_NC(typedargslist, list_with_end, rule(typedargslist_item), tok(DEL_COMMA)) DEF_RULE_NC(typedargslist_item, or(3), rule(typedargslist_name), rule(typedargslist_star), rule(typedargslist_dbl_star)) -DEF_RULE_NC(typedargslist_name, and_ident(3), tok(NAME), opt_rule(typedargslist_colon), opt_rule(typedargslist_equal)) +DEF_RULE_NC(typedargslist_name, and_ident(3), tok(NAME), opt_rule(generic_colon_test), opt_rule(generic_equal_test)) DEF_RULE_NC(typedargslist_star, and(2), tok(OP_STAR), opt_rule(tfpdef)) -DEF_RULE_NC(typedargslist_dbl_star, and(3), tok(OP_DBL_STAR), tok(NAME), opt_rule(typedargslist_colon)) -DEF_RULE_NC(typedargslist_colon, and_ident(2), tok(DEL_COLON), rule(test)) -DEF_RULE_NC(typedargslist_equal, and_ident(2), tok(DEL_EQUAL), rule(test)) -DEF_RULE_NC(tfpdef, and(2), tok(NAME), opt_rule(typedargslist_colon)) +DEF_RULE_NC(typedargslist_dbl_star, and(3), tok(OP_DBL_STAR), tok(NAME), opt_rule(generic_colon_test)) +DEF_RULE_NC(tfpdef, and(2), tok(NAME), opt_rule(generic_colon_test)) // note: varargslist lets through more than is allowed, compiler does further checks DEF_RULE_NC(varargslist, list_with_end, rule(varargslist_item), tok(DEL_COMMA)) DEF_RULE_NC(varargslist_item, or(3), rule(varargslist_name), rule(varargslist_star), rule(varargslist_dbl_star)) -DEF_RULE_NC(varargslist_name, and_ident(2), tok(NAME), opt_rule(varargslist_equal)) +DEF_RULE_NC(varargslist_name, and_ident(2), tok(NAME), opt_rule(generic_equal_test)) DEF_RULE_NC(varargslist_star, and(2), tok(OP_STAR), opt_rule(vfpdef)) DEF_RULE_NC(varargslist_dbl_star, and(2), tok(OP_DBL_STAR), tok(NAME)) -DEF_RULE_NC(varargslist_equal, and_ident(2), tok(DEL_EQUAL), rule(test)) DEF_RULE_NC(vfpdef, and_ident(1), tok(NAME)) // stmt: compound_stmt | simple_stmt @@ -96,20 +98,22 @@ DEF_RULE_NC(simple_stmt, and_ident(2), rule(simple_stmt_2), tok(NEWLINE)) DEF_RULE(simple_stmt_2, c(generic_all_nodes), list_with_end, rule(small_stmt), tok(DEL_SEMICOLON)) // small_stmt: expr_stmt | del_stmt | pass_stmt | flow_stmt | import_stmt | global_stmt | nonlocal_stmt | assert_stmt -// expr_stmt: testlist_star_expr (augassign (yield_expr|testlist) | ('=' (yield_expr|testlist_star_expr))*) +// expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) | ('=' (yield_expr|testlist_star_expr))*) // testlist_star_expr: (test|star_expr) (',' (test|star_expr))* [','] +// annassign: ':' test ['=' (yield_expr|testlist_star_expr)] // augassign: '+=' | '-=' | '*=' | '@=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<=' | '>>=' | '**=' | '//=' -// # For normal assignments, additional restrictions enforced by the interpreter +// # For normal and annotated assignments, additional restrictions enforced by the interpreter DEF_RULE_NC(small_stmt, or(8), rule(del_stmt), rule(pass_stmt), rule(flow_stmt), rule(import_stmt), rule(global_stmt), rule(nonlocal_stmt), rule(assert_stmt), rule(expr_stmt)) DEF_RULE(expr_stmt, c(expr_stmt), and(2), rule(testlist_star_expr), opt_rule(expr_stmt_2)) -DEF_RULE_NC(expr_stmt_2, or(2), rule(expr_stmt_augassign), rule(expr_stmt_assign_list)) +DEF_RULE_NC(expr_stmt_2, or(3), rule(annassign), rule(expr_stmt_augassign), rule(expr_stmt_assign_list)) DEF_RULE_NC(expr_stmt_augassign, and_ident(2), rule(augassign), rule(expr_stmt_6)) DEF_RULE_NC(expr_stmt_assign_list, one_or_more, rule(expr_stmt_assign)) DEF_RULE_NC(expr_stmt_assign, and_ident(2), tok(DEL_EQUAL), rule(expr_stmt_6)) DEF_RULE_NC(expr_stmt_6, or(2), rule(yield_expr), rule(testlist_star_expr)) DEF_RULE(testlist_star_expr, c(generic_tuple), list_with_end, rule(testlist_star_expr_2), tok(DEL_COMMA)) DEF_RULE_NC(testlist_star_expr_2, or(2), rule(star_expr), rule(test)) +DEF_RULE_NC(annassign, and(3), tok(DEL_COLON), rule(test), opt_rule(expr_stmt_assign)) DEF_RULE_NC(augassign, or(13), tok(DEL_PLUS_EQUAL), tok(DEL_MINUS_EQUAL), tok(DEL_STAR_EQUAL), tok(DEL_AT_EQUAL), tok(DEL_SLASH_EQUAL), tok(DEL_PERCENT_EQUAL), tok(DEL_AMPERSAND_EQUAL), tok(DEL_PIPE_EQUAL), tok(DEL_CARET_EQUAL), tok(DEL_DBL_LESS_EQUAL), tok(DEL_DBL_MORE_EQUAL), tok(DEL_DBL_STAR_EQUAL), tok(DEL_DBL_SLASH_EQUAL)) // del_stmt: 'del' exprlist @@ -184,10 +188,10 @@ DEF_RULE_NC(async_stmt_2, or(3), rule(funcdef), rule(with_stmt), rule(for_stmt)) #else DEF_RULE_NC(compound_stmt, or(8), rule(if_stmt), rule(while_stmt), rule(for_stmt), rule(try_stmt), rule(with_stmt), rule(funcdef), rule(classdef), rule(decorated)) #endif -DEF_RULE(if_stmt, c(if_stmt), and(6), tok(KW_IF), rule(test), tok(DEL_COLON), rule(suite), opt_rule(if_stmt_elif_list), opt_rule(else_stmt)) +DEF_RULE(if_stmt, c(if_stmt), and(6), tok(KW_IF), rule(namedexpr_test), tok(DEL_COLON), rule(suite), opt_rule(if_stmt_elif_list), opt_rule(else_stmt)) DEF_RULE_NC(if_stmt_elif_list, one_or_more, rule(if_stmt_elif)) -DEF_RULE_NC(if_stmt_elif, and(4), tok(KW_ELIF), rule(test), tok(DEL_COLON), rule(suite)) -DEF_RULE(while_stmt, c(while_stmt), and(5), tok(KW_WHILE), rule(test), tok(DEL_COLON), rule(suite), opt_rule(else_stmt)) +DEF_RULE_NC(if_stmt_elif, and(4), tok(KW_ELIF), rule(namedexpr_test), tok(DEL_COLON), rule(suite)) +DEF_RULE(while_stmt, c(while_stmt), and(5), tok(KW_WHILE), rule(namedexpr_test), tok(DEL_COLON), rule(suite), opt_rule(else_stmt)) DEF_RULE(for_stmt, c(for_stmt), and(7), tok(KW_FOR), rule(exprlist), tok(KW_IN), rule(testlist), tok(DEL_COLON), rule(suite), opt_rule(else_stmt)) DEF_RULE(try_stmt, c(try_stmt), and(4), tok(KW_TRY), tok(DEL_COLON), rule(suite), rule(try_stmt_2)) DEF_RULE_NC(try_stmt_2, or(2), rule(try_stmt_except_and_more), rule(try_stmt_finally)) @@ -210,6 +214,12 @@ DEF_RULE(suite_block_stmts, c(generic_all_nodes), one_or_more, rule(stmt)) // lambdef: 'lambda' [varargslist] ':' test // lambdef_nocond: 'lambda' [varargslist] ':' test_nocond +#if MICROPY_PY_ASSIGN_EXPR +DEF_RULE(namedexpr_test, c(namedexpr), and_ident(2), rule(test), opt_rule(namedexpr_test_2)) +DEF_RULE_NC(namedexpr_test_2, and_ident(2), tok(OP_ASSIGN), rule(test)) +#else +DEF_RULE_NC(namedexpr_test, or(1), rule(test)) +#endif DEF_RULE_NC(test, or(2), rule(lambdef), rule(test_if_expr)) DEF_RULE(test_if_expr, c(test_if_expr), and_ident(2), rule(or_test), opt_rule(test_if_else)) DEF_RULE_NC(test_if_else, and(4), tok(KW_IF), rule(or_test), tok(KW_ELSE), rule(test)) @@ -276,7 +286,7 @@ DEF_RULE_NC(atom_2b, or(2), rule(yield_expr), rule(testlist_comp)) DEF_RULE(atom_bracket, c(atom_bracket), and(3), tok(DEL_BRACKET_OPEN), opt_rule(testlist_comp), tok(DEL_BRACKET_CLOSE)) DEF_RULE(atom_brace, c(atom_brace), and(3), tok(DEL_BRACE_OPEN), opt_rule(dictorsetmaker), tok(DEL_BRACE_CLOSE)) DEF_RULE_NC(testlist_comp, and_ident(2), rule(testlist_comp_2), opt_rule(testlist_comp_3)) -DEF_RULE_NC(testlist_comp_2, or(2), rule(star_expr), rule(test)) +DEF_RULE_NC(testlist_comp_2, or(2), rule(star_expr), rule(namedexpr_test)) DEF_RULE_NC(testlist_comp_3, or(2), rule(comp_for), rule(testlist_comp_3b)) DEF_RULE_NC(testlist_comp_3b, and_ident(2), tok(DEL_COMMA), opt_rule(testlist_comp_3c)) DEF_RULE_NC(testlist_comp_3c, list_with_end, rule(testlist_comp_2), tok(DEL_COMMA)) @@ -312,8 +322,7 @@ DEF_RULE(testlist, c(generic_tuple), list_with_end, rule(test), tok(DEL_COMMA)) // TODO dictorsetmaker lets through more than is allowed DEF_RULE_NC(dictorsetmaker, and_ident(2), rule(dictorsetmaker_item), opt_rule(dictorsetmaker_tail)) #if MICROPY_PY_BUILTINS_SET -DEF_RULE(dictorsetmaker_item, c(dictorsetmaker_item), and_ident(2), rule(test), opt_rule(dictorsetmaker_colon)) -DEF_RULE_NC(dictorsetmaker_colon, and_ident(2), tok(DEL_COLON), rule(test)) +DEF_RULE(dictorsetmaker_item, c(dictorsetmaker_item), and_ident(2), rule(test), opt_rule(generic_colon_test)) #else DEF_RULE(dictorsetmaker_item, c(dictorsetmaker_item), and(3), rule(test), tok(DEL_COLON), rule(test)) #endif @@ -342,8 +351,12 @@ DEF_RULE_NC(arglist_dbl_star, and(2), tok(OP_DBL_STAR), rule(test)) // comp_if: 'if' test_nocond [comp_iter] DEF_RULE_NC(argument, and_ident(2), rule(test), opt_rule(argument_2)) -DEF_RULE_NC(argument_2, or(2), rule(comp_for), rule(argument_3)) -DEF_RULE_NC(argument_3, and_ident(2), tok(DEL_EQUAL), rule(test)) +#if MICROPY_PY_ASSIGN_EXPR +DEF_RULE_NC(argument_2, or(3), rule(comp_for), rule(generic_equal_test), rule(argument_3)) +DEF_RULE_NC(argument_3, and(2), tok(OP_ASSIGN), rule(test)) +#else +DEF_RULE_NC(argument_2, or(2), rule(comp_for), rule(generic_equal_test)) +#endif DEF_RULE_NC(comp_iter, or(2), rule(comp_for), rule(comp_if)) DEF_RULE_NC(comp_for, and_blank(5), tok(KW_FOR), rule(exprlist), tok(KW_IN), rule(or_test), opt_rule(comp_iter)) DEF_RULE_NC(comp_if, and(3), tok(KW_IF), rule(test_nocond), opt_rule(comp_iter)) diff --git a/py/lexer.c b/py/lexer.c index 10bb999af6b70..7d2a251d41d75 100644 --- a/py/lexer.c +++ b/py/lexer.c @@ -174,7 +174,8 @@ STATIC void indent_pop(mp_lexer_t *lex) { // this means if the start of two ops are the same then they are equal til the last char STATIC const char *const tok_enc = - "()[]{},:;~" // singles + "()[]{},;~" // singles + ":e=" // : := " >= >> >>= "*e=c*e=" // * *= ** **= @@ -194,8 +195,9 @@ STATIC const uint8_t tok_enc_kind[] = { MP_TOKEN_DEL_PAREN_OPEN, MP_TOKEN_DEL_PAREN_CLOSE, MP_TOKEN_DEL_BRACKET_OPEN, MP_TOKEN_DEL_BRACKET_CLOSE, MP_TOKEN_DEL_BRACE_OPEN, MP_TOKEN_DEL_BRACE_CLOSE, - MP_TOKEN_DEL_COMMA, MP_TOKEN_DEL_COLON, MP_TOKEN_DEL_SEMICOLON, MP_TOKEN_OP_TILDE, + MP_TOKEN_DEL_COMMA, MP_TOKEN_DEL_SEMICOLON, MP_TOKEN_OP_TILDE, + MP_TOKEN_DEL_COLON, MP_TOKEN_OP_ASSIGN, MP_TOKEN_OP_LESS, MP_TOKEN_OP_LESS_EQUAL, MP_TOKEN_OP_DBL_LESS, MP_TOKEN_DEL_DBL_LESS_EQUAL, MP_TOKEN_OP_MORE, MP_TOKEN_OP_MORE_EQUAL, MP_TOKEN_OP_DBL_MORE, MP_TOKEN_DEL_DBL_MORE_EQUAL, MP_TOKEN_OP_STAR, MP_TOKEN_DEL_STAR_EQUAL, MP_TOKEN_OP_DBL_STAR, MP_TOKEN_DEL_DBL_STAR_EQUAL, diff --git a/py/lexer.h b/py/lexer.h index b9f97013a1bd9..91767a44bf991 100644 --- a/py/lexer.h +++ b/py/lexer.h @@ -96,6 +96,7 @@ typedef enum _mp_token_kind_t { MP_TOKEN_KW_WITH, MP_TOKEN_KW_YIELD, + MP_TOKEN_OP_ASSIGN, MP_TOKEN_OP_TILDE, // Order of these 6 matches corresponding mp_binary_op_t operator diff --git a/py/makemoduledefs.py b/py/makemoduledefs.py index d718da6e02513..612f3d29a3660 100644 --- a/py/makemoduledefs.py +++ b/py/makemoduledefs.py @@ -17,7 +17,7 @@ def find_c_file(obj_file, vpath): - """ Search vpaths for the c file that matches the provided object_file. + """Search vpaths for the c file that matches the provided object_file. :param str obj_file: object file to find the matching c file for :param List[str] vpath: List of base paths, similar to gcc vpath @@ -36,7 +36,7 @@ def find_c_file(obj_file, vpath): def find_module_registrations(c_file): - """ Find any MP_REGISTER_MODULE definitions in the provided c file. + """Find any MP_REGISTER_MODULE definitions in the provided c file. :param str c_file: path to c file to check :return: List[(module_name, obj_module, enabled_define)] @@ -52,7 +52,7 @@ def find_module_registrations(c_file): def generate_module_table_header(modules): - """ Generate header with module table entries for builtin modules. + """Generate header with module table entries for builtin modules. :param List[(module_name, obj_module, enabled_define)] modules: module defs :return: None diff --git a/py/makeversionhdr.py b/py/makeversionhdr.py index 567b3b8322f9f..970a86e6ca3ab 100644 --- a/py/makeversionhdr.py +++ b/py/makeversionhdr.py @@ -23,7 +23,7 @@ def get_version_info_from_git(): # Note: git describe doesn't work if no tag is available try: git_tag = subprocess.check_output( - ["git", "describe", "--dirty", "--always"], + ["git", "describe", "--dirty", "--always", "--match", "v[1-9].*"], stderr=subprocess.STDOUT, universal_newlines=True, ).strip() diff --git a/py/map.c b/py/map.c index 676c364da76a1..54f4b0204b87f 100644 --- a/py/map.c +++ b/py/map.c @@ -40,17 +40,6 @@ #define DEBUG_printf(...) (void)0 #endif -// Fixed empty map. Useful when need to call kw-receiving functions -// without any keywords from C, etc. -const mp_map_t mp_const_empty_map = { - .all_keys_are_qstrs = 0, - .is_fixed = 1, - .is_ordered = 1, - .used = 0, - .alloc = 0, - .table = NULL, -}; - // This table of sizes is used to control the growth of hash tables. // The first set of sizes are chosen so the allocation fits exactly in a // 4-word GC block, and it's not so important for these small values to be diff --git a/py/misc.h b/py/misc.h index c7060ddf9d223..2dd20365c934c 100644 --- a/py/misc.h +++ b/py/misc.h @@ -247,7 +247,7 @@ typedef union _mp_float_union_t { } p; #else struct { - mp_float_uint_t sgn : 1 + mp_float_uint_t sgn : 1; mp_float_uint_t exp : MP_FLOAT_EXP_BITS; mp_float_uint_t frc : MP_FLOAT_FRAC_BITS; } p; diff --git a/py/modbuiltins.c b/py/modbuiltins.c index 85d30ab66d052..cfbfc5a25683e 100644 --- a/py/modbuiltins.c +++ b/py/modbuiltins.c @@ -558,7 +558,11 @@ MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_sorted_obj, 1, mp_builtin_sorted); static inline mp_obj_t mp_load_attr_default(mp_obj_t base, qstr attr, mp_obj_t defval) { mp_obj_t dest[2]; // use load_method, raising or not raising exception - ((defval == MP_OBJ_NULL) ? mp_load_method : mp_load_method_maybe)(base, attr, dest); + if (defval == MP_OBJ_NULL) { + mp_load_method(base, attr, dest); + } else { + mp_load_method_protected(base, attr, dest, false); + } if (dest[0] == MP_OBJ_NULL) { return defval; } else if (dest[1] == MP_OBJ_NULL) { diff --git a/py/modmath.c b/py/modmath.c index 5ff892ba112c8..b7948f39e7c91 100644 --- a/py/modmath.c +++ b/py/modmath.c @@ -34,6 +34,8 @@ // M_PI is not part of the math.h standard and may not be defined // And by defining our own we can ensure it uses the correct const format. #define MP_PI MICROPY_FLOAT_CONST(3.14159265358979323846) +#define MP_PI_4 MICROPY_FLOAT_CONST(0.78539816339744830962) +#define MP_3_PI_4 MICROPY_FLOAT_CONST(2.35619449019234492885) STATIC NORETURN void math_error(void) { mp_raise_ValueError(MP_ERROR_TEXT("math domain error")); @@ -96,7 +98,19 @@ mp_float_t MICROPY_FLOAT_C_FUN(log2)(mp_float_t x) { // sqrt(x): returns the square root of x MATH_FUN_1(sqrt, sqrt) // pow(x, y): returns x to the power of y +#if MICROPY_PY_MATH_POW_FIX_NAN +mp_float_t pow_func(mp_float_t x, mp_float_t y) { + // pow(base, 0) returns 1 for any base, even when base is NaN + // pow(+1, exponent) returns 1 for any exponent, even when exponent is NaN + if (x == MICROPY_FLOAT_CONST(1.0) || y == MICROPY_FLOAT_CONST(0.0)) { + return MICROPY_FLOAT_CONST(1.0); + } + return MICROPY_FLOAT_C_FUN(pow)(x, y); +} +MATH_FUN_2(pow, pow_func) +#else MATH_FUN_2(pow, pow) +#endif // exp(x) MATH_FUN_1(exp, exp) #if MICROPY_PY_MATH_SPECIAL_FUNCTIONS @@ -132,7 +146,17 @@ MATH_FUN_1(asin, asin) // atan(x) MATH_FUN_1(atan, atan) // atan2(y, x) +#if MICROPY_PY_MATH_ATAN2_FIX_INFNAN +mp_float_t atan2_func(mp_float_t x, mp_float_t y) { + if (isinf(x) && isinf(y)) { + return copysign(y < 0 ? MP_3_PI_4 : MP_PI_4, x); + } + return atan2(x, y); +} +MATH_FUN_2(atan2, atan2_func) +#else MATH_FUN_2(atan2, atan2) +#endif // ceil(x) MATH_FUN_1_TO_INT(ceil, ceil) // copysign(x, y) @@ -148,7 +172,14 @@ MATH_FUN_1(fabs, fabs_func) // floor(x) MATH_FUN_1_TO_INT(floor, floor) // TODO: delegate to x.__floor__() if x is not a float // fmod(x, y) +#if MICROPY_PY_MATH_FMOD_FIX_INFNAN +mp_float_t fmod_func(mp_float_t x, mp_float_t y) { + return (!isinf(x) && isinf(y)) ? x : fmod(x, y); +} +MATH_FUN_2(fmod, fmod_func) +#else MATH_FUN_2(fmod, fmod) +#endif // isfinite(x) MATH_FUN_1_TO_BOOL(isfinite, isfinite) // isinf(x) @@ -246,7 +277,13 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_math_frexp_obj, mp_math_frexp); // modf(x) STATIC mp_obj_t mp_math_modf(mp_obj_t x_obj) { mp_float_t int_part = 0.0; - mp_float_t fractional_part = MICROPY_FLOAT_C_FUN(modf)(mp_obj_get_float(x_obj), &int_part); + mp_float_t x = mp_obj_get_float(x_obj); + mp_float_t fractional_part = MICROPY_FLOAT_C_FUN(modf)(x, &int_part); + #if MICROPY_PY_MATH_MODF_FIX_NEGZERO + if (fractional_part == MICROPY_FLOAT_CONST(0.0)) { + fractional_part = copysign(fractional_part, x); + } + #endif mp_obj_t tuple[2]; tuple[0] = mp_obj_new_float(fractional_part); tuple[1] = mp_obj_new_float(int_part); diff --git a/py/modsys.c b/py/modsys.c index 8092d9799d81f..586b13e747130 100644 --- a/py/modsys.c +++ b/py/modsys.c @@ -53,7 +53,7 @@ const mp_print_t mp_sys_stdout_print = {&mp_sys_stdout_obj, mp_stream_write_adap #endif // version - Python language version that this implementation conforms to, as a string -STATIC const MP_DEFINE_STR_OBJ(version_obj, "3.4.0"); +STATIC const MP_DEFINE_STR_OBJ(mp_sys_version_obj, "3.4.0"); // version_info - Python language version that this implementation conforms to, as a tuple of ints #define I(n) MP_OBJ_NEW_SMALL_INT(n) @@ -105,7 +105,7 @@ STATIC const mp_rom_obj_tuple_t mp_sys_implementation_obj = { #ifdef MICROPY_PY_SYS_PLATFORM // platform - the platform that MicroPython is running on -STATIC const MP_DEFINE_STR_OBJ(platform_obj, MICROPY_PY_SYS_PLATFORM); +STATIC const MP_DEFINE_STR_OBJ(mp_sys_platform_obj, MICROPY_PY_SYS_PLATFORM); #endif // exit([retval]): raise SystemExit, with optional argument given to the exception @@ -189,11 +189,11 @@ STATIC const mp_rom_map_elem_t mp_module_sys_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_path), MP_ROM_PTR(&MP_STATE_VM(mp_sys_path_obj)) }, { MP_ROM_QSTR(MP_QSTR_argv), MP_ROM_PTR(&MP_STATE_VM(mp_sys_argv_obj)) }, - { MP_ROM_QSTR(MP_QSTR_version), MP_ROM_PTR(&version_obj) }, + { MP_ROM_QSTR(MP_QSTR_version), MP_ROM_PTR(&mp_sys_version_obj) }, { MP_ROM_QSTR(MP_QSTR_version_info), MP_ROM_PTR(&mp_sys_version_info_obj) }, { MP_ROM_QSTR(MP_QSTR_implementation), MP_ROM_PTR(&mp_sys_implementation_obj) }, #ifdef MICROPY_PY_SYS_PLATFORM - { MP_ROM_QSTR(MP_QSTR_platform), MP_ROM_PTR(&platform_obj) }, + { MP_ROM_QSTR(MP_QSTR_platform), MP_ROM_PTR(&mp_sys_platform_obj) }, #endif #if MP_ENDIANNESS_LITTLE { MP_ROM_QSTR(MP_QSTR_byteorder), MP_ROM_QSTR(MP_QSTR_little) }, @@ -210,7 +210,7 @@ STATIC const mp_rom_map_elem_t mp_module_sys_globals_table[] = { // of "one" bits in sys.maxsize. { MP_ROM_QSTR(MP_QSTR_maxsize), MP_ROM_INT(MP_SMALL_INT_MAX) }, #else - { MP_ROM_QSTR(MP_QSTR_maxsize), MP_ROM_PTR(&mp_maxsize_obj) }, + { MP_ROM_QSTR(MP_QSTR_maxsize), MP_ROM_PTR(&mp_sys_maxsize_obj) }, #endif #endif diff --git a/py/mpconfig.h b/py/mpconfig.h index f2b3af1f2a314..cc83f3850d695 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -28,7 +28,7 @@ // Current version of MicroPython #define MICROPY_VERSION_MAJOR 1 -#define MICROPY_VERSION_MINOR 12 +#define MICROPY_VERSION_MINOR 13 #define MICROPY_VERSION_MICRO 0 // Combined version as a 32-bit number for convenience @@ -351,6 +351,18 @@ // Convenience definition for whether any native or inline assembler emitter is enabled #define MICROPY_EMIT_MACHINE_CODE (MICROPY_EMIT_NATIVE || MICROPY_EMIT_INLINE_ASM) +// Whether native relocatable code loaded from .mpy files is explicitly tracked +// so that the GC cannot reclaim it. Needed on architectures that allocate +// executable memory on the MicroPython heap and don't explicitly track this +// data some other way. +#ifndef MICROPY_PERSISTENT_CODE_TRACK_RELOC_CODE +#if !MICROPY_EMIT_MACHINE_CODE || defined(MP_PLAT_ALLOC_EXEC) || defined(MP_PLAT_COMMIT_EXEC) +#define MICROPY_PERSISTENT_CODE_TRACK_RELOC_CODE (0) +#else +#define MICROPY_PERSISTENT_CODE_TRACK_RELOC_CODE (1) +#endif +#endif + /*****************************************************************************/ /* Compiler configuration */ @@ -442,6 +454,11 @@ #define MICROPY_DEBUG_MP_OBJ_SENTINELS (0) #endif +// Whether to print parse rule names (rather than integers) in mp_parse_node_print +#ifndef MICROPY_DEBUG_PARSE_RULE_NAME +#define MICROPY_DEBUG_PARSE_RULE_NAME (0) +#endif + // Whether to enable a simple VM stack overflow check #ifndef MICROPY_DEBUG_VM_STACK_OVERFLOW #define MICROPY_DEBUG_VM_STACK_OVERFLOW (0) @@ -835,6 +852,11 @@ typedef double mp_float_t; #define MICROPY_PY_ASYNC_AWAIT (1) #endif +// Support for assignment expressions with := (see PEP 572, Python 3.8+) +#ifndef MICROPY_PY_ASSIGN_EXPR +#define MICROPY_PY_ASSIGN_EXPR (1) +#endif + // Non-standard .pend_throw() method for generators, allowing for // Future-like behavior with respect to exception handling: an // exception set with .pend_throw() will activate on the next call @@ -1128,6 +1150,26 @@ typedef double mp_float_t; #define MICROPY_PY_MATH_ISCLOSE (0) #endif +// Whether to provide fix for atan2 Inf handling. +#ifndef MICROPY_PY_MATH_ATAN2_FIX_INFNAN +#define MICROPY_PY_MATH_ATAN2_FIX_INFNAN (0) +#endif + +// Whether to provide fix for fmod Inf handling. +#ifndef MICROPY_PY_MATH_FMOD_FIX_INFNAN +#define MICROPY_PY_MATH_FMOD_FIX_INFNAN (0) +#endif + +// Whether to provide fix for modf negative zero handling. +#ifndef MICROPY_PY_MATH_MODF_FIX_NEGZERO +#define MICROPY_PY_MATH_MODF_FIX_NEGZERO (0) +#endif + +// Whether to provide fix for pow(1, NaN) and pow(NaN, 0), which both should be 1 not NaN. +#ifndef MICROPY_PY_MATH_POW_FIX_NAN +#define MICROPY_PY_MATH_POW_FIX_NAN (0) +#endif + // Whether to provide "cmath" module #ifndef MICROPY_PY_CMATH #define MICROPY_PY_CMATH (0) diff --git a/py/mphal.h b/py/mphal.h index 66d80705a60ee..0d4b1224e5cec 100644 --- a/py/mphal.h +++ b/py/mphal.h @@ -26,6 +26,7 @@ #ifndef MICROPY_INCLUDED_PY_MPHAL_H #define MICROPY_INCLUDED_PY_MPHAL_H +#include #include "py/mpconfig.h" #ifdef MICROPY_MPHALPORT_H @@ -74,6 +75,11 @@ mp_uint_t mp_hal_ticks_us(void); mp_uint_t mp_hal_ticks_cpu(void); #endif +#ifndef mp_hal_time_ns +// Nanoseconds since the Epoch. +uint64_t mp_hal_time_ns(void); +#endif + // If port HAL didn't define its own pin API, use generic // "virtual pin" API from the core. #ifndef mp_hal_pin_obj_t diff --git a/py/mpstate.h b/py/mpstate.h index 5f6cf55936a32..2519c77e2d647 100644 --- a/py/mpstate.h +++ b/py/mpstate.h @@ -167,6 +167,11 @@ typedef struct _mp_state_vm_t { mp_obj_dict_t *mp_module_builtins_override_dict; #endif + #if MICROPY_PERSISTENT_CODE_TRACK_RELOC_CODE + // An mp_obj_list_t that tracks relocated native code to prevent the GC from reclaiming them. + mp_obj_t track_reloc_code_list; + #endif + // include any root pointers defined by a port MICROPY_PORT_ROOT_POINTERS diff --git a/py/obj.c b/py/obj.c index 07b1612552548..ed047acc39f76 100644 --- a/py/obj.c +++ b/py/obj.c @@ -371,7 +371,7 @@ mp_float_t mp_obj_get_float(mp_obj_t arg) { } #if MICROPY_PY_BUILTINS_COMPLEX -void mp_obj_get_complex(mp_obj_t arg, mp_float_t *real, mp_float_t *imag) { +bool mp_obj_get_complex_maybe(mp_obj_t arg, mp_float_t *real, mp_float_t *imag) { if (arg == mp_const_false) { *real = 0; *imag = 0; @@ -392,6 +392,13 @@ void mp_obj_get_complex(mp_obj_t arg, mp_float_t *real, mp_float_t *imag) { } else if (mp_obj_is_type(arg, &mp_type_complex)) { mp_obj_complex_get(arg, real, imag); } else { + return false; + } + return true; +} + +void mp_obj_get_complex(mp_obj_t arg, mp_float_t *real, mp_float_t *imag) { + if (!mp_obj_get_complex_maybe(arg, real, imag)) { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("can't convert to complex")); #else diff --git a/py/obj.h b/py/obj.h index 125acf118f08d..6a040b77739c2 100644 --- a/py/obj.h +++ b/py/obj.h @@ -407,8 +407,8 @@ typedef struct _mp_rom_map_elem_t { typedef struct _mp_map_t { size_t all_keys_are_qstrs : 1; - size_t is_fixed : 1; // a fixed array that can't be modified; must also be ordered - size_t is_ordered : 1; // an ordered array + size_t is_fixed : 1; // if set, table is fixed/read-only and can't be modified + size_t is_ordered : 1; // if set, table is an ordered array, not a hash map size_t used : (8 * sizeof(size_t) - 3); size_t alloc; mp_map_elem_t *table; @@ -422,8 +422,6 @@ typedef enum _mp_map_lookup_kind_t { MP_MAP_LOOKUP_ADD_IF_NOT_FOUND_OR_REMOVE_IF_FOUND = 3, // only valid for mp_set_lookup } mp_map_lookup_kind_t; -extern const mp_map_t mp_const_empty_map; - static inline bool mp_map_slot_is_filled(const mp_map_t *map, size_t pos) { assert(pos < map->alloc); return (map)->table[pos].key != MP_OBJ_NULL && (map)->table[pos].key != MP_OBJ_SENTINEL; @@ -473,11 +471,15 @@ typedef mp_obj_t (*mp_fun_kw_t)(size_t n, const mp_obj_t *, mp_map_t *); // then the type may check for equality against a different type. // If MP_TYPE_FLAG_EQ_HAS_NEQ_TEST is clear then the type only implements the __eq__ // operator and not the __ne__ operator. If it's set then __ne__ may be implemented. +// If MP_TYPE_FLAG_BINDS_SELF is set then the type as a method binds self as the first arg. +// If MP_TYPE_FLAG_BUILTIN_FUN is set then the type is a built-in function type. #define MP_TYPE_FLAG_IS_SUBCLASSED (0x0001) #define MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS (0x0002) -#define MP_TYPE_FLAG_EQ_NOT_REFLEXIVE (0x0040) -#define MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE (0x0080) +#define MP_TYPE_FLAG_EQ_NOT_REFLEXIVE (0x0004) +#define MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE (0x0008) #define MP_TYPE_FLAG_EQ_HAS_NEQ_TEST (0x0010) +#define MP_TYPE_FLAG_BINDS_SELF (0x0020) +#define MP_TYPE_FLAG_BUILTIN_FUN (0x0040) typedef enum { PRINT_STR = 0, @@ -681,17 +683,22 @@ extern const struct _mp_obj_bool_t mp_const_false_obj; extern const struct _mp_obj_bool_t mp_const_true_obj; #endif -// Constant objects, globally accessible: b'', (), Ellipsis, NotImplemented, GeneratorExit() +// Constant objects, globally accessible: b'', (), {}, Ellipsis, NotImplemented, GeneratorExit() // The below macros are for convenience only. #define mp_const_empty_bytes (MP_OBJ_FROM_PTR(&mp_const_empty_bytes_obj)) #define mp_const_empty_tuple (MP_OBJ_FROM_PTR(&mp_const_empty_tuple_obj)) #define mp_const_notimplemented (MP_OBJ_FROM_PTR(&mp_const_notimplemented_obj)) extern const struct _mp_obj_str_t mp_const_empty_bytes_obj; extern const struct _mp_obj_tuple_t mp_const_empty_tuple_obj; +extern const struct _mp_obj_dict_t mp_const_empty_dict_obj; extern const struct _mp_obj_singleton_t mp_const_ellipsis_obj; extern const struct _mp_obj_singleton_t mp_const_notimplemented_obj; extern const struct _mp_obj_exception_t mp_const_GeneratorExit_obj; +// Fixed empty map. Useful when calling keyword-receiving functions +// without any keywords from C, etc. +#define mp_const_empty_map (mp_const_empty_dict_obj.map) + // General API for objects // These macros are derived from more primitive ones and are used to @@ -708,6 +715,7 @@ extern const struct _mp_obj_exception_t mp_const_GeneratorExit_obj; #define mp_obj_is_int(o) (mp_obj_is_small_int(o) || mp_obj_is_type(o, &mp_type_int)) #define mp_obj_is_str(o) (mp_obj_is_qstr(o) || mp_obj_is_type(o, &mp_type_str)) #define mp_obj_is_str_or_bytes(o) (mp_obj_is_qstr(o) || (mp_obj_is_obj(o) && ((mp_obj_base_t *)MP_OBJ_TO_PTR(o))->type->binary_op == mp_obj_str_binary_op)) +#define mp_obj_is_dict_or_ordereddict(o) (mp_obj_is_obj(o) && ((mp_obj_base_t *)MP_OBJ_TO_PTR(o))->type->make_new == mp_obj_dict_make_new) #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); @@ -777,6 +785,7 @@ bool mp_obj_get_int_maybe(mp_const_obj_t arg, mp_int_t *value); mp_float_t mp_obj_get_float(mp_obj_t self_in); bool mp_obj_get_float_maybe(mp_obj_t arg, mp_float_t *value); void mp_obj_get_complex(mp_obj_t self_in, mp_float_t *real, mp_float_t *imag); +bool mp_obj_get_complex_maybe(mp_obj_t self_in, mp_float_t *real, mp_float_t *imag); #endif void mp_obj_get_array(mp_obj_t o, size_t *len, mp_obj_t **items); // *items may point inside a GC block void mp_obj_get_array_fixed_n(mp_obj_t o, size_t len, mp_obj_t **items); // *items may point inside a GC block @@ -890,11 +899,13 @@ typedef struct _mp_obj_dict_t { mp_obj_base_t base; mp_map_t map; } mp_obj_dict_t; +mp_obj_t mp_obj_dict_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args); void mp_obj_dict_init(mp_obj_dict_t *dict, size_t n_args); size_t mp_obj_dict_len(mp_obj_t self_in); mp_obj_t mp_obj_dict_get(mp_obj_t self_in, mp_obj_t index); mp_obj_t mp_obj_dict_store(mp_obj_t self_in, mp_obj_t key, mp_obj_t value); mp_obj_t mp_obj_dict_delete(mp_obj_t self_in, mp_obj_t key); +mp_obj_t mp_obj_dict_copy(mp_obj_t self_in); static inline mp_map_t *mp_obj_dict_get_map(mp_obj_t dict) { return &((mp_obj_dict_t *)MP_OBJ_TO_PTR(dict))->map; } @@ -984,17 +995,17 @@ bool mp_seq_cmp_objs(mp_uint_t op, const mp_obj_t *items1, size_t len1, const mp mp_obj_t mp_seq_index_obj(const mp_obj_t *items, size_t len, size_t n_args, const mp_obj_t *args); mp_obj_t mp_seq_count_obj(const mp_obj_t *items, size_t len, mp_obj_t value); mp_obj_t mp_seq_extract_slice(size_t len, const mp_obj_t *seq, mp_bound_slice_t *indexes); + // Helper to clear stale pointers from allocated, but unused memory, to preclude GC problems #define mp_seq_clear(start, len, alloc_len, item_sz) memset((byte *)(start) + (len) * (item_sz), 0, ((alloc_len) - (len)) * (item_sz)) + +// Note: dest and slice regions may overlap #define mp_seq_replace_slice_no_grow(dest, dest_len, beg, end, slice, slice_len, item_sz) \ - /*printf("memcpy(%p, %p, %d)\n", dest + beg, slice, slice_len * (item_sz));*/ \ - memcpy(((char *)dest) + (beg) * (item_sz), slice, slice_len * (item_sz)); \ - /*printf("memmove(%p, %p, %d)\n", dest + (beg + slice_len), dest + end, (dest_len - end) * (item_sz));*/ \ + memmove(((char *)dest) + (beg) * (item_sz), slice, slice_len * (item_sz)); \ memmove(((char *)dest) + (beg + slice_len) * (item_sz), ((char *)dest) + (end) * (item_sz), (dest_len - end) * (item_sz)); // Note: dest and slice regions may overlap #define mp_seq_replace_slice_grow_inplace(dest, dest_len, beg, end, slice, slice_len, len_adj, item_sz) \ - /*printf("memmove(%p, %p, %d)\n", dest + beg + len_adj, dest + beg, (dest_len - beg) * (item_sz));*/ \ memmove(((char *)dest) + (beg + slice_len) * (item_sz), ((char *)dest) + (end) * (item_sz), ((dest_len) + (len_adj) - ((beg) + (slice_len))) * (item_sz)); \ memmove(((char *)dest) + (beg) * (item_sz), slice, slice_len * (item_sz)); diff --git a/py/objarray.h b/py/objarray.h index 2fb6e2c9158f4..94c31c969386b 100644 --- a/py/objarray.h +++ b/py/objarray.h @@ -49,4 +49,14 @@ typedef struct _mp_obj_array_t { void *items; } mp_obj_array_t; +#if MICROPY_PY_BUILTINS_MEMORYVIEW +static inline void mp_obj_memoryview_init(mp_obj_array_t *self, size_t typecode, size_t offset, size_t len, void *items) { + self->base.type = &mp_type_memoryview; + self->typecode = typecode; + self->free = offset; + self->len = len; + self->items = items; +} +#endif + #endif // MICROPY_INCLUDED_PY_OBJARRAY_H diff --git a/py/objclosure.c b/py/objclosure.c index f5038ffc46513..9a8c7631c2bea 100644 --- a/py/objclosure.c +++ b/py/objclosure.c @@ -80,6 +80,7 @@ STATIC void closure_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_ const mp_obj_type_t closure_type = { { &mp_type_type }, + .flags = MP_TYPE_FLAG_BINDS_SELF, .name = MP_QSTR_closure, #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_DETAILED .print = closure_print, diff --git a/py/objcomplex.c b/py/objcomplex.c index 91e4402309d88..f4c4aeffcb91d 100644 --- a/py/objcomplex.c +++ b/py/objcomplex.c @@ -178,7 +178,10 @@ void mp_obj_complex_get(mp_obj_t self_in, mp_float_t *real, mp_float_t *imag) { mp_obj_t mp_obj_complex_binary_op(mp_binary_op_t op, mp_float_t lhs_real, mp_float_t lhs_imag, mp_obj_t rhs_in) { mp_float_t rhs_real, rhs_imag; - mp_obj_get_complex(rhs_in, &rhs_real, &rhs_imag); // can be any type, this function will convert to float (if possible) + if (!mp_obj_get_complex_maybe(rhs_in, &rhs_real, &rhs_imag)) { + return MP_OBJ_NULL; // op not supported + } + switch (op) { case MP_BINARY_OP_ADD: case MP_BINARY_OP_INPLACE_ADD: diff --git a/py/objdict.c b/py/objdict.c index 7690eeab29af1..4e51f259e7ff4 100644 --- a/py/objdict.c +++ b/py/objdict.c @@ -33,7 +33,17 @@ #include "py/objtype.h" #include "py/objstr.h" -#define mp_obj_is_dict_type(o) (mp_obj_is_obj(o) && ((mp_obj_base_t *)MP_OBJ_TO_PTR(o))->type->make_new == dict_make_new) +const mp_obj_dict_t mp_const_empty_dict_obj = { + .base = { .type = &mp_type_dict }, + .map = { + .all_keys_are_qstrs = 0, + .is_fixed = 1, + .is_ordered = 1, + .used = 0, + .alloc = 0, + .table = NULL, + } +}; STATIC mp_obj_t dict_update(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs); @@ -90,7 +100,7 @@ STATIC void dict_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_ } } -STATIC mp_obj_t dict_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { +mp_obj_t mp_obj_dict_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_obj_t dict_out = mp_obj_new_dict(0); mp_obj_dict_t *dict = MP_OBJ_TO_PTR(dict_out); dict->base.type = type; @@ -217,7 +227,7 @@ STATIC void mp_ensure_not_fixed(const mp_obj_dict_t *dict) { } STATIC mp_obj_t dict_clear(mp_obj_t self_in) { - mp_check_self(mp_obj_is_dict_type(self_in)); + mp_check_self(mp_obj_is_dict_or_ordereddict(self_in)); mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_in); mp_ensure_not_fixed(self); @@ -227,8 +237,8 @@ STATIC mp_obj_t dict_clear(mp_obj_t self_in) { } STATIC MP_DEFINE_CONST_FUN_OBJ_1(dict_clear_obj, dict_clear); -STATIC mp_obj_t dict_copy(mp_obj_t self_in) { - mp_check_self(mp_obj_is_dict_type(self_in)); +mp_obj_t mp_obj_dict_copy(mp_obj_t self_in) { + mp_check_self(mp_obj_is_dict_or_ordereddict(self_in)); mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_in); mp_obj_t other_out = mp_obj_new_dict(self->map.alloc); mp_obj_dict_t *other = MP_OBJ_TO_PTR(other_out); @@ -240,7 +250,7 @@ STATIC mp_obj_t dict_copy(mp_obj_t self_in) { memcpy(other->map.table, self->map.table, self->map.alloc * sizeof(mp_map_elem_t)); return other_out; } -STATIC MP_DEFINE_CONST_FUN_OBJ_1(dict_copy_obj, dict_copy); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(dict_copy_obj, mp_obj_dict_copy); #if MICROPY_PY_BUILTINS_DICT_FROMKEYS // this is a classmethod @@ -275,7 +285,7 @@ STATIC MP_DEFINE_CONST_CLASSMETHOD_OBJ(dict_fromkeys_obj, MP_ROM_PTR(&dict_fromk #endif STATIC mp_obj_t dict_get_helper(size_t n_args, const mp_obj_t *args, mp_map_lookup_kind_t lookup_kind) { - mp_check_self(mp_obj_is_dict_type(args[0])); + mp_check_self(mp_obj_is_dict_or_ordereddict(args[0])); mp_obj_dict_t *self = MP_OBJ_TO_PTR(args[0]); if (lookup_kind != MP_MAP_LOOKUP) { mp_ensure_not_fixed(self); @@ -320,7 +330,7 @@ STATIC mp_obj_t dict_setdefault(size_t n_args, const mp_obj_t *args) { STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(dict_setdefault_obj, 2, 3, dict_setdefault); STATIC mp_obj_t dict_popitem(mp_obj_t self_in) { - mp_check_self(mp_obj_is_dict_type(self_in)); + mp_check_self(mp_obj_is_dict_or_ordereddict(self_in)); mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_in); mp_ensure_not_fixed(self); if (self->map.used == 0) { @@ -345,7 +355,7 @@ STATIC mp_obj_t dict_popitem(mp_obj_t self_in) { STATIC MP_DEFINE_CONST_FUN_OBJ_1(dict_popitem_obj, dict_popitem); STATIC mp_obj_t dict_update(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { - mp_check_self(mp_obj_is_dict_type(args[0])); + mp_check_self(mp_obj_is_dict_or_ordereddict(args[0])); mp_obj_dict_t *self = MP_OBJ_TO_PTR(args[0]); mp_ensure_not_fixed(self); @@ -354,7 +364,7 @@ STATIC mp_obj_t dict_update(size_t n_args, const mp_obj_t *args, mp_map_t *kwarg if (n_args == 2) { // given a positional argument - if (mp_obj_is_dict_type(args[1])) { + if (mp_obj_is_dict_or_ordereddict(args[1])) { // update from other dictionary (make sure other is not self) if (args[1] != args[0]) { size_t cur = 0; @@ -512,7 +522,7 @@ STATIC mp_obj_t mp_obj_new_dict_view(mp_obj_t dict, mp_dict_view_kind_t kind) { } STATIC mp_obj_t dict_view(mp_obj_t self_in, mp_dict_view_kind_t kind) { - mp_check_self(mp_obj_is_dict_type(self_in)); + mp_check_self(mp_obj_is_dict_or_ordereddict(self_in)); return mp_obj_new_dict_view(self_in, kind); } @@ -536,7 +546,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(dict_values_obj, dict_values); STATIC mp_obj_t dict_getiter(mp_obj_t self_in, mp_obj_iter_buf_t *iter_buf) { assert(sizeof(mp_obj_dict_view_it_t) <= sizeof(mp_obj_iter_buf_t)); - mp_check_self(mp_obj_is_dict_type(self_in)); + mp_check_self(mp_obj_is_dict_or_ordereddict(self_in)); mp_obj_dict_view_it_t *o = (mp_obj_dict_view_it_t *)iter_buf; o->base.type = &dict_view_it_type; o->kind = MP_DICT_VIEW_KEYS; @@ -573,7 +583,7 @@ const mp_obj_type_t mp_type_dict = { { &mp_type_type }, .name = MP_QSTR_dict, .print = dict_print, - .make_new = dict_make_new, + .make_new = mp_obj_dict_make_new, .unary_op = dict_unary_op, .binary_op = dict_binary_op, .subscr = dict_subscr, @@ -586,7 +596,7 @@ const mp_obj_type_t mp_type_ordereddict = { { &mp_type_type }, .name = MP_QSTR_OrderedDict, .print = dict_print, - .make_new = dict_make_new, + .make_new = mp_obj_dict_make_new, .unary_op = dict_unary_op, .binary_op = dict_binary_op, .subscr = dict_subscr, @@ -613,7 +623,7 @@ size_t mp_obj_dict_len(mp_obj_t self_in) { } mp_obj_t mp_obj_dict_store(mp_obj_t self_in, mp_obj_t key, mp_obj_t value) { - mp_check_self(mp_obj_is_dict_type(self_in)); + mp_check_self(mp_obj_is_dict_or_ordereddict(self_in)); mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_in); mp_ensure_not_fixed(self); mp_map_lookup(&self->map, key, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = value; diff --git a/py/objfloat.c b/py/objfloat.c index 09b73c8cda525..451609492e5c1 100644 --- a/py/objfloat.c +++ b/py/objfloat.c @@ -293,13 +293,19 @@ mp_obj_t mp_obj_float_binary_op(mp_binary_op_t op, mp_float_t lhs_val, mp_obj_t if (lhs_val == 0 && rhs_val < 0 && !isinf(rhs_val)) { goto zero_division_error; } - if (lhs_val < 0 && rhs_val != MICROPY_FLOAT_C_FUN(floor)(rhs_val)) { + if (lhs_val < 0 && rhs_val != MICROPY_FLOAT_C_FUN(floor)(rhs_val) && !isnan(rhs_val)) { #if MICROPY_PY_BUILTINS_COMPLEX return mp_obj_complex_binary_op(MP_BINARY_OP_POWER, lhs_val, 0, rhs_in); #else mp_raise_ValueError(MP_ERROR_TEXT("complex values not supported")); #endif } + #if MICROPY_PY_MATH_POW_FIX_NAN // Also see modmath.c. + if (lhs_val == MICROPY_FLOAT_CONST(1.0) || rhs_val == MICROPY_FLOAT_CONST(0.0)) { + lhs_val = MICROPY_FLOAT_CONST(1.0); + break; + } + #endif lhs_val = MICROPY_FLOAT_C_FUN(pow)(lhs_val, rhs_val); break; case MP_BINARY_OP_DIVMOD: { diff --git a/py/objfun.c b/py/objfun.c index 3a63d8f439d2b..052f4b1cedfd2 100644 --- a/py/objfun.c +++ b/py/objfun.c @@ -58,6 +58,7 @@ STATIC mp_obj_t fun_builtin_0_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_type_t mp_type_fun_builtin_0 = { { &mp_type_type }, + .flags = MP_TYPE_FLAG_BINDS_SELF | MP_TYPE_FLAG_BUILTIN_FUN, .name = MP_QSTR_function, .call = fun_builtin_0_call, .unary_op = mp_generic_unary_op, @@ -72,6 +73,7 @@ STATIC mp_obj_t fun_builtin_1_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_type_t mp_type_fun_builtin_1 = { { &mp_type_type }, + .flags = MP_TYPE_FLAG_BINDS_SELF | MP_TYPE_FLAG_BUILTIN_FUN, .name = MP_QSTR_function, .call = fun_builtin_1_call, .unary_op = mp_generic_unary_op, @@ -86,6 +88,7 @@ STATIC mp_obj_t fun_builtin_2_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_type_t mp_type_fun_builtin_2 = { { &mp_type_type }, + .flags = MP_TYPE_FLAG_BINDS_SELF | MP_TYPE_FLAG_BUILTIN_FUN, .name = MP_QSTR_function, .call = fun_builtin_2_call, .unary_op = mp_generic_unary_op, @@ -100,6 +103,7 @@ STATIC mp_obj_t fun_builtin_3_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_type_t mp_type_fun_builtin_3 = { { &mp_type_type }, + .flags = MP_TYPE_FLAG_BINDS_SELF | MP_TYPE_FLAG_BUILTIN_FUN, .name = MP_QSTR_function, .call = fun_builtin_3_call, .unary_op = mp_generic_unary_op, @@ -130,6 +134,7 @@ STATIC mp_obj_t fun_builtin_var_call(mp_obj_t self_in, size_t n_args, size_t n_k const mp_obj_type_t mp_type_fun_builtin_var = { { &mp_type_type }, + .flags = MP_TYPE_FLAG_BINDS_SELF | MP_TYPE_FLAG_BUILTIN_FUN, .name = MP_QSTR_function, .call = fun_builtin_var_call, .unary_op = mp_generic_unary_op, @@ -197,8 +202,8 @@ STATIC void dump_args(const mp_obj_t *a, size_t sz) { MP_BC_PRELUDE_SIG_DECODE_INTO(ip, n_state_out_var, n_exc_stack, scope_flags, n_pos_args, n_kwonly_args, n_def_args); \ \ /* state size in bytes */ \ - state_size_out_var = n_state_out_var *sizeof(mp_obj_t) \ - + n_exc_stack *sizeof(mp_exc_stack_t); \ + state_size_out_var = n_state_out_var * sizeof(mp_obj_t) \ + + n_exc_stack * sizeof(mp_exc_stack_t); \ } #define INIT_CODESTATE(code_state, _fun_bc, _n_state, n_args, n_kw, args) \ @@ -355,6 +360,7 @@ void mp_obj_fun_bc_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { const mp_obj_type_t mp_type_fun_bc = { { &mp_type_type }, + .flags = MP_TYPE_FLAG_BINDS_SELF, .name = MP_QSTR_function, #if MICROPY_CPYTHON_COMPAT .print = fun_bc_print, @@ -406,6 +412,7 @@ STATIC mp_obj_t fun_native_call(mp_obj_t self_in, size_t n_args, size_t n_kw, co STATIC const mp_obj_type_t mp_type_fun_native = { { &mp_type_type }, + .flags = MP_TYPE_FLAG_BINDS_SELF, .name = MP_QSTR_function, .call = fun_native_call, .unary_op = mp_generic_unary_op, @@ -513,6 +520,7 @@ STATIC mp_obj_t fun_asm_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const STATIC const mp_obj_type_t mp_type_fun_asm = { { &mp_type_type }, + .flags = MP_TYPE_FLAG_BINDS_SELF, .name = MP_QSTR_function, .call = fun_asm_call, .unary_op = mp_generic_unary_op, diff --git a/py/objgenerator.c b/py/objgenerator.c index 5e140ba234474..543685fac7860 100644 --- a/py/objgenerator.c +++ b/py/objgenerator.c @@ -73,6 +73,7 @@ STATIC mp_obj_t gen_wrap_call(mp_obj_t self_in, size_t n_args, size_t n_kw, cons const mp_obj_type_t mp_type_gen_wrap = { { &mp_type_type }, + .flags = MP_TYPE_FLAG_BINDS_SELF, .name = MP_QSTR_generator, .call = gen_wrap_call, .unary_op = mp_generic_unary_op, @@ -126,6 +127,7 @@ STATIC mp_obj_t native_gen_wrap_call(mp_obj_t self_in, size_t n_args, size_t n_k const mp_obj_type_t mp_type_native_gen_wrap = { { &mp_type_type }, + .flags = MP_TYPE_FLAG_BINDS_SELF, .name = MP_QSTR_generator, .call = native_gen_wrap_call, .unary_op = mp_generic_unary_op, diff --git a/py/objint.h b/py/objint.h index c4752384e04db..5eed87705dedb 100644 --- a/py/objint.h +++ b/py/objint.h @@ -38,7 +38,7 @@ typedef struct _mp_obj_int_t { #endif } mp_obj_int_t; -extern const mp_obj_int_t mp_maxsize_obj; +extern const mp_obj_int_t mp_sys_maxsize_obj; #if MICROPY_PY_BUILTINS_FLOAT mp_float_t mp_obj_int_as_float_impl(mp_obj_t self_in); diff --git a/py/objint_longlong.c b/py/objint_longlong.c index 619b4d2b54475..f2e88c3ea5643 100644 --- a/py/objint_longlong.c +++ b/py/objint_longlong.c @@ -40,7 +40,7 @@ #if MICROPY_PY_SYS_MAXSIZE // Export value for sys.maxsize -const mp_obj_int_t mp_maxsize_obj = {{&mp_type_int}, MP_SSIZE_MAX}; +const mp_obj_int_t mp_sys_maxsize_obj = {{&mp_type_int}, MP_SSIZE_MAX}; #endif mp_obj_t mp_obj_int_from_bytes_impl(bool big_endian, size_t len, const byte *buf) { diff --git a/py/objint_mpz.c b/py/objint_mpz.c index f2f0dbc3a51cf..6e52073a6e85b 100644 --- a/py/objint_mpz.c +++ b/py/objint_mpz.c @@ -66,7 +66,7 @@ STATIC const mpz_dig_t maxsize_dig[] = { #endif }; // *FORMAT-ON* -const mp_obj_int_t mp_maxsize_obj = { +const mp_obj_int_t mp_sys_maxsize_obj = { {&mp_type_int}, {.fixed_dig = 1, .len = NUM_DIG, .alloc = NUM_DIG, .dig = (mpz_dig_t *)maxsize_dig} }; diff --git a/py/objmodule.c b/py/objmodule.c index 060e1bc1e6c2d..a1f9d9d7f146a 100644 --- a/py/objmodule.c +++ b/py/objmodule.c @@ -159,7 +159,7 @@ STATIC const mp_rom_map_elem_t mp_builtin_module_table[] = { #endif #endif #if MICROPY_PY_SYS - { MP_ROM_QSTR(MP_QSTR_sys), MP_ROM_PTR(&mp_module_sys) }, + { MP_ROM_QSTR(MP_QSTR_usys), MP_ROM_PTR(&mp_module_sys) }, #endif #if MICROPY_PY_GC && MICROPY_ENABLE_GC { MP_ROM_QSTR(MP_QSTR_gc), MP_ROM_PTR(&mp_module_gc) }, diff --git a/py/objstr.c b/py/objstr.c index a276a255e516f..84728e6f2d919 100644 --- a/py/objstr.c +++ b/py/objstr.c @@ -205,6 +205,10 @@ STATIC mp_obj_t bytes_make_new(const mp_obj_type_t *type_in, size_t n_args, size return mp_const_empty_bytes; } + if (mp_obj_is_type(args[0], &mp_type_bytes)) { + return args[0]; + } + if (mp_obj_is_str(args[0])) { if (n_args < 2 || n_args > 3) { goto wrong_args; diff --git a/py/objtype.c b/py/objtype.c index d08c69e284b3e..7f75232941046 100644 --- a/py/objtype.c +++ b/py/objtype.c @@ -153,7 +153,7 @@ STATIC void mp_obj_class_lookup(struct class_lookup_data *lookup, const mp_obj_t if (type->locals_dict != NULL) { // search locals_dict (the set of methods/attributes) - assert(type->locals_dict->base.type == &mp_type_dict); // MicroPython restriction, for now + assert(mp_obj_is_dict_or_ordereddict(MP_OBJ_FROM_PTR(type->locals_dict))); // MicroPython restriction, for now mp_map_t *locals_map = &type->locals_dict->map; mp_map_elem_t *elem = mp_map_lookup(locals_map, MP_OBJ_NEW_QSTR(lookup->attr), MP_MAP_LOOKUP); if (elem != NULL) { @@ -588,16 +588,13 @@ STATIC void mp_obj_instance_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *des #if MICROPY_CPYTHON_COMPAT if (attr == MP_QSTR___dict__) { // Create a new dict with a copy of the instance's map items. - // This creates, unlike CPython, a 'read-only' __dict__: modifying - // it will not result in modifications to the actual instance members. - mp_map_t *map = &self->members; - mp_obj_t attr_dict = mp_obj_new_dict(map->used); - for (size_t i = 0; i < map->alloc; ++i) { - if (mp_map_slot_is_filled(map, i)) { - mp_obj_dict_store(attr_dict, map->table[i].key, map->table[i].value); - } - } - dest[0] = attr_dict; + // This creates, unlike CPython, a read-only __dict__ that can't be modified. + mp_obj_dict_t dict; + dict.base.type = &mp_type_dict; + dict.map = self->members; + dest[0] = mp_obj_dict_copy(MP_OBJ_FROM_PTR(&dict)); + mp_obj_dict_t *dest_dict = MP_OBJ_TO_PTR(dest[0]); + dest_dict->map.is_fixed = 1; return; } #endif @@ -1013,6 +1010,24 @@ STATIC void type_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { dest[0] = MP_OBJ_NEW_QSTR(self->name); return; } + #if MICROPY_CPYTHON_COMPAT + if (attr == MP_QSTR___dict__) { + // Returns a read-only dict of the class attributes. + // If the internal locals is not fixed, a copy will be created. + const mp_obj_dict_t *dict = self->locals_dict; + if (!dict) { + dict = &mp_const_empty_dict_obj; + } + if (dict->map.is_fixed) { + dest[0] = MP_OBJ_FROM_PTR(dict); + } else { + dest[0] = mp_obj_dict_copy(MP_OBJ_FROM_PTR(dict)); + mp_obj_dict_t *dict_copy = MP_OBJ_TO_PTR(dest[0]); + dict_copy->map.is_fixed = 1; + } + return; + } + #endif if (attr == MP_QSTR___bases__) { if (self == &mp_type_object) { dest[0] = mp_const_empty_tuple; @@ -1041,7 +1056,7 @@ STATIC void type_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { // delete/store attribute if (self->locals_dict != NULL) { - assert(self->locals_dict->base.type == &mp_type_dict); // MicroPython restriction, for now + assert(mp_obj_is_dict_or_ordereddict(MP_OBJ_FROM_PTR(self->locals_dict))); // MicroPython restriction, for now mp_map_t *locals_map = &self->locals_dict->map; if (locals_map->is_fixed) { // can't apply delete/store to a fixed map @@ -1091,7 +1106,7 @@ mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict) if (!mp_obj_is_type(bases_tuple, &mp_type_tuple)) { mp_raise_TypeError(NULL); } - if (!mp_obj_is_type(locals_dict, &mp_type_dict)) { + if (!mp_obj_is_dict_or_ordereddict(locals_dict)) { mp_raise_TypeError(NULL); } diff --git a/py/parse.c b/py/parse.c index b93282165f88a..da2f5e796d9be 100644 --- a/py/parse.c +++ b/py/parse.c @@ -55,9 +55,6 @@ #define RULE_ARG_RULE (0x2000) #define RULE_ARG_OPT_RULE (0x3000) -// (un)comment to use rule names; for debugging -// #define USE_RULE_NAME (1) - // *FORMAT-OFF* enum { @@ -192,7 +189,7 @@ static const size_t FIRST_RULE_WITH_OFFSET_ABOVE_255 = #undef DEF_RULE_NC 0; -#if USE_RULE_NAME +#if MICROPY_DEBUG_PARSE_RULE_NAME // Define an array of rule names corresponding to each rule STATIC const char *const rule_name_table[] = { #define DEF_RULE(rule, comp, kind, ...) #rule, @@ -368,35 +365,35 @@ size_t mp_parse_node_extract_list(mp_parse_node_t *pn, size_t pn_kind, mp_parse_ } #if MICROPY_DEBUG_PRINTERS -void mp_parse_node_print(mp_parse_node_t pn, size_t indent) { +void mp_parse_node_print(const mp_print_t *print, mp_parse_node_t pn, size_t indent) { if (MP_PARSE_NODE_IS_STRUCT(pn)) { - printf("[% 4d] ", (int)((mp_parse_node_struct_t *)pn)->source_line); + mp_printf(print, "[% 4d] ", (int)((mp_parse_node_struct_t *)pn)->source_line); } else { - printf(" "); + mp_printf(print, " "); } for (size_t i = 0; i < indent; i++) { - printf(" "); + mp_printf(print, " "); } if (MP_PARSE_NODE_IS_NULL(pn)) { - printf("NULL\n"); + mp_printf(print, "NULL\n"); } else if (MP_PARSE_NODE_IS_SMALL_INT(pn)) { mp_int_t arg = MP_PARSE_NODE_LEAF_SMALL_INT(pn); - printf("int(" INT_FMT ")\n", arg); + mp_printf(print, "int(" INT_FMT ")\n", arg); } else if (MP_PARSE_NODE_IS_LEAF(pn)) { uintptr_t arg = MP_PARSE_NODE_LEAF_ARG(pn); switch (MP_PARSE_NODE_LEAF_KIND(pn)) { case MP_PARSE_NODE_ID: - printf("id(%s)\n", qstr_str(arg)); + mp_printf(print, "id(%s)\n", qstr_str(arg)); break; case MP_PARSE_NODE_STRING: - printf("str(%s)\n", qstr_str(arg)); + mp_printf(print, "str(%s)\n", qstr_str(arg)); break; case MP_PARSE_NODE_BYTES: - printf("bytes(%s)\n", qstr_str(arg)); + mp_printf(print, "bytes(%s)\n", qstr_str(arg)); break; default: assert(MP_PARSE_NODE_LEAF_KIND(pn) == MP_PARSE_NODE_TOKEN); - printf("tok(%u)\n", (uint)arg); + mp_printf(print, "tok(%u)\n", (uint)arg); break; } } else { @@ -404,19 +401,19 @@ void mp_parse_node_print(mp_parse_node_t pn, size_t indent) { mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)pn; if (MP_PARSE_NODE_STRUCT_KIND(pns) == RULE_const_object) { #if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_D - printf("literal const(%016llx)\n", (uint64_t)pns->nodes[0] | ((uint64_t)pns->nodes[1] << 32)); + mp_printf(print, "literal const(%016llx)\n", (uint64_t)pns->nodes[0] | ((uint64_t)pns->nodes[1] << 32)); #else - printf("literal const(%p)\n", (mp_obj_t)pns->nodes[0]); + mp_printf(print, "literal const(%p)\n", (mp_obj_t)pns->nodes[0]); #endif } else { size_t n = MP_PARSE_NODE_STRUCT_NUM_NODES(pns); - #if USE_RULE_NAME - printf("%s(%u) (n=%u)\n", rule_name_table[MP_PARSE_NODE_STRUCT_KIND(pns)], (uint)MP_PARSE_NODE_STRUCT_KIND(pns), (uint)n); + #if MICROPY_DEBUG_PARSE_RULE_NAME + mp_printf(print, "%s(%u) (n=%u)\n", rule_name_table[MP_PARSE_NODE_STRUCT_KIND(pns)], (uint)MP_PARSE_NODE_STRUCT_KIND(pns), (uint)n); #else - printf("rule(%u) (n=%u)\n", (uint)MP_PARSE_NODE_STRUCT_KIND(pns), (uint)n); + mp_printf(print, "rule(%u) (n=%u)\n", (uint)MP_PARSE_NODE_STRUCT_KIND(pns), (uint)n); #endif for (size_t i = 0; i < n; i++) { - mp_parse_node_print(pns->nodes[i], indent + 2); + mp_parse_node_print(print, pns->nodes[i], indent + 2); } } } @@ -424,10 +421,10 @@ void mp_parse_node_print(mp_parse_node_t pn, size_t indent) { #endif // MICROPY_DEBUG_PRINTERS /* -STATIC void result_stack_show(parser_t *parser) { - printf("result stack, most recent first\n"); +STATIC void result_stack_show(const mp_print_t *print, parser_t *parser) { + mp_printf(print, "result stack, most recent first\n"); for (ssize_t i = parser->result_stack_top - 1; i >= 0; i--) { - mp_parse_node_print(parser->result_stack[i], 0); + mp_parse_node_print(print, parser->result_stack[i], 0); } } */ diff --git a/py/parse.h b/py/parse.h index 4ed8a7ad12324..a6eb380047dc3 100644 --- a/py/parse.h +++ b/py/parse.h @@ -86,7 +86,7 @@ bool mp_parse_node_is_const_false(mp_parse_node_t pn); bool mp_parse_node_is_const_true(mp_parse_node_t pn); bool mp_parse_node_get_int_maybe(mp_parse_node_t pn, mp_obj_t *o); size_t mp_parse_node_extract_list(mp_parse_node_t *pn, size_t pn_kind, mp_parse_node_t **nodes); -void mp_parse_node_print(mp_parse_node_t pn, size_t indent); +void mp_parse_node_print(const mp_print_t *print, mp_parse_node_t pn, size_t indent); typedef enum { MP_PARSE_SINGLE_INPUT, diff --git a/py/persistentcode.c b/py/persistentcode.c index 386ea49477f6b..da3234a5fe9cd 100644 --- a/py/persistentcode.c +++ b/py/persistentcode.c @@ -3,7 +3,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2013-2016 Damien P. George + * Copyright (c) 2013-2020 Damien P. George * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -516,6 +516,18 @@ STATIC mp_raw_code_t *load_raw_code(mp_reader_t *reader, qstr_window_t *qw) { fun_data = MP_PLAT_COMMIT_EXEC(fun_data, fun_data_len, opt_ri); #else if (prelude.scope_flags & MP_SCOPE_FLAG_VIPERRELOC) { + #if MICROPY_PERSISTENT_CODE_TRACK_RELOC_CODE + // If native code needs relocations then it's not guaranteed that a pointer to + // the head of `buf` (containing the machine code) will be retained for the GC + // to trace. This is because native functions can start inside `buf` and so + // it's possible that the only GC-reachable pointers are pointers inside `buf`. + // So put this `buf` on a list of reachable root pointers. + if (MP_STATE_PORT(track_reloc_code_list) == MP_OBJ_NULL) { + MP_STATE_PORT(track_reloc_code_list) = mp_obj_new_list(0, NULL); + } + mp_obj_list_append(MP_STATE_PORT(track_reloc_code_list), MP_OBJ_FROM_PTR(fun_data)); + #endif + // Do the relocations. mp_native_relocate(&ri, fun_data, (uintptr_t)fun_data); } #endif diff --git a/py/py.mk b/py/py.mk index 0a98ccc00ca0f..d864a7ed3d843 100644 --- a/py/py.mk +++ b/py/py.mk @@ -266,6 +266,11 @@ $(HEADER_BUILD)/moduledefs.h: $(SRC_QSTR) $(QSTR_GLOBAL_DEPENDENCIES) | $(HEADER SRC_QSTR += $(HEADER_BUILD)/moduledefs.h +# Standard C functions like memset need to be compiled with special flags so +# the compiler does not optimise these functions in terms of themselves. +CFLAGS_BUILTIN ?= -ffreestanding -fno-builtin -fno-lto +$(BUILD)/lib/libc/string0.o: CFLAGS += $(CFLAGS_BUILTIN) + # Force nlr code to always be compiled with space-saving optimisation so # that the function preludes are of a minimal and predictable form. $(PY_BUILD)/nlr%.o: CFLAGS += -Os diff --git a/py/ringbuf.h b/py/ringbuf.h index 4d316d961b2b5..4685848961c52 100644 --- a/py/ringbuf.h +++ b/py/ringbuf.h @@ -29,6 +29,10 @@ #include #include +#ifdef _MSC_VER +#include "py/mpconfig.h" // For inline. +#endif + typedef struct _ringbuf_t { uint8_t *buf; uint16_t size; @@ -59,6 +63,13 @@ static inline int ringbuf_get(ringbuf_t *r) { return v; } +static inline int ringbuf_peek(ringbuf_t *r) { + if (r->iget == r->iput) { + return -1; + } + return r->buf[r->iget]; +} + static inline int ringbuf_put(ringbuf_t *r, uint8_t v) { uint32_t iput_new = r->iput + 1; if (iput_new >= r->size) { diff --git a/py/runtime.c b/py/runtime.c index 157bc74a8dfb4..c12271f4e2cf5 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -106,6 +106,10 @@ void mp_init(void) { MP_STATE_VM(mp_module_builtins_override_dict) = NULL; #endif + #if MICROPY_PERSISTENT_CODE_TRACK_RELOC_CODE + MP_STATE_VM(track_reloc_code_list) = MP_OBJ_NULL; + #endif + #if MICROPY_PY_OS_DUPTERM for (size_t i = 0; i < MICROPY_PY_OS_DUPTERM; ++i) { MP_STATE_VM(dupterm_objs[i]) = MP_OBJ_NULL; @@ -993,6 +997,7 @@ STATIC mp_obj_t checked_fun_call(mp_obj_t self_in, size_t n_args, size_t n_kw, c STATIC const mp_obj_type_t mp_type_checked_fun = { { &mp_type_type }, + .flags = MP_TYPE_FLAG_BINDS_SELF, .name = MP_QSTR_function, .call = checked_fun_call, }; @@ -1011,49 +1016,54 @@ STATIC mp_obj_t mp_obj_new_checked_fun(const mp_obj_type_t *type, mp_obj_t fun) // and put the result in the dest[] array for a possible method call. // Conversion means dealing with static/class methods, callables, and values. // see http://docs.python.org/3/howto/descriptor.html +// and also https://mail.python.org/pipermail/python-dev/2015-March/138950.html void mp_convert_member_lookup(mp_obj_t self, const mp_obj_type_t *type, mp_obj_t member, mp_obj_t *dest) { - if (mp_obj_is_type(member, &mp_type_staticmethod)) { - // return just the function - dest[0] = ((mp_obj_static_class_method_t *)MP_OBJ_TO_PTR(member))->fun; - } else if (mp_obj_is_type(member, &mp_type_classmethod)) { - // return a bound method, with self being the type of this object - // this type should be the type of the original instance, not the base - // type (which is what is passed in the 'type' argument to this function) - if (self != MP_OBJ_NULL) { - type = mp_obj_get_type(self); - } - dest[0] = ((mp_obj_static_class_method_t *)MP_OBJ_TO_PTR(member))->fun; - dest[1] = MP_OBJ_FROM_PTR(type); - } else if (mp_obj_is_type(member, &mp_type_type)) { - // Don't try to bind types (even though they're callable) - dest[0] = member; - } else if (mp_obj_is_fun(member) - || (mp_obj_is_obj(member) - && (((mp_obj_base_t *)MP_OBJ_TO_PTR(member))->type->name == MP_QSTR_closure - || ((mp_obj_base_t *)MP_OBJ_TO_PTR(member))->type->name == MP_QSTR_generator))) { - // only functions, closures and generators objects can be bound to self - #if MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG + if (mp_obj_is_obj(member)) { const mp_obj_type_t *m_type = ((mp_obj_base_t *)MP_OBJ_TO_PTR(member))->type; - if (self == MP_OBJ_NULL - && (m_type == &mp_type_fun_builtin_0 - || m_type == &mp_type_fun_builtin_1 - || m_type == &mp_type_fun_builtin_2 - || m_type == &mp_type_fun_builtin_3 - || m_type == &mp_type_fun_builtin_var) - && type != &mp_type_object) { - // we extracted a builtin method without a first argument, so we must - // wrap this function in a type checker - // Note that object will do its own checking so shouldn't be wrapped. - dest[0] = mp_obj_new_checked_fun(type, member); - } else - #endif - { - // return a bound method, with self being this object + if (m_type->flags & MP_TYPE_FLAG_BINDS_SELF) { + // `member` is a function that binds self as its first argument. + if (m_type->flags & MP_TYPE_FLAG_BUILTIN_FUN) { + // `member` is a built-in function, which has special behaviour. + if (mp_obj_is_instance_type(type)) { + // Built-in functions on user types always behave like a staticmethod. + dest[0] = member; + } + #if MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG + else if (self == MP_OBJ_NULL && type != &mp_type_object) { + // `member` is a built-in method without a first argument, so wrap + // it in a type checker that will check self when it's supplied. + // Note that object will do its own checking so shouldn't be wrapped. + dest[0] = mp_obj_new_checked_fun(type, member); + } + #endif + else { + // Return a (built-in) bound method, with self being this object. + dest[0] = member; + dest[1] = self; + } + } else { + // Return a bound method, with self being this object. + dest[0] = member; + dest[1] = self; + } + } else if (m_type == &mp_type_staticmethod) { + // `member` is a staticmethod, return the function that it wraps. + dest[0] = ((mp_obj_static_class_method_t *)MP_OBJ_TO_PTR(member))->fun; + } else if (m_type == &mp_type_classmethod) { + // `member` is a classmethod, return a bound method with self being the type of + // this object. This type should be the type of the original instance, not the + // base type (which is what is passed in the `type` argument to this function). + if (self != MP_OBJ_NULL) { + type = mp_obj_get_type(self); + } + dest[0] = ((mp_obj_static_class_method_t *)MP_OBJ_TO_PTR(member))->fun; + dest[1] = MP_OBJ_FROM_PTR(type); + } else { + // `member` is a value, so just return that value. dest[0] = member; - dest[1] = self; } } else { - // class member is a value, so just return that value + // `member` is a value, so just return that value. dest[0] = member; } } @@ -1463,7 +1473,7 @@ mp_obj_t mp_parse_compile_execute(mp_lexer_t *lex, mp_parse_input_kind_t parse_i if (nlr_push(&nlr) == 0) { qstr source_name = lex->source_name; mp_parse_tree_t parse_tree = mp_parse(lex, parse_input_kind); - mp_obj_t module_fun = mp_compile(&parse_tree, source_name, false); + mp_obj_t module_fun = mp_compile(&parse_tree, source_name, parse_input_kind == MP_PARSE_SINGLE_INPUT); mp_obj_t ret; if (MICROPY_PY_BUILTINS_COMPILE && globals == NULL) { diff --git a/py/scope.h b/py/scope.h index ba07c39498b5a..b52d98ea1c72d 100644 --- a/py/scope.h +++ b/py/scope.h @@ -55,6 +55,7 @@ typedef struct _id_info_t { } id_info_t; #define SCOPE_IS_FUNC_LIKE(s) ((s) >= SCOPE_LAMBDA) +#define SCOPE_IS_COMP_LIKE(s) (SCOPE_LIST_COMP <= (s) && (s) <= SCOPE_GEN_EXPR) // scope is a "block" in Python parlance typedef enum { diff --git a/py/showbc.c b/py/showbc.c index 4d6527fae2e45..8941dd7544487 100644 --- a/py/showbc.c +++ b/py/showbc.c @@ -32,9 +32,6 @@ #if MICROPY_DEBUG_PRINTERS -// redirect all printfs in this file to the platform print stream -#define printf(...) mp_printf(&mp_plat_print, __VA_ARGS__) - #define DECODE_UINT { \ unum = 0; \ do { \ @@ -80,7 +77,7 @@ const byte *mp_showbc_code_start; const mp_uint_t *mp_showbc_const_table; -void mp_bytecode_print(const void *descr, const byte *ip, mp_uint_t len, const mp_uint_t *const_table) { +void mp_bytecode_print(const mp_print_t *print, const void *descr, const byte *ip, mp_uint_t len, const mp_uint_t *const_table) { mp_showbc_code_start = ip; // Decode prelude @@ -96,30 +93,30 @@ void mp_bytecode_print(const void *descr, const byte *ip, mp_uint_t len, const m qstr block_name = mp_decode_uint(&code_info); qstr source_file = mp_decode_uint(&code_info); #endif - printf("File %s, code block '%s' (descriptor: %p, bytecode @%p " UINT_FMT " bytes)\n", + mp_printf(print, "File %s, code block '%s' (descriptor: %p, bytecode @%p " UINT_FMT " bytes)\n", qstr_str(source_file), qstr_str(block_name), descr, mp_showbc_code_start, len); // raw bytecode dump size_t prelude_size = ip - mp_showbc_code_start + n_info + n_cell; - printf("Raw bytecode (code_info_size=" UINT_FMT ", bytecode_size=" UINT_FMT "):\n", + mp_printf(print, "Raw bytecode (code_info_size=" UINT_FMT ", bytecode_size=" UINT_FMT "):\n", prelude_size, len - prelude_size); for (mp_uint_t i = 0; i < len; i++) { if (i > 0 && i % 16 == 0) { - printf("\n"); + mp_printf(print, "\n"); } - printf(" %02x", mp_showbc_code_start[i]); + mp_printf(print, " %02x", mp_showbc_code_start[i]); } - printf("\n"); + mp_printf(print, "\n"); // bytecode prelude: arg names (as qstr objects) - printf("arg names:"); + mp_printf(print, "arg names:"); for (mp_uint_t i = 0; i < n_pos_args + n_kwonly_args; i++) { - printf(" %s", qstr_str(MP_OBJ_QSTR_VALUE(const_table[i]))); + mp_printf(print, " %s", qstr_str(MP_OBJ_QSTR_VALUE(const_table[i]))); } - printf("\n"); + mp_printf(print, "\n"); - printf("(N_STATE %u)\n", (unsigned)n_state); - printf("(N_EXC_STACK %u)\n", (unsigned)n_exc_stack); + mp_printf(print, "(N_STATE %u)\n", (unsigned)n_state); + mp_printf(print, "(N_EXC_STACK %u)\n", (unsigned)n_exc_stack); // skip over code_info ip += n_info; @@ -127,14 +124,14 @@ void mp_bytecode_print(const void *descr, const byte *ip, mp_uint_t len, const m // bytecode prelude: initialise closed over variables for (size_t i = 0; i < n_cell; ++i) { uint local_num = *ip++; - printf("(INIT_CELL %u)\n", local_num); + mp_printf(print, "(INIT_CELL %u)\n", local_num); } // print out line number info { mp_int_t bc = 0; mp_uint_t source_line = 1; - printf(" bc=" INT_FMT " line=" UINT_FMT "\n", bc, source_line); + mp_printf(print, " bc=" INT_FMT " line=" UINT_FMT "\n", bc, source_line); for (const byte *ci = code_info; *ci;) { if ((ci[0] & 0x80) == 0) { // 0b0LLBBBBB encoding @@ -147,27 +144,27 @@ void mp_bytecode_print(const void *descr, const byte *ip, mp_uint_t len, const m source_line += ((ci[0] << 4) & 0x700) | ci[1]; ci += 2; } - printf(" bc=" INT_FMT " line=" UINT_FMT "\n", bc, source_line); + mp_printf(print, " bc=" INT_FMT " line=" UINT_FMT "\n", bc, source_line); } } - mp_bytecode_print2(ip, len - prelude_size, const_table); + mp_bytecode_print2(print, ip, len - prelude_size, const_table); } -const byte *mp_bytecode_print_str(const byte *ip) { +const byte *mp_bytecode_print_str(const mp_print_t *print, const byte *ip) { mp_uint_t unum; qstr qst; switch (*ip++) { case MP_BC_LOAD_CONST_FALSE: - printf("LOAD_CONST_FALSE"); + mp_printf(print, "LOAD_CONST_FALSE"); break; case MP_BC_LOAD_CONST_NONE: - printf("LOAD_CONST_NONE"); + mp_printf(print, "LOAD_CONST_NONE"); break; case MP_BC_LOAD_CONST_TRUE: - printf("LOAD_CONST_TRUE"); + mp_printf(print, "LOAD_CONST_TRUE"); break; case MP_BC_LOAD_CONST_SMALL_INT: { @@ -179,197 +176,197 @@ const byte *mp_bytecode_print_str(const byte *ip) { do { num = (num << 7) | (*ip & 0x7f); } while ((*ip++ & 0x80) != 0); - printf("LOAD_CONST_SMALL_INT " INT_FMT, num); + mp_printf(print, "LOAD_CONST_SMALL_INT " INT_FMT, num); break; } case MP_BC_LOAD_CONST_STRING: DECODE_QSTR; - printf("LOAD_CONST_STRING '%s'", qstr_str(qst)); + mp_printf(print, "LOAD_CONST_STRING '%s'", qstr_str(qst)); break; case MP_BC_LOAD_CONST_OBJ: DECODE_OBJ; - printf("LOAD_CONST_OBJ %p=", MP_OBJ_TO_PTR(unum)); - mp_obj_print_helper(&mp_plat_print, (mp_obj_t)unum, PRINT_REPR); + mp_printf(print, "LOAD_CONST_OBJ %p=", MP_OBJ_TO_PTR(unum)); + mp_obj_print_helper(print, (mp_obj_t)unum, PRINT_REPR); break; case MP_BC_LOAD_NULL: - printf("LOAD_NULL"); + mp_printf(print, "LOAD_NULL"); break; case MP_BC_LOAD_FAST_N: DECODE_UINT; - printf("LOAD_FAST_N " UINT_FMT, unum); + mp_printf(print, "LOAD_FAST_N " UINT_FMT, unum); break; case MP_BC_LOAD_DEREF: DECODE_UINT; - printf("LOAD_DEREF " UINT_FMT, unum); + mp_printf(print, "LOAD_DEREF " UINT_FMT, unum); break; case MP_BC_LOAD_NAME: DECODE_QSTR; - printf("LOAD_NAME %s", qstr_str(qst)); + mp_printf(print, "LOAD_NAME %s", qstr_str(qst)); if (MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE) { - printf(" (cache=%u)", *ip++); + mp_printf(print, " (cache=%u)", *ip++); } break; case MP_BC_LOAD_GLOBAL: DECODE_QSTR; - printf("LOAD_GLOBAL %s", qstr_str(qst)); + mp_printf(print, "LOAD_GLOBAL %s", qstr_str(qst)); if (MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE) { - printf(" (cache=%u)", *ip++); + mp_printf(print, " (cache=%u)", *ip++); } break; case MP_BC_LOAD_ATTR: DECODE_QSTR; - printf("LOAD_ATTR %s", qstr_str(qst)); + mp_printf(print, "LOAD_ATTR %s", qstr_str(qst)); if (MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE) { - printf(" (cache=%u)", *ip++); + mp_printf(print, " (cache=%u)", *ip++); } break; case MP_BC_LOAD_METHOD: DECODE_QSTR; - printf("LOAD_METHOD %s", qstr_str(qst)); + mp_printf(print, "LOAD_METHOD %s", qstr_str(qst)); break; case MP_BC_LOAD_SUPER_METHOD: DECODE_QSTR; - printf("LOAD_SUPER_METHOD %s", qstr_str(qst)); + mp_printf(print, "LOAD_SUPER_METHOD %s", qstr_str(qst)); break; case MP_BC_LOAD_BUILD_CLASS: - printf("LOAD_BUILD_CLASS"); + mp_printf(print, "LOAD_BUILD_CLASS"); break; case MP_BC_LOAD_SUBSCR: - printf("LOAD_SUBSCR"); + mp_printf(print, "LOAD_SUBSCR"); break; case MP_BC_STORE_FAST_N: DECODE_UINT; - printf("STORE_FAST_N " UINT_FMT, unum); + mp_printf(print, "STORE_FAST_N " UINT_FMT, unum); break; case MP_BC_STORE_DEREF: DECODE_UINT; - printf("STORE_DEREF " UINT_FMT, unum); + mp_printf(print, "STORE_DEREF " UINT_FMT, unum); break; case MP_BC_STORE_NAME: DECODE_QSTR; - printf("STORE_NAME %s", qstr_str(qst)); + mp_printf(print, "STORE_NAME %s", qstr_str(qst)); break; case MP_BC_STORE_GLOBAL: DECODE_QSTR; - printf("STORE_GLOBAL %s", qstr_str(qst)); + mp_printf(print, "STORE_GLOBAL %s", qstr_str(qst)); break; case MP_BC_STORE_ATTR: DECODE_QSTR; - printf("STORE_ATTR %s", qstr_str(qst)); + mp_printf(print, "STORE_ATTR %s", qstr_str(qst)); if (MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE) { - printf(" (cache=%u)", *ip++); + mp_printf(print, " (cache=%u)", *ip++); } break; case MP_BC_STORE_SUBSCR: - printf("STORE_SUBSCR"); + mp_printf(print, "STORE_SUBSCR"); break; case MP_BC_DELETE_FAST: DECODE_UINT; - printf("DELETE_FAST " UINT_FMT, unum); + mp_printf(print, "DELETE_FAST " UINT_FMT, unum); break; case MP_BC_DELETE_DEREF: DECODE_UINT; - printf("DELETE_DEREF " UINT_FMT, unum); + mp_printf(print, "DELETE_DEREF " UINT_FMT, unum); break; case MP_BC_DELETE_NAME: DECODE_QSTR; - printf("DELETE_NAME %s", qstr_str(qst)); + mp_printf(print, "DELETE_NAME %s", qstr_str(qst)); break; case MP_BC_DELETE_GLOBAL: DECODE_QSTR; - printf("DELETE_GLOBAL %s", qstr_str(qst)); + mp_printf(print, "DELETE_GLOBAL %s", qstr_str(qst)); break; case MP_BC_DUP_TOP: - printf("DUP_TOP"); + mp_printf(print, "DUP_TOP"); break; case MP_BC_DUP_TOP_TWO: - printf("DUP_TOP_TWO"); + mp_printf(print, "DUP_TOP_TWO"); break; case MP_BC_POP_TOP: - printf("POP_TOP"); + mp_printf(print, "POP_TOP"); break; case MP_BC_ROT_TWO: - printf("ROT_TWO"); + mp_printf(print, "ROT_TWO"); break; case MP_BC_ROT_THREE: - printf("ROT_THREE"); + mp_printf(print, "ROT_THREE"); break; case MP_BC_JUMP: DECODE_SLABEL; - printf("JUMP " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); + mp_printf(print, "JUMP " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); break; case MP_BC_POP_JUMP_IF_TRUE: DECODE_SLABEL; - printf("POP_JUMP_IF_TRUE " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); + mp_printf(print, "POP_JUMP_IF_TRUE " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); break; case MP_BC_POP_JUMP_IF_FALSE: DECODE_SLABEL; - printf("POP_JUMP_IF_FALSE " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); + mp_printf(print, "POP_JUMP_IF_FALSE " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); break; case MP_BC_JUMP_IF_TRUE_OR_POP: DECODE_SLABEL; - printf("JUMP_IF_TRUE_OR_POP " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); + mp_printf(print, "JUMP_IF_TRUE_OR_POP " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); break; case MP_BC_JUMP_IF_FALSE_OR_POP: DECODE_SLABEL; - printf("JUMP_IF_FALSE_OR_POP " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); + mp_printf(print, "JUMP_IF_FALSE_OR_POP " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); break; case MP_BC_SETUP_WITH: DECODE_ULABEL; // loop-like labels are always forward - printf("SETUP_WITH " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); + mp_printf(print, "SETUP_WITH " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); break; case MP_BC_WITH_CLEANUP: - printf("WITH_CLEANUP"); + mp_printf(print, "WITH_CLEANUP"); break; case MP_BC_UNWIND_JUMP: DECODE_SLABEL; - printf("UNWIND_JUMP " UINT_FMT " %d", (mp_uint_t)(ip + unum - mp_showbc_code_start), *ip); + mp_printf(print, "UNWIND_JUMP " UINT_FMT " %d", (mp_uint_t)(ip + unum - mp_showbc_code_start), *ip); ip += 1; break; case MP_BC_SETUP_EXCEPT: DECODE_ULABEL; // except labels are always forward - printf("SETUP_EXCEPT " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); + mp_printf(print, "SETUP_EXCEPT " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); break; case MP_BC_SETUP_FINALLY: DECODE_ULABEL; // except labels are always forward - printf("SETUP_FINALLY " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); + mp_printf(print, "SETUP_FINALLY " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); break; case MP_BC_END_FINALLY: @@ -377,169 +374,169 @@ const byte *mp_bytecode_print_str(const byte *ip) { // if TOS is an integer, does something else // if TOS is None, just pops it and continues // else error - printf("END_FINALLY"); + mp_printf(print, "END_FINALLY"); break; case MP_BC_GET_ITER: - printf("GET_ITER"); + mp_printf(print, "GET_ITER"); break; case MP_BC_GET_ITER_STACK: - printf("GET_ITER_STACK"); + mp_printf(print, "GET_ITER_STACK"); break; case MP_BC_FOR_ITER: DECODE_ULABEL; // the jump offset if iteration finishes; for labels are always forward - printf("FOR_ITER " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); + mp_printf(print, "FOR_ITER " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); break; case MP_BC_POP_EXCEPT_JUMP: DECODE_ULABEL; // these labels are always forward - printf("POP_EXCEPT_JUMP " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); + mp_printf(print, "POP_EXCEPT_JUMP " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); break; case MP_BC_BUILD_TUPLE: DECODE_UINT; - printf("BUILD_TUPLE " UINT_FMT, unum); + mp_printf(print, "BUILD_TUPLE " UINT_FMT, unum); break; case MP_BC_BUILD_LIST: DECODE_UINT; - printf("BUILD_LIST " UINT_FMT, unum); + mp_printf(print, "BUILD_LIST " UINT_FMT, unum); break; case MP_BC_BUILD_MAP: DECODE_UINT; - printf("BUILD_MAP " UINT_FMT, unum); + mp_printf(print, "BUILD_MAP " UINT_FMT, unum); break; case MP_BC_STORE_MAP: - printf("STORE_MAP"); + mp_printf(print, "STORE_MAP"); break; case MP_BC_BUILD_SET: DECODE_UINT; - printf("BUILD_SET " UINT_FMT, unum); + mp_printf(print, "BUILD_SET " UINT_FMT, unum); break; #if MICROPY_PY_BUILTINS_SLICE case MP_BC_BUILD_SLICE: DECODE_UINT; - printf("BUILD_SLICE " UINT_FMT, unum); + mp_printf(print, "BUILD_SLICE " UINT_FMT, unum); break; #endif case MP_BC_STORE_COMP: DECODE_UINT; - printf("STORE_COMP " UINT_FMT, unum); + mp_printf(print, "STORE_COMP " UINT_FMT, unum); break; case MP_BC_UNPACK_SEQUENCE: DECODE_UINT; - printf("UNPACK_SEQUENCE " UINT_FMT, unum); + mp_printf(print, "UNPACK_SEQUENCE " UINT_FMT, unum); break; case MP_BC_UNPACK_EX: DECODE_UINT; - printf("UNPACK_EX " UINT_FMT, unum); + mp_printf(print, "UNPACK_EX " UINT_FMT, unum); break; case MP_BC_MAKE_FUNCTION: DECODE_PTR; - printf("MAKE_FUNCTION %p", (void *)(uintptr_t)unum); + mp_printf(print, "MAKE_FUNCTION %p", (void *)(uintptr_t)unum); break; case MP_BC_MAKE_FUNCTION_DEFARGS: DECODE_PTR; - printf("MAKE_FUNCTION_DEFARGS %p", (void *)(uintptr_t)unum); + mp_printf(print, "MAKE_FUNCTION_DEFARGS %p", (void *)(uintptr_t)unum); break; case MP_BC_MAKE_CLOSURE: { DECODE_PTR; mp_uint_t n_closed_over = *ip++; - printf("MAKE_CLOSURE %p " UINT_FMT, (void *)(uintptr_t)unum, n_closed_over); + mp_printf(print, "MAKE_CLOSURE %p " UINT_FMT, (void *)(uintptr_t)unum, n_closed_over); break; } case MP_BC_MAKE_CLOSURE_DEFARGS: { DECODE_PTR; mp_uint_t n_closed_over = *ip++; - printf("MAKE_CLOSURE_DEFARGS %p " UINT_FMT, (void *)(uintptr_t)unum, n_closed_over); + mp_printf(print, "MAKE_CLOSURE_DEFARGS %p " UINT_FMT, (void *)(uintptr_t)unum, n_closed_over); break; } case MP_BC_CALL_FUNCTION: DECODE_UINT; - printf("CALL_FUNCTION n=" UINT_FMT " nkw=" UINT_FMT, unum & 0xff, (unum >> 8) & 0xff); + mp_printf(print, "CALL_FUNCTION n=" UINT_FMT " nkw=" UINT_FMT, unum & 0xff, (unum >> 8) & 0xff); break; case MP_BC_CALL_FUNCTION_VAR_KW: DECODE_UINT; - printf("CALL_FUNCTION_VAR_KW n=" UINT_FMT " nkw=" UINT_FMT, unum & 0xff, (unum >> 8) & 0xff); + mp_printf(print, "CALL_FUNCTION_VAR_KW n=" UINT_FMT " nkw=" UINT_FMT, unum & 0xff, (unum >> 8) & 0xff); break; case MP_BC_CALL_METHOD: DECODE_UINT; - printf("CALL_METHOD n=" UINT_FMT " nkw=" UINT_FMT, unum & 0xff, (unum >> 8) & 0xff); + mp_printf(print, "CALL_METHOD n=" UINT_FMT " nkw=" UINT_FMT, unum & 0xff, (unum >> 8) & 0xff); break; case MP_BC_CALL_METHOD_VAR_KW: DECODE_UINT; - printf("CALL_METHOD_VAR_KW n=" UINT_FMT " nkw=" UINT_FMT, unum & 0xff, (unum >> 8) & 0xff); + mp_printf(print, "CALL_METHOD_VAR_KW n=" UINT_FMT " nkw=" UINT_FMT, unum & 0xff, (unum >> 8) & 0xff); break; case MP_BC_RETURN_VALUE: - printf("RETURN_VALUE"); + mp_printf(print, "RETURN_VALUE"); break; case MP_BC_RAISE_LAST: - printf("RAISE_LAST"); + mp_printf(print, "RAISE_LAST"); break; case MP_BC_RAISE_OBJ: - printf("RAISE_OBJ"); + mp_printf(print, "RAISE_OBJ"); break; case MP_BC_RAISE_FROM: - printf("RAISE_FROM"); + mp_printf(print, "RAISE_FROM"); break; case MP_BC_YIELD_VALUE: - printf("YIELD_VALUE"); + mp_printf(print, "YIELD_VALUE"); break; case MP_BC_YIELD_FROM: - printf("YIELD_FROM"); + mp_printf(print, "YIELD_FROM"); break; case MP_BC_IMPORT_NAME: DECODE_QSTR; - printf("IMPORT_NAME '%s'", qstr_str(qst)); + mp_printf(print, "IMPORT_NAME '%s'", qstr_str(qst)); break; case MP_BC_IMPORT_FROM: DECODE_QSTR; - printf("IMPORT_FROM '%s'", qstr_str(qst)); + mp_printf(print, "IMPORT_FROM '%s'", qstr_str(qst)); break; case MP_BC_IMPORT_STAR: - printf("IMPORT_STAR"); + mp_printf(print, "IMPORT_STAR"); break; default: if (ip[-1] < MP_BC_LOAD_CONST_SMALL_INT_MULTI + 64) { - printf("LOAD_CONST_SMALL_INT " INT_FMT, (mp_int_t)ip[-1] - MP_BC_LOAD_CONST_SMALL_INT_MULTI - 16); + mp_printf(print, "LOAD_CONST_SMALL_INT " INT_FMT, (mp_int_t)ip[-1] - MP_BC_LOAD_CONST_SMALL_INT_MULTI - 16); } else if (ip[-1] < MP_BC_LOAD_FAST_MULTI + 16) { - printf("LOAD_FAST " UINT_FMT, (mp_uint_t)ip[-1] - MP_BC_LOAD_FAST_MULTI); + mp_printf(print, "LOAD_FAST " UINT_FMT, (mp_uint_t)ip[-1] - MP_BC_LOAD_FAST_MULTI); } else if (ip[-1] < MP_BC_STORE_FAST_MULTI + 16) { - printf("STORE_FAST " UINT_FMT, (mp_uint_t)ip[-1] - MP_BC_STORE_FAST_MULTI); + mp_printf(print, "STORE_FAST " UINT_FMT, (mp_uint_t)ip[-1] - MP_BC_STORE_FAST_MULTI); } else if (ip[-1] < MP_BC_UNARY_OP_MULTI + MP_UNARY_OP_NUM_BYTECODE) { - printf("UNARY_OP " UINT_FMT, (mp_uint_t)ip[-1] - MP_BC_UNARY_OP_MULTI); + mp_printf(print, "UNARY_OP " UINT_FMT, (mp_uint_t)ip[-1] - MP_BC_UNARY_OP_MULTI); } else if (ip[-1] < MP_BC_BINARY_OP_MULTI + MP_BINARY_OP_NUM_BYTECODE) { mp_uint_t op = ip[-1] - MP_BC_BINARY_OP_MULTI; - printf("BINARY_OP " UINT_FMT " %s", op, qstr_str(mp_binary_op_method_name[op])); + mp_printf(print, "BINARY_OP " UINT_FMT " %s", op, qstr_str(mp_binary_op_method_name[op])); } else { - printf("code %p, byte code 0x%02x not implemented\n", ip - 1, ip[-1]); + mp_printf(print, "code %p, byte code 0x%02x not implemented\n", ip - 1, ip[-1]); assert(0); return ip; } @@ -549,13 +546,13 @@ const byte *mp_bytecode_print_str(const byte *ip) { return ip; } -void mp_bytecode_print2(const byte *ip, size_t len, const mp_uint_t *const_table) { +void mp_bytecode_print2(const mp_print_t *print, const byte *ip, size_t len, const mp_uint_t *const_table) { mp_showbc_code_start = ip; mp_showbc_const_table = const_table; while (ip < len + mp_showbc_code_start) { - printf("%02u ", (uint)(ip - mp_showbc_code_start)); - ip = mp_bytecode_print_str(ip); - printf("\n"); + mp_printf(print, "%02u ", (uint)(ip - mp_showbc_code_start)); + ip = mp_bytecode_print_str(print, ip); + mp_printf(print, "\n"); } } diff --git a/py/vm.c b/py/vm.c index f30aed8ff1e36..393a2bbbd6513 100644 --- a/py/vm.c +++ b/py/vm.c @@ -39,7 +39,7 @@ // *FORMAT-OFF* #if 0 -#define TRACE(ip) printf("sp=%d ", (int)(sp - &code_state->state[0] + 1)); mp_bytecode_print2(ip, 1, code_state->fun_bc->const_table); +#define TRACE(ip) printf("sp=%d ", (int)(sp - &code_state->state[0] + 1)); mp_bytecode_print2(&mp_plat_print, ip, 1, code_state->fun_bc->const_table); #else #define TRACE(ip) #endif diff --git a/tests/basics/annotate_var.py b/tests/basics/annotate_var.py new file mode 100644 index 0000000000000..3f767e4a73505 --- /dev/null +++ b/tests/basics/annotate_var.py @@ -0,0 +1,25 @@ +# test PEP 526, varible annotations + +x: int +print("x" in globals()) + +x: int = 1 +print(x) + +t: tuple = 1, 2 +print(t) + +# a pure annotation in a function makes that variable local +def f(): + x: int + try: + print(x) + except NameError: + print("NameError") +f() + +# here, "x" should remain a global +def f(): + x.y: int + print(x) +f() diff --git a/tests/basics/annotate_var.py.exp b/tests/basics/annotate_var.py.exp new file mode 100644 index 0000000000000..9b6536e966b0e --- /dev/null +++ b/tests/basics/annotate_var.py.exp @@ -0,0 +1,5 @@ +False +1 +(1, 2) +NameError +1 diff --git a/tests/basics/assign_expr.py b/tests/basics/assign_expr.py new file mode 100644 index 0000000000000..f243905dc2a63 --- /dev/null +++ b/tests/basics/assign_expr.py @@ -0,0 +1,29 @@ +(x := 4) +print(x) + +if x := 2: + print(True) +print(x) + +print(4, x := 5) +print(x) + +x = 1 +print(x, x := 5, x) +print(x) + + +def foo(): + print("any", any((hit := i) % 5 == 3 and (hit % 2) == 0 for i in range(10))) + return hit + + +hit = 123 +print(foo()) +print(hit) # shouldn't be changed by foo + +print("any", any((hit := i) % 5 == 3 and (hit % 2) == 0 for i in range(10))) +print(hit) # should be changed by above + +print([((m := k + 1), k * m) for k in range(4)]) +print(m) diff --git a/tests/basics/assign_expr.py.exp b/tests/basics/assign_expr.py.exp new file mode 100644 index 0000000000000..e38e1ae7a626c --- /dev/null +++ b/tests/basics/assign_expr.py.exp @@ -0,0 +1,14 @@ +4 +True +2 +4 5 +5 +1 5 5 +5 +any True +8 +123 +any True +8 +[(1, 0), (2, 2), (3, 6), (4, 12)] +4 diff --git a/tests/basics/assign_expr_syntaxerror.py b/tests/basics/assign_expr_syntaxerror.py new file mode 100644 index 0000000000000..11b3501292546 --- /dev/null +++ b/tests/basics/assign_expr_syntaxerror.py @@ -0,0 +1,16 @@ +# test SyntaxError with := operator + +def test(code): + try: + print(eval(code)) + except SyntaxError: + print('SyntaxError') + +test("x := 1") +test("((x, y) := 1)") + +# these are currently all allowed in MicroPython, but not in CPython +test("([i := i + 1 for i in range(4)])") +test("([i := -1 for i, j in [(1, 2)]])") +test("([[(i := j) for i in range(2)] for j in range(2)])") +test("([[(j := i) for i in range(2)] for j in range(2)])") diff --git a/tests/basics/assign_expr_syntaxerror.py.exp b/tests/basics/assign_expr_syntaxerror.py.exp new file mode 100644 index 0000000000000..2ba7d7df869eb --- /dev/null +++ b/tests/basics/assign_expr_syntaxerror.py.exp @@ -0,0 +1,6 @@ +SyntaxError +SyntaxError +[1, 2, 3, 4] +[-1] +[[0, 0], [1, 1]] +[[0, 1], [0, 1]] diff --git a/tests/basics/async_await2.py b/tests/basics/async_await2.py index 1e3164df93372..56f77604ac1be 100644 --- a/tests/basics/async_await2.py +++ b/tests/basics/async_await2.py @@ -1,6 +1,9 @@ # test await expression -import sys +try: + import usys as sys +except ImportError: + import sys if sys.implementation.name == 'micropython': # uPy allows normal generators to be awaitables coroutine = lambda f: f diff --git a/tests/basics/async_for.py b/tests/basics/async_for.py index 6b4e136d593d4..5fd0540828afe 100644 --- a/tests/basics/async_for.py +++ b/tests/basics/async_for.py @@ -6,7 +6,7 @@ def __init__(self, obj): print('init') self._it = iter(obj) - async def __aiter__(self): + def __aiter__(self): print('aiter') return self diff --git a/tests/basics/async_for2.py b/tests/basics/async_for2.py index 89584fcb100f3..4af3be4c6df32 100644 --- a/tests/basics/async_for2.py +++ b/tests/basics/async_for2.py @@ -1,6 +1,9 @@ -# test waiting within "async for" aiter/anext functions +# test waiting within "async for" __anext__ function -import sys +try: + import usys as sys +except ImportError: + import sys if sys.implementation.name == 'micropython': # uPy allows normal generators to be awaitables coroutine = lambda f: f @@ -21,9 +24,8 @@ def __init__(self, high): self.cur = 0 self.high = high - async def __aiter__(self): + def __aiter__(self): print('aiter') - print('f returned:', await f(10)) return self async def __anext__(self): diff --git a/tests/basics/async_for2.py.exp b/tests/basics/async_for2.py.exp index 886232f7bab73..52bbe90c85376 100644 --- a/tests/basics/async_for2.py.exp +++ b/tests/basics/async_for2.py.exp @@ -1,9 +1,5 @@ init aiter -f start: 10 -coro yielded: 11 -coro yielded: 12 -f returned: 13 anext f start: 20 coro yielded: 21 diff --git a/tests/basics/async_with2.py b/tests/basics/async_with2.py index 44421ae91798e..4dd1386240f2c 100644 --- a/tests/basics/async_with2.py +++ b/tests/basics/async_with2.py @@ -1,6 +1,9 @@ # test waiting within async with enter/exit functions -import sys +try: + import usys as sys +except ImportError: + import sys if sys.implementation.name == 'micropython': # uPy allows normal generators to be awaitables coroutine = lambda f: f diff --git a/tests/basics/attrtuple1.py b/tests/basics/attrtuple1.py index 78a0fbed1b73a..249c030bb4fdb 100644 --- a/tests/basics/attrtuple1.py +++ b/tests/basics/attrtuple1.py @@ -1,7 +1,10 @@ # test attrtuple # we can't test this type directly so we use sys.implementation object -import sys +try: + import usys as sys +except ImportError: + import sys t = sys.implementation # It can be just a normal tuple on small ports diff --git a/tests/basics/builtin_callable.py b/tests/basics/builtin_callable.py index 3ae49f004d18a..c0a9d0c473a5a 100644 --- a/tests/basics/builtin_callable.py +++ b/tests/basics/builtin_callable.py @@ -7,7 +7,10 @@ print(callable("dfsd")) # modules should not be callabe -import sys +try: + import usys as sys +except ImportError: + import sys print(callable(sys)) # builtins should be callable diff --git a/tests/basics/builtin_compile.py b/tests/basics/builtin_compile.py index bf272aca15d43..a2f2cbe550131 100644 --- a/tests/basics/builtin_compile.py +++ b/tests/basics/builtin_compile.py @@ -1,11 +1,10 @@ # test compile builtin -def have_compile(): - try: - compile - return True - except NameError: - return False +try: + compile +except NameError: + print("SKIP") + raise SystemExit def test(): global x @@ -26,8 +25,9 @@ def test(): exec(c, {}, {"x":3}) # single/eval mode - exec(compile('print(1 + 1)', 'file', 'single')) - print(eval(compile('1 + 1', 'file', 'eval'))) + exec(compile("if 1: 10 + 1\n", "file", "single")) + exec(compile("print(10 + 2)", "file", "single")) + print(eval(compile("10 + 3", "file", "eval"))) # bad mode try: @@ -42,8 +42,4 @@ def test(): print("NameError") print(x) # check 'x' still exists as a global -if have_compile(): - test() -else: - print("SKIP") - raise SystemExit +test() diff --git a/tests/basics/builtin_dir.py b/tests/basics/builtin_dir.py index 1eecbd044b7b0..1f2b498d77f77 100644 --- a/tests/basics/builtin_dir.py +++ b/tests/basics/builtin_dir.py @@ -4,7 +4,10 @@ print('__name__' in dir()) # dir of module -import sys +try: + import usys as sys +except ImportError: + import sys print('version' in dir(sys)) # dir of type diff --git a/tests/basics/builtin_getattr.py b/tests/basics/builtin_getattr.py index 59cb7e7f7a4b1..afa4ab3293ca6 100644 --- a/tests/basics/builtin_getattr.py +++ b/tests/basics/builtin_getattr.py @@ -16,3 +16,15 @@ def meth(self, i): print(getattr(a, "_none_such", 123)) print(getattr(list, "foo", 456)) print(getattr(a, "va" + "r2")) + +# test a class that defines __getattr__ and may raise AttributeError +class B: + def __getattr__(self, attr): + if attr == "a": + return attr + else: + raise AttributeError +b = B() +print(getattr(b, "a")) +print(getattr(b, "a", "default")) +print(getattr(b, "b", "default")) diff --git a/tests/basics/class_bind_self.py b/tests/basics/class_bind_self.py index 16e4f5ac95ade..813f876446ef2 100644 --- a/tests/basics/class_bind_self.py +++ b/tests/basics/class_bind_self.py @@ -40,11 +40,18 @@ def f4(self, arg): # generator print(c.f3()) print(next(c.f4(4))) print(c.f5(5)) -#print(c.f6(-6)) not working in uPy +print(c.f6(-6)) print(c.f7(7)) print(c.f8(8)) print(c.f9(9)) +# test calling the functions accessed via the class itself +print(C.f5(10)) +print(C.f6(-11)) +print(C.f7(12)) +print(C.f8(13)) +print(C.f9(14)) + # not working in uPy #class C(list): # # this acts like a method and binds self diff --git a/tests/basics/class_dict.py b/tests/basics/class_dict.py new file mode 100644 index 0000000000000..508ae5e2e543e --- /dev/null +++ b/tests/basics/class_dict.py @@ -0,0 +1,24 @@ +# test __dict__ attribute of a class + +if not hasattr(int, "__dict__"): + print("SKIP") + raise SystemExit + + +# dict of a built-in type +print("from_bytes" in int.__dict__) + + +# dict of a user class +class Foo: + a = 1 + b = "bar" + + +d = Foo.__dict__ +print(d["a"], d["b"]) + + +# dict of a class that has no locals_dict (return empty dict). +d = type(type('')).__dict__ +print(d is not None) diff --git a/tests/basics/class_inplace_op2.py b/tests/basics/class_inplace_op2.py new file mode 100644 index 0000000000000..0e3f2add412c2 --- /dev/null +++ b/tests/basics/class_inplace_op2.py @@ -0,0 +1,73 @@ +# Test inplace special methods enabled by MICROPY_PY_ALL_INPLACE_SPECIAL_METHODS + + +class A: + def __imul__(self, other): + print("__imul__") + return self + + def __imatmul__(self, other): + print("__imatmul__") + return self + + def __ifloordiv__(self, other): + print("__ifloordiv__") + return self + + def __itruediv__(self, other): + print("__itruediv__") + return self + + def __imod__(self, other): + print("__imod__") + return self + + def __ipow__(self, other): + print("__ipow__") + return self + + def __ior__(self, other): + print("__ior__") + return self + + def __ixor__(self, other): + print("__ixor__") + return self + + def __iand__(self, other): + print("__iand__") + return self + + def __ilshift__(self, other): + print("__ilshift__") + return self + + def __irshift__(self, other): + print("__irshift__") + return self + + +a = A() + +try: + a *= None +except TypeError: + print("SKIP") + raise SystemExit + +a @= None +a //= None +a /= None +a %= None +a **= None +a |= None +a ^= None +a &= None +a <<= None +a >>= None + +# Normal operator should not fallback to inplace operator +try: + a * None +except TypeError: + print("TypeError") diff --git a/tests/basics/class_inplace_op2.py.exp b/tests/basics/class_inplace_op2.py.exp new file mode 100644 index 0000000000000..8c323b5178ef4 --- /dev/null +++ b/tests/basics/class_inplace_op2.py.exp @@ -0,0 +1,12 @@ +__imul__ +__imatmul__ +__ifloordiv__ +__itruediv__ +__imod__ +__ipow__ +__ior__ +__ixor__ +__iand__ +__ilshift__ +__irshift__ +TypeError diff --git a/tests/basics/class_ordereddict.py b/tests/basics/class_ordereddict.py new file mode 100644 index 0000000000000..4dd25eb6e15b4 --- /dev/null +++ b/tests/basics/class_ordereddict.py @@ -0,0 +1,18 @@ +# test using an OrderedDict as the locals to construct a class + +try: + from ucollections import OrderedDict +except ImportError: + try: + from collections import OrderedDict + except ImportError: + print("SKIP") + raise SystemExit + +if not hasattr(int, "__dict__"): + print("SKIP") + raise SystemExit + + +A = type("A", (), OrderedDict(a=1, b=2, c=3, d=4, e=5)) +print([k for k in A.__dict__.keys() if not k.startswith("_")]) diff --git a/tests/basics/class_ordereddict.py.exp b/tests/basics/class_ordereddict.py.exp new file mode 100644 index 0000000000000..b723e327515c7 --- /dev/null +++ b/tests/basics/class_ordereddict.py.exp @@ -0,0 +1 @@ +['a', 'b', 'c', 'd', 'e'] diff --git a/tests/basics/containment.py b/tests/basics/containment.py index 4c94a9bae4c81..24317ddd233b9 100644 --- a/tests/basics/containment.py +++ b/tests/basics/containment.py @@ -1,8 +1,8 @@ # sets, see set_containment for i in 1, 2: for o in {1:2}, {1:2}.keys(): - print("{} in {}: {}".format(i, o, i in o)) - print("{} not in {}: {}".format(i, o, i not in o)) + print("{} in {}: {}".format(i, str(o), i in o)) + print("{} not in {}: {}".format(i, str(o), i not in o)) haystack = "supercalifragilistc" for needle in [haystack[i:] for i in range(len(haystack))]: diff --git a/tests/basics/int_big1.py b/tests/basics/int_big1.py index 40d16c455bf38..108e3ee5c9626 100644 --- a/tests/basics/int_big1.py +++ b/tests/basics/int_big1.py @@ -102,7 +102,10 @@ x = -4611686018427387904 # big # sys.maxsize is a constant mpz, so test it's compatible with dynamic ones -import sys +try: + import usys as sys +except ImportError: + import sys print(sys.maxsize + 1 - 1 == sys.maxsize) # test extraction of big int value via mp_obj_get_int_maybe diff --git a/tests/basics/memoryview1.py b/tests/basics/memoryview1.py index 1bfeabdfdc824..4c20c91f4947f 100644 --- a/tests/basics/memoryview1.py +++ b/tests/basics/memoryview1.py @@ -54,54 +54,6 @@ m[2] = 6 print(a) -# test slice assignment between memoryviews -b1 = bytearray(b'1234') -b2 = bytearray(b'5678') -b3 = bytearray(b'5678') -m1 = memoryview(b1) -m2 = memoryview(b2) -m3 = memoryview(b3) -m2[1:3] = m1[0:2] -print(b2) -b3[1:3] = m1[0:2] -print(b3) -m1[2:4] = b3[1:3] -print(b1) - -try: - m2[1:3] = b1[0:4] -except ValueError: - print("ValueError") - -try: - m2[1:3] = m1[0:4] -except ValueError: - print("ValueError") - -try: - m2[0:4] = m1[1:3] -except ValueError: - print("ValueError") - -# test memoryview of arrays with items sized larger than 1 -a1 = array.array('i', [0]*5) -m4 = memoryview(a1) -a2 = array.array('i', [3]*5) -m5 = memoryview(a2) -m4[1:3] = m5[1:3] -print(a1) - -try: - m4[1:3] = m2[1:3] -except ValueError: - print("ValueError") - -# invalid assignment on RHS -try: - memoryview(array.array('i'))[0:2] = b'1234' -except ValueError: - print('ValueError') - # invalid attribute try: memoryview(b'a').noexist diff --git a/tests/basics/memoryview_slice_assign.py b/tests/basics/memoryview_slice_assign.py new file mode 100644 index 0000000000000..74f6fae6f788d --- /dev/null +++ b/tests/basics/memoryview_slice_assign.py @@ -0,0 +1,87 @@ +# test slice assignment to memoryview + +try: + memoryview(bytearray(1))[:] = memoryview(bytearray(1)) +except (NameError, TypeError): + print("SKIP") + raise SystemExit + +try: + import uarray as array +except ImportError: + try: + import array + except ImportError: + print("SKIP") + raise SystemExit + +# test slice assignment between memoryviews +b1 = bytearray(b'1234') +b2 = bytearray(b'5678') +b3 = bytearray(b'5678') +m1 = memoryview(b1) +m2 = memoryview(b2) +m3 = memoryview(b3) +m2[1:3] = m1[0:2] +print(b2) +b3[1:3] = m1[0:2] +print(b3) +m1[2:4] = b3[1:3] +print(b1) + +# invalid slice assignments +try: + m2[1:3] = b1[0:4] +except ValueError: + print("ValueError") +try: + m2[1:3] = m1[0:4] +except ValueError: + print("ValueError") +try: + m2[0:4] = m1[1:3] +except ValueError: + print("ValueError") + +# test memoryview of arrays with items sized larger than 1 +a1 = array.array('i', [0]*5) +m4 = memoryview(a1) +a2 = array.array('i', [3]*5) +m5 = memoryview(a2) +m4[1:3] = m5[1:3] +print(a1) + +try: + m4[1:3] = m2[1:3] +except ValueError: + print("ValueError") + +# invalid assignment on RHS +try: + memoryview(array.array('i'))[0:2] = b'1234' +except ValueError: + print('ValueError') + +# test shift left of bytearray +b = bytearray(range(10)) +mv = memoryview(b) +mv[1:] = mv[:-1] +print(b) + +# test shift right of bytearray +b = bytearray(range(10)) +mv = memoryview(b) +mv[:-1] = mv[1:] +print(b) + +# test shift left of array +a = array.array('I', range(10)) +mv = memoryview(a) +mv[1:] = mv[:-1] +print(a) + +# test shift right of array +a = array.array('I', range(10)) +mv = memoryview(a) +mv[:-1] = mv[1:] +print(a) diff --git a/tests/basics/module2.py b/tests/basics/module2.py index a135601579cd7..5923a27e08d42 100644 --- a/tests/basics/module2.py +++ b/tests/basics/module2.py @@ -1,6 +1,6 @@ # uPy behaviour only: builtin modules are read-only -import sys +import usys try: - sys.x = 1 + usys.x = 1 except AttributeError: print("AttributeError") diff --git a/tests/basics/python34.py b/tests/basics/python34.py index 4030db143c4d7..0f6e4bafd0b9e 100644 --- a/tests/basics/python34.py +++ b/tests/basics/python34.py @@ -33,9 +33,9 @@ def test_syntax(code): # from basics/sys1.py # uPy prints version 3.4 -import sys -print(sys.version[:3]) -print(sys.version_info[0], sys.version_info[1]) +import usys +print(usys.version[:3]) +print(usys.version_info[0], usys.version_info[1]) # from basics/exception1.py # in 3.7 no comma is printed if there is only 1 arg (in 3.4-3.6 one is printed) diff --git a/tests/basics/special_methods.py b/tests/basics/special_methods.py index b56bc1c9c479d..d8af8e07979f2 100644 --- a/tests/basics/special_methods.py +++ b/tests/basics/special_methods.py @@ -100,6 +100,10 @@ def __int__(self): cud2 = Cud() str(cud1) +cud1 == cud1 +cud1 == cud2 +cud1 != cud1 +cud1 != cud2 cud1 < cud2 cud1 <= cud2 cud1 == cud2 diff --git a/tests/basics/special_methods2.py b/tests/basics/special_methods2.py index 09e43fff29973..31f330ab42b9d 100644 --- a/tests/basics/special_methods2.py +++ b/tests/basics/special_methods2.py @@ -129,12 +129,3 @@ def __dir__(self): # test that dir() does not delegate to __dir__ for the type print('a' in dir(Cud)) - -# TODO: the following operations are not supported on every ports -# -# ne is not supported, !(eq) is called instead -#cud1 != cud2 -# -# in the following test, cpython still calls __eq__ -# cud3=cud1 -# cud3==cud1 diff --git a/tests/basics/string_compare.py b/tests/basics/string_compare.py index 6515809b3642d..f34879df25bcb 100644 --- a/tests/basics/string_compare.py +++ b/tests/basics/string_compare.py @@ -51,7 +51,10 @@ # this tests an internal string that doesn't have a hash with a string # that does have a hash, but the lengths of the two strings are different -import sys +try: + import usys as sys +except ImportError: + import sys print(sys.version == 'a long string that has a hash') # this special string would have a hash of 0 but is incremented to 1 diff --git a/tests/basics/sys1.py b/tests/basics/sys1.py index 095824afaf1fc..0947ea1964bf9 100644 --- a/tests/basics/sys1.py +++ b/tests/basics/sys1.py @@ -1,6 +1,9 @@ # test sys module -import sys +try: + import usys as sys +except ImportError: + import sys print(sys.__name__) print(type(sys.path)) diff --git a/tests/basics/sys_exit.py b/tests/basics/sys_exit.py index b1f71549db1c5..4d640ae60e4e9 100644 --- a/tests/basics/sys_exit.py +++ b/tests/basics/sys_exit.py @@ -1,6 +1,9 @@ # test sys module's exit function -import sys +try: + import usys as sys +except ImportError: + import sys try: sys.exit diff --git a/tests/basics/sys_getsizeof.py b/tests/basics/sys_getsizeof.py index fe1b403e04bbf..4dc919848ca4c 100644 --- a/tests/basics/sys_getsizeof.py +++ b/tests/basics/sys_getsizeof.py @@ -1,6 +1,9 @@ # test sys.getsizeof() function -import sys +try: + import usys as sys +except ImportError: + import sys try: sys.getsizeof except AttributeError: diff --git a/tests/cmdline/cmd_parsetree.py.exp b/tests/cmdline/cmd_parsetree.py.exp index 18986318a02b4..e64f4f782951a 100644 --- a/tests/cmdline/cmd_parsetree.py.exp +++ b/tests/cmdline/cmd_parsetree.py.exp @@ -1,31 +1,31 @@ ---------------- -[ 4] rule(1) (n=9) +[ 4] \(rule\|file_input_2\)(1) (n=9) tok(4) -[ 4] rule(22) (n=4) +[ 4] \(rule\|for_stmt\)(22) (n=4) id(i) -[ 4] rule(44) (n=1) +[ 4] \(rule\|atom_paren\)(45) (n=1) NULL -[ 5] rule(8) (n=0) +[ 5] \(rule\|pass_stmt\)(8) (n=0) NULL -[ 6] rule(5) (n=2) +[ 6] \(rule\|expr_stmt\)(5) (n=2) id(a) tok(14) -[ 7] rule(5) (n=2) +[ 7] \(rule\|expr_stmt\)(5) (n=2) id(b) str(str) -[ 8] rule(5) (n=2) +[ 8] \(rule\|expr_stmt\)(5) (n=2) id(c) [ 8] literal \.\+ -[ 9] rule(5) (n=2) +[ 9] \(rule\|expr_stmt\)(5) (n=2) id(d) bytes(bytes) -[ 10] rule(5) (n=2) +[ 10] \(rule\|expr_stmt\)(5) (n=2) id(e) [ 10] literal \.\+ -[ 11] rule(5) (n=2) +[ 11] \(rule\|expr_stmt\)(5) (n=2) id(f) [ 11] literal \.\+ -[ 12] rule(5) (n=2) +[ 12] \(rule\|expr_stmt\)(5) (n=2) id(g) int(123) ---------------- diff --git a/tests/cpydiff/syntax_assign_expr.py b/tests/cpydiff/syntax_assign_expr.py new file mode 100644 index 0000000000000..d4ed063b39ae7 --- /dev/null +++ b/tests/cpydiff/syntax_assign_expr.py @@ -0,0 +1,7 @@ +""" +categories: Syntax,Operators +description: MicroPython allows using := to assign to the variable of a comprehension, CPython raises a SyntaxError. +cause: MicroPython is optimised for code size and doesn't check this case. +workaround: Do not rely on this behaviour if writing CPython compatible code. +""" +print([i := -1 for i in range(4)]) diff --git a/tests/extmod/uasyncio_basic.py b/tests/extmod/uasyncio_basic.py index f6685fa674676..c88908d99b320 100644 --- a/tests/extmod/uasyncio_basic.py +++ b/tests/extmod/uasyncio_basic.py @@ -36,8 +36,16 @@ async def main(): t1 = ticks() await delay_print(0.04, "long") t2 = ticks() - - print("took {} {}".format(round(ticks_diff(t1, t0), -1), round(ticks_diff(t2, t1), -1))) + await delay_print(-1, "negative") + t3 = ticks() + + print( + "took {} {} {}".format( + round(ticks_diff(t1, t0), -1), + round(ticks_diff(t2, t1), -1), + round(ticks_diff(t3, t2), -1), + ) + ) asyncio.run(main()) diff --git a/tests/extmod/uasyncio_basic.py.exp b/tests/extmod/uasyncio_basic.py.exp index 447d05d5e0984..6673978769fbf 100644 --- a/tests/extmod/uasyncio_basic.py.exp +++ b/tests/extmod/uasyncio_basic.py.exp @@ -2,4 +2,5 @@ start after sleep short long -took 20 40 +negative +took 20 40 0 diff --git a/tests/extmod/uasyncio_fair.py b/tests/extmod/uasyncio_fair.py index 9b04454bc1f50..e2257060548bb 100644 --- a/tests/extmod/uasyncio_fair.py +++ b/tests/extmod/uasyncio_fair.py @@ -21,11 +21,13 @@ async def task(id, t): async def main(): t1 = asyncio.create_task(task(1, -0.01)) t2 = asyncio.create_task(task(2, 0.1)) - t3 = asyncio.create_task(task(3, 0.2)) + t3 = asyncio.create_task(task(3, 0.18)) + t4 = asyncio.create_task(task(4, -100)) await asyncio.sleep(0.5) t1.cancel() t2.cancel() t3.cancel() + t4.cancel() print("finish") diff --git a/tests/extmod/uasyncio_fair.py.exp b/tests/extmod/uasyncio_fair.py.exp index 4428943f46287..b4b6481db019f 100644 --- a/tests/extmod/uasyncio_fair.py.exp +++ b/tests/extmod/uasyncio_fair.py.exp @@ -3,6 +3,7 @@ task start 2 task work 2 task start 3 task work 3 +task start 4 task work 2 task work 3 task work 2 diff --git a/tests/extmod/uasyncio_gather.py b/tests/extmod/uasyncio_gather.py index 2697a6278b08c..0e2948b07caa4 100644 --- a/tests/extmod/uasyncio_gather.py +++ b/tests/extmod/uasyncio_gather.py @@ -34,7 +34,7 @@ async def gather_task(): async def main(): # Simple gather with return values - print(await asyncio.gather(factorial("A", 2), factorial("B", 3), factorial("C", 4),)) + print(await asyncio.gather(factorial("A", 2), factorial("B", 3), factorial("C", 4))) # Cancel a multi gather # TODO doesn't work, Task should not forward cancellation from gather to sub-task diff --git a/tests/extmod/uasyncio_micropython.py b/tests/extmod/uasyncio_micropython.py new file mode 100644 index 0000000000000..69e5fa3224df7 --- /dev/null +++ b/tests/extmod/uasyncio_micropython.py @@ -0,0 +1,37 @@ +# Test MicroPython extensions on CPython asyncio: +# - sleep_ms +# - wait_for_ms + +try: + import utime, uasyncio +except ImportError: + print("SKIP") + raise SystemExit + + +async def task(id, t): + print("task start", id) + await uasyncio.sleep_ms(t) + print("task end", id) + return id * 2 + + +async def main(): + # Simple sleep_ms + t0 = utime.ticks_ms() + await uasyncio.sleep_ms(1) + print(utime.ticks_diff(utime.ticks_ms(), t0) < 100) + + # When task finished before the timeout + print(await uasyncio.wait_for_ms(task(1, 5), 50)) + + # When timeout passes and task is cancelled + try: + print(await uasyncio.wait_for_ms(task(2, 50), 5)) + except uasyncio.TimeoutError: + print("timeout") + + print("finish") + + +uasyncio.run(main()) diff --git a/tests/extmod/uasyncio_micropython.py.exp b/tests/extmod/uasyncio_micropython.py.exp new file mode 100644 index 0000000000000..f5be1dc75a254 --- /dev/null +++ b/tests/extmod/uasyncio_micropython.py.exp @@ -0,0 +1,7 @@ +True +task start 1 +task end 1 +2 +task start 2 +timeout +finish diff --git a/tests/extmod/uctypes_array_assign_native_le.py b/tests/extmod/uctypes_array_assign_native_le.py index d4c27fc4b3f5a..5bddfcdf2f601 100644 --- a/tests/extmod/uctypes_array_assign_native_le.py +++ b/tests/extmod/uctypes_array_assign_native_le.py @@ -1,4 +1,4 @@ -import sys +import usys try: import uctypes @@ -6,7 +6,7 @@ print("SKIP") raise SystemExit -if sys.byteorder != "little": +if usys.byteorder != "little": print("SKIP") raise SystemExit diff --git a/tests/extmod/uctypes_array_assign_native_le_intbig.py b/tests/extmod/uctypes_array_assign_native_le_intbig.py index f33c63b4ef959..42583b8afefef 100644 --- a/tests/extmod/uctypes_array_assign_native_le_intbig.py +++ b/tests/extmod/uctypes_array_assign_native_le_intbig.py @@ -1,4 +1,4 @@ -import sys +import usys try: import uctypes @@ -6,7 +6,7 @@ print("SKIP") raise SystemExit -if sys.byteorder != "little": +if usys.byteorder != "little": print("SKIP") raise SystemExit diff --git a/tests/extmod/uctypes_le.py b/tests/extmod/uctypes_le.py index 23f2af982a1a9..f69da30b61ea7 100644 --- a/tests/extmod/uctypes_le.py +++ b/tests/extmod/uctypes_le.py @@ -6,7 +6,7 @@ desc = { "s0": uctypes.UINT16 | 0, - "sub": (0, {"b0": uctypes.UINT8 | 0, "b1": uctypes.UINT8 | 1,}), + "sub": (0, {"b0": uctypes.UINT8 | 0, "b1": uctypes.UINT8 | 1}), "arr": (uctypes.ARRAY | 0, uctypes.UINT8 | 2), "arr2": (uctypes.ARRAY | 0, 2, {"b": uctypes.UINT8 | 0}), "bitf0": uctypes.BFUINT16 | 0 | 0 << uctypes.BF_POS | 8 << uctypes.BF_LEN, diff --git a/tests/extmod/uctypes_native_le.py b/tests/extmod/uctypes_native_le.py index c161539c624f8..9889b98e9d00f 100644 --- a/tests/extmod/uctypes_native_le.py +++ b/tests/extmod/uctypes_native_le.py @@ -1,7 +1,7 @@ # This test is exactly like uctypes_le.py, but uses native structure layout. # Codepaths for packed vs native structures are different. This test only works # on little-endian machine (no matter if 32 or 64 bit). -import sys +import usys try: import uctypes @@ -9,14 +9,14 @@ print("SKIP") raise SystemExit -if sys.byteorder != "little": +if usys.byteorder != "little": print("SKIP") raise SystemExit desc = { "s0": uctypes.UINT16 | 0, - "sub": (0, {"b0": uctypes.UINT8 | 0, "b1": uctypes.UINT8 | 1,}), + "sub": (0, {"b0": uctypes.UINT8 | 0, "b1": uctypes.UINT8 | 1}), "arr": (uctypes.ARRAY | 0, uctypes.UINT8 | 2), "arr2": (uctypes.ARRAY | 0, 2, {"b": uctypes.UINT8 | 0}), "bitf0": uctypes.BFUINT16 | 0 | 0 << uctypes.BF_POS | 8 << uctypes.BF_LEN, diff --git a/tests/extmod/uctypes_ptr_le.py b/tests/extmod/uctypes_ptr_le.py index 5d8094ee48a79..f475465ae80f8 100644 --- a/tests/extmod/uctypes_ptr_le.py +++ b/tests/extmod/uctypes_ptr_le.py @@ -1,4 +1,4 @@ -import sys +import usys try: import uctypes @@ -6,7 +6,7 @@ print("SKIP") raise SystemExit -if sys.byteorder != "little": +if usys.byteorder != "little": print("SKIP") raise SystemExit diff --git a/tests/extmod/uctypes_ptr_native_le.py b/tests/extmod/uctypes_ptr_native_le.py index 8ca4d2c55cf9c..ca2b316c54a5d 100644 --- a/tests/extmod/uctypes_ptr_native_le.py +++ b/tests/extmod/uctypes_ptr_native_le.py @@ -1,4 +1,4 @@ -import sys +import usys try: import uctypes @@ -6,7 +6,7 @@ print("SKIP") raise SystemExit -if sys.byteorder != "little": +if usys.byteorder != "little": print("SKIP") raise SystemExit diff --git a/tests/extmod/uctypes_sizeof_native.py b/tests/extmod/uctypes_sizeof_native.py index 352c191e0d8a6..157e554b09d47 100644 --- a/tests/extmod/uctypes_sizeof_native.py +++ b/tests/extmod/uctypes_sizeof_native.py @@ -28,7 +28,7 @@ "b": uctypes.UINT32 | 4, "c": uctypes.UINT8 | 8, "d": uctypes.UINT32 | 0, - "sub": (4, {"b0": uctypes.UINT8 | 0, "b1": uctypes.UINT8 | 1,}), + "sub": (4, {"b0": uctypes.UINT8 | 0, "b1": uctypes.UINT8 | 1}), } assert uctypes.sizeof(S5) == 12 diff --git a/tests/extmod/ure1.py b/tests/extmod/ure1.py index 9e1be5fc796bc..2bdf2d0cfb4f3 100644 --- a/tests/extmod/ure1.py +++ b/tests/extmod/ure1.py @@ -125,3 +125,14 @@ print(re.compile(r"[a\-x]").split("foo-bar")) print(re.compile(r"[\-ax]").split("foo-bar")) print("===") + +# Module functions take str/bytes/re. +for f in (re.match, re.search): + print(f(".", "foo").group(0)) + print(f(b".", b"foo").group(0)) + print(f(re.compile("."), "foo").group(0)) + try: + f(123, "a") + except TypeError: + print("TypeError") +print("===") diff --git a/tests/extmod/ure_sub.py b/tests/extmod/ure_sub.py index 6bb332077dd29..ae6ad28d621f4 100644 --- a/tests/extmod/ure_sub.py +++ b/tests/extmod/ure_sub.py @@ -43,6 +43,9 @@ def A(): ) ) +# \g immediately followed by another \g +print(re.sub("(abc)", r"\g<1>\g<1>", "abc")) + # no matches at all print(re.sub("a", "b", "c")) @@ -60,3 +63,15 @@ def A(): re.sub("(a)", "b\\199999999999999999999999999999999999999", "a") except: print("invalid group") + +# Module function takes str/bytes/re. +print(re.sub("a", "a", "a")) +print(re.sub(b".", b"a", b"a")) +print(re.sub(re.compile("a"), "a", "a")) +try: + re.sub(123, "a", "a") +except TypeError: + print("TypeError") + +# Include \ in the sub replacement +print(re.sub("b", "\\\\b", "abc")) diff --git a/tests/extmod/usocket_tcp_basic.py b/tests/extmod/usocket_tcp_basic.py new file mode 100644 index 0000000000000..368dfe3c98f4a --- /dev/null +++ b/tests/extmod/usocket_tcp_basic.py @@ -0,0 +1,17 @@ +# Test basic, stand-alone TCP socket functionality + +try: + import usocket as socket, uerrno as errno +except ImportError: + try: + import socket, errno + except ImportError: + print("SKIP") + raise SystemExit + +# recv() on a fresh socket should raise ENOTCONN +s = socket.socket() +try: + s.recv(1) +except OSError as er: + print("ENOTCONN:", er.args[0] == errno.ENOTCONN) diff --git a/tests/extmod/ussl_basic.py b/tests/extmod/ussl_basic.py index b4e21c7dce660..9e1821dca9981 100644 --- a/tests/extmod/ussl_basic.py +++ b/tests/extmod/ussl_basic.py @@ -9,7 +9,7 @@ # create in client mode try: - ss = ssl.wrap_socket(io.BytesIO()) + ss = ssl.wrap_socket(io.BytesIO(), server_hostname="test.example.com") except OSError as er: print("wrap_socket:", repr(er)) diff --git a/tests/extmod/ussl_basic.py.exp b/tests/extmod/ussl_basic.py.exp index 5282338319efb..eb7df855aa099 100644 --- a/tests/extmod/ussl_basic.py.exp +++ b/tests/extmod/ussl_basic.py.exp @@ -1,5 +1,4 @@ -ssl_handshake_status: -256 -wrap_socket: OSError(5,) +wrap_socket: OSError(-256, 'CONN_LOST') <_SSLSocket 4 b'' diff --git a/tests/extmod/utime_time_ns.py b/tests/extmod/utime_time_ns.py new file mode 100644 index 0000000000000..8f3890f1cb85e --- /dev/null +++ b/tests/extmod/utime_time_ns.py @@ -0,0 +1,24 @@ +# test utime.time_ns() + +try: + import utime + + utime.sleep_us + utime.time_ns +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + + +t0 = utime.time_ns() +utime.sleep_us(1000) +t1 = utime.time_ns() + +# Check that time_ns increases. +print(t0 < t1) + +# Check that time_ns counts correctly, but be very lenient with the upper bound (50ms). +if 950000 < t1 - t0 < 50000000: + print(True) +else: + print(t0, t1, t1 - t0) diff --git a/tests/extmod/utime_time_ns.py.exp b/tests/extmod/utime_time_ns.py.exp new file mode 100644 index 0000000000000..dbde422651c9a --- /dev/null +++ b/tests/extmod/utime_time_ns.py.exp @@ -0,0 +1,2 @@ +True +True diff --git a/tests/extmod/vfs_basic.py b/tests/extmod/vfs_basic.py index c8e203b3df882..9a9ef2ca61be3 100644 --- a/tests/extmod/vfs_basic.py +++ b/tests/extmod/vfs_basic.py @@ -10,8 +10,9 @@ class Filesystem: - def __init__(self, id): + def __init__(self, id, fail=0): self.id = id + self.fail = fail def mount(self, readonly, mkfs): print(self.id, "mount", readonly, mkfs) @@ -25,6 +26,8 @@ def ilistdir(self, dir): def chdir(self, dir): print(self.id, "chdir", dir) + if self.fail: + raise OSError(self.fail) def getcwd(self): print(self.id, "getcwd") @@ -71,6 +74,14 @@ def open(self, file, mode): # getcwd when in root dir print(uos.getcwd()) +# test operations on the root directory with nothing mounted, they should all fail +for func in ("chdir", "listdir", "mkdir", "remove", "rmdir", "stat"): + for arg in ("x", "/x"): + try: + getattr(uos, func)(arg) + except OSError: + print(func, arg, "OSError") + # basic mounting and listdir uos.mount(Filesystem(1), "/test_mnt") print(uos.listdir()) @@ -158,3 +169,18 @@ def open(self, file, mode): uos.umount("/") print(uos.listdir("/")) uos.umount("/mnt") + +# chdir to a non-existent mount point (current directory should remain unchanged) +try: + uos.chdir("/foo") +except OSError: + print("OSError") +print(uos.getcwd()) + +# chdir to a non-existent subdirectory in a mounted filesystem +uos.mount(Filesystem(5, 1), "/mnt") +try: + uos.chdir("/mnt/subdir") +except OSError: + print("OSError") +print(uos.getcwd()) diff --git a/tests/extmod/vfs_basic.py.exp b/tests/extmod/vfs_basic.py.exp index 0ae2c2cc975c4..536bb4c805d30 100644 --- a/tests/extmod/vfs_basic.py.exp +++ b/tests/extmod/vfs_basic.py.exp @@ -1,6 +1,18 @@ (16384, 0, 0, 0, 0, 0, 0, 0, 0, 0) True / +chdir x OSError +chdir /x OSError +listdir x OSError +listdir /x OSError +mkdir x OSError +mkdir /x OSError +remove x OSError +remove /x OSError +rmdir x OSError +rmdir /x OSError +stat x OSError +stat /x OSError 1 mount False False ['test_mnt'] ('test_mnt', 16384, 0) @@ -58,3 +70,9 @@ OSError 3 umount ['mnt'] 4 umount +OSError +/ +5 mount False False +5 chdir /subdir +OSError +/ diff --git a/tests/extmod/vfs_fat_more.py b/tests/extmod/vfs_fat_more.py index d8449829deacb..076802f6ad5eb 100644 --- a/tests/extmod/vfs_fat_more.py +++ b/tests/extmod/vfs_fat_more.py @@ -111,10 +111,10 @@ def ioctl(self, op, arg): print(uos.listdir("sys")) # test importing a file from a mounted FS -import sys +import usys -sys.path.clear() -sys.path.append("/sys") +usys.path.clear() +usys.path.append("/sys") with open("sys/test_module.py", "w") as f: f.write('print("test_module!")') import test_module diff --git a/tests/extmod/vfs_fat_mtime.py b/tests/extmod/vfs_fat_mtime.py new file mode 100644 index 0000000000000..d8fd66b75f769 --- /dev/null +++ b/tests/extmod/vfs_fat_mtime.py @@ -0,0 +1,74 @@ +# Test for VfsFat using a RAM device, mtime feature + +try: + import utime, uos + + utime.time + utime.sleep + uos.VfsFat +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + + +class RAMBlockDevice: + ERASE_BLOCK_SIZE = 512 + + def __init__(self, blocks): + self.data = bytearray(blocks * self.ERASE_BLOCK_SIZE) + + def readblocks(self, block, buf): + addr = block * self.ERASE_BLOCK_SIZE + for i in range(len(buf)): + buf[i] = self.data[addr + i] + + def writeblocks(self, block, buf): + addr = block * self.ERASE_BLOCK_SIZE + for i in range(len(buf)): + self.data[addr + i] = buf[i] + + def ioctl(self, op, arg): + if op == 4: # block count + return len(self.data) // self.ERASE_BLOCK_SIZE + if op == 5: # block size + return self.ERASE_BLOCK_SIZE + + +def test(bdev, vfs_class): + print("test", vfs_class) + + # Initial format of block device. + vfs_class.mkfs(bdev) + + # construction + vfs = vfs_class(bdev) + + # Create an empty file, should have a timestamp. + current_time = int(utime.time()) + vfs.open("test1", "wt").close() + + # Wait 2 seconds so mtime will increase (FAT has 2 second resolution). + utime.sleep(2) + + # Create another empty file, should have a timestamp. + vfs.open("test2", "wt").close() + + # Stat the files and check mtime is non-zero. + stat1 = vfs.stat("test1") + stat2 = vfs.stat("test2") + print(stat1[8] != 0, stat2[8] != 0) + + # Check that test1 has mtime which matches time.time() at point of creation. + # TODO this currently fails on the unix port because FAT stores timestamps + # in localtime and stat() is UTC based. + # print(current_time - 1 <= stat1[8] <= current_time + 1) + + # Check that test1 is older than test2. + print(stat1[8] < stat2[8]) + + # Unmount. + vfs.umount() + + +bdev = RAMBlockDevice(50) +test(bdev, uos.VfsFat) diff --git a/tests/extmod/vfs_fat_mtime.py.exp b/tests/extmod/vfs_fat_mtime.py.exp new file mode 100644 index 0000000000000..55550b9834e5b --- /dev/null +++ b/tests/extmod/vfs_fat_mtime.py.exp @@ -0,0 +1,3 @@ +test +True True +True diff --git a/tests/extmod/vfs_fat_ramdisk.py b/tests/extmod/vfs_fat_ramdisk.py index 5f758bf89c87a..9a68d94fedefa 100644 --- a/tests/extmod/vfs_fat_ramdisk.py +++ b/tests/extmod/vfs_fat_ramdisk.py @@ -63,7 +63,7 @@ def ioctl(self, op, arg): f.write("hello!") print(list(vfs.ilistdir())) -print("stat root:", vfs.stat("/")) +print("stat root:", vfs.stat("/")[:-3]) # timestamps differ across runs print("stat file:", vfs.stat("foo_file.txt")[:-3]) # timestamps differ across runs print(b"FOO_FILETXT" in bdev.data) diff --git a/tests/extmod/vfs_fat_ramdisk.py.exp b/tests/extmod/vfs_fat_ramdisk.py.exp index ef6cf1e72b62f..384fa64c76510 100644 --- a/tests/extmod/vfs_fat_ramdisk.py.exp +++ b/tests/extmod/vfs_fat_ramdisk.py.exp @@ -4,7 +4,7 @@ statvfs: (512, 512, 16, 16, 16, 0, 0, 0, 0, 255) getcwd: / True [('foo_file.txt', 32768, 0, 6)] -stat root: (16384, 0, 0, 0, 0, 0, 0, 0, 0, 0) +stat root: (16384, 0, 0, 0, 0, 0, 0) stat file: (32768, 0, 0, 0, 0, 0, 6) True True diff --git a/tests/extmod/vfs_lfs.py b/tests/extmod/vfs_lfs.py index 609d9f949e17d..8e56400df3c2b 100644 --- a/tests/extmod/vfs_lfs.py +++ b/tests/extmod/vfs_lfs.py @@ -35,6 +35,11 @@ def ioctl(self, op, arg): return 0 +def print_stat(st, print_size=True): + # don't print times (just check that they have the correct type) + print(st[:6], st[6] if print_size else -1, type(st[7]), type(st[8]), type(st[9])) + + def test(bdev, vfs_class): print("test", vfs_class) @@ -69,10 +74,10 @@ def test(bdev, vfs_class): vfs.mkdir("testdir") # stat a file - print(vfs.stat("test")) + print_stat(vfs.stat("test")) # stat a dir (size seems to vary on LFS2 so don't print that) - print(vfs.stat("testdir")[:6]) + print_stat(vfs.stat("testdir"), False) # read with vfs.open("test", "r") as f: @@ -112,8 +117,8 @@ def test(bdev, vfs_class): # create file in directory to make sure paths are relative vfs.open("test2", "w").close() - print(vfs.stat("test2")) - print(vfs.stat("/testdir/test2")) + print_stat(vfs.stat("test2")) + print_stat(vfs.stat("/testdir/test2")) vfs.remove("test2") # chdir back to root and remove testdir diff --git a/tests/extmod/vfs_lfs.py.exp b/tests/extmod/vfs_lfs.py.exp index a7025577449d0..a0450c84b79f8 100644 --- a/tests/extmod/vfs_lfs.py.exp +++ b/tests/extmod/vfs_lfs.py.exp @@ -7,8 +7,8 @@ test [('test', 32768, 0, 8), ('testdir', 16384, 0, 0)] [] [('test', 32768, 0, 8)] -(32768, 0, 0, 0, 0, 0, 8, 0, 0, 0) -(16384, 0, 0, 0, 0, 0) +(32768, 0, 0, 0, 0, 0) 8 +(16384, 0, 0, 0, 0, 0) -1 littlefs data length: 4096 write 0 @@ -22,8 +22,8 @@ write 3 [('test', 32768, 0, 8), ('testdir', 16384, 0, 0)] / /testdir -(32768, 0, 0, 0, 0, 0, 0, 0, 0, 0) -(32768, 0, 0, 0, 0, 0, 0, 0, 0, 0) +(32768, 0, 0, 0, 0, 0) 0 +(32768, 0, 0, 0, 0, 0) 0 / /testdir / @@ -44,8 +44,8 @@ test [('testdir', 16384, 0, 0), ('test', 32768, 0, 8)] [] [('test', 32768, 0, 8)] -(32768, 0, 0, 0, 0, 0, 8, 0, 0, 0) -(16384, 0, 0, 0, 0, 0) +(32768, 0, 0, 0, 0, 0) 8 +(16384, 0, 0, 0, 0, 0) -1 littlefs data length: 4096 write 0 @@ -59,8 +59,8 @@ write 3 [('test', 32768, 0, 8), ('testdir', 16384, 0, 0)] / /testdir -(32768, 0, 0, 0, 0, 0, 0, 0, 0, 0) -(32768, 0, 0, 0, 0, 0, 0, 0, 0, 0) +(32768, 0, 0, 0, 0, 0) 0 +(32768, 0, 0, 0, 0, 0) 0 / /testdir / diff --git a/tests/extmod/vfs_lfs_mount.py b/tests/extmod/vfs_lfs_mount.py index 9207f4a8c6d05..2c40b2989796f 100644 --- a/tests/extmod/vfs_lfs_mount.py +++ b/tests/extmod/vfs_lfs_mount.py @@ -68,17 +68,17 @@ def test(bdev, vfs_class): uos.umount("/lfs") # clear imported modules - sys.modules.clear() + usys.modules.clear() bdev = RAMBlockDevice(30) # initialise path -import sys +import usys -sys.path.clear() -sys.path.append("/lfs") -sys.path.append("") +usys.path.clear() +usys.path.append("/lfs") +usys.path.append("") # run tests test(bdev, uos.VfsLfs1) diff --git a/tests/extmod/vfs_lfs_mtime.py b/tests/extmod/vfs_lfs_mtime.py new file mode 100644 index 0000000000000..bacdd2246c6d6 --- /dev/null +++ b/tests/extmod/vfs_lfs_mtime.py @@ -0,0 +1,105 @@ +# Test for VfsLfs using a RAM device, mtime feature + +try: + import utime, uos + + utime.time + utime.sleep + uos.VfsLfs2 +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + + +class RAMBlockDevice: + ERASE_BLOCK_SIZE = 1024 + + def __init__(self, blocks): + self.data = bytearray(blocks * self.ERASE_BLOCK_SIZE) + + def readblocks(self, block, buf, off): + addr = block * self.ERASE_BLOCK_SIZE + off + for i in range(len(buf)): + buf[i] = self.data[addr + i] + + def writeblocks(self, block, buf, off): + addr = block * self.ERASE_BLOCK_SIZE + off + for i in range(len(buf)): + self.data[addr + i] = buf[i] + + def ioctl(self, op, arg): + if op == 4: # block count + return len(self.data) // self.ERASE_BLOCK_SIZE + if op == 5: # block size + return self.ERASE_BLOCK_SIZE + if op == 6: # erase block + return 0 + + +def test(bdev, vfs_class): + print("test", vfs_class) + + # Initial format of block device. + vfs_class.mkfs(bdev) + + # construction + print("mtime=True") + vfs = vfs_class(bdev, mtime=True) + + # Create an empty file, should have a timestamp. + current_time = int(utime.time()) + vfs.open("test1", "wt").close() + + # Wait 1 second so mtime will increase by at least 1. + utime.sleep(1) + + # Create another empty file, should have a timestamp. + vfs.open("test2", "wt").close() + + # Stat the files and check mtime is non-zero. + stat1 = vfs.stat("test1") + stat2 = vfs.stat("test2") + print(stat1[8] != 0, stat2[8] != 0) + + # Check that test1 has mtime which matches time.time() at point of creation. + print(current_time <= stat1[8] <= current_time + 1) + + # Check that test1 is older than test2. + print(stat1[8] < stat2[8]) + + # Wait 1 second so mtime will increase by at least 1. + utime.sleep(1) + + # Open test1 for reading and ensure mtime did not change. + vfs.open("test1", "rt").close() + print(vfs.stat("test1") == stat1) + + # Open test1 for writing and ensure mtime increased from the previous value. + vfs.open("test1", "wt").close() + stat1_old = stat1 + stat1 = vfs.stat("test1") + print(stat1_old[8] < stat1[8]) + + # Unmount. + vfs.umount() + + # Check that remounting with mtime=False can read the timestamps. + print("mtime=False") + vfs = vfs_class(bdev, mtime=False) + print(vfs.stat("test1") == stat1) + print(vfs.stat("test2") == stat2) + f = vfs.open("test1", "wt") + f.close() + print(vfs.stat("test1") == stat1) + vfs.umount() + + # Check that remounting with mtime=True still has the timestamps. + print("mtime=True") + vfs = vfs_class(bdev, mtime=True) + print(vfs.stat("test1") == stat1) + print(vfs.stat("test2") == stat2) + vfs.umount() + + +bdev = RAMBlockDevice(30) +test(bdev, uos.VfsLfs2) diff --git a/tests/extmod/vfs_lfs_mtime.py.exp b/tests/extmod/vfs_lfs_mtime.py.exp new file mode 100644 index 0000000000000..cf6455c04015b --- /dev/null +++ b/tests/extmod/vfs_lfs_mtime.py.exp @@ -0,0 +1,14 @@ +test +mtime=True +True True +True +True +True +True +mtime=False +True +True +True +mtime=True +True +True diff --git a/tests/extmod/vfs_userfs.py b/tests/extmod/vfs_userfs.py index 06e546b081a97..570b8335341e1 100644 --- a/tests/extmod/vfs_userfs.py +++ b/tests/extmod/vfs_userfs.py @@ -1,7 +1,7 @@ # test VFS functionality with a user-defined filesystem # also tests parts of uio.IOBase implementation -import sys +import usys try: import uio @@ -16,14 +16,20 @@ class UserFile(uio.IOBase): - def __init__(self, data): + def __init__(self, mode, data): + assert isinstance(data, bytes) + self.is_text = mode.find("b") == -1 self.data = data self.pos = 0 def read(self): - return self.data + if self.is_text: + return str(self.data, "utf8") + else: + return self.data def readinto(self, buf): + assert not self.is_text n = 0 while n < len(buf) and self.pos < len(self.data): buf[n] = self.data[self.pos] @@ -54,12 +60,12 @@ def stat(self, path): def open(self, path, mode): print("open", path, mode) - return UserFile(self.files[path]) + return UserFile(mode, self.files[path]) # create and mount a user filesystem user_files = { - "/data.txt": b"some data in a text file\n", + "/data.txt": b"some data in a text file", "/usermod1.py": b"print('in usermod1')\nimport usermod2", "/usermod2.py": b"print('in usermod2')", } @@ -70,9 +76,9 @@ def open(self, path, mode): print(f.read()) # import files from the user filesystem -sys.path.append("/userfs") +usys.path.append("/userfs") import usermod1 # unmount and undo path addition uos.umount("/userfs") -sys.path.pop() +usys.path.pop() diff --git a/tests/extmod/vfs_userfs.py.exp b/tests/extmod/vfs_userfs.py.exp index 6a4d925b91e8b..00ddd95fca508 100644 --- a/tests/extmod/vfs_userfs.py.exp +++ b/tests/extmod/vfs_userfs.py.exp @@ -1,12 +1,12 @@ open /data.txt r -b'some data in a text file\n' +some data in a text file stat /usermod1 stat /usermod1.py -open /usermod1.py r +open /usermod1.py rb ioctl 4 0 in usermod1 stat /usermod2 stat /usermod2.py -open /usermod2.py r +open /usermod2.py rb ioctl 4 0 in usermod2 diff --git a/tests/feature_check/byteorder.py b/tests/feature_check/byteorder.py index c82a41a24b1c0..509bd8b1b51de 100644 --- a/tests/feature_check/byteorder.py +++ b/tests/feature_check/byteorder.py @@ -1,3 +1,6 @@ -import sys +try: + import usys as sys +except ImportError: + import sys print(sys.byteorder) diff --git a/tests/float/cmath_fun.py b/tests/float/cmath_fun.py index 7b5e692452765..39011733b02b4 100644 --- a/tests/float/cmath_fun.py +++ b/tests/float/cmath_fun.py @@ -22,7 +22,7 @@ test_values_non_zero.append(complex(r, -i)) if r != 0.0 and i != 0.0: test_values_non_zero.append(complex(-r, -i)) -test_values = [complex(0.0, 0.0),] + test_values_non_zero +test_values = [complex(0.0, 0.0)] + test_values_non_zero print(test_values) functions = [ @@ -57,3 +57,9 @@ if abs(real) < 1e-6: real = 0.0 print("complex(%.5g, %.5g)" % (real, ret.imag)) + +# test invalid type passed to cmath function +try: + log([]) +except TypeError: + print("TypeError") diff --git a/tests/float/complex_reverse_op.py b/tests/float/complex_reverse_op.py new file mode 100644 index 0000000000000..a7351949d0cea --- /dev/null +++ b/tests/float/complex_reverse_op.py @@ -0,0 +1,10 @@ +# test complex interacting with special reverse methods + + +class A: + def __radd__(self, x): + print("__radd__") + return 2 + + +print(1j + A()) diff --git a/tests/float/complex_special_methods.py b/tests/float/complex_special_methods.py new file mode 100644 index 0000000000000..7e54905e495ad --- /dev/null +++ b/tests/float/complex_special_methods.py @@ -0,0 +1,10 @@ +# test complex interacting with special methods + + +class A: + def __add__(self, x): + print("__add__") + return 1 + + +print(A() + 1j) diff --git a/tests/float/float2int_doubleprec_intbig.py b/tests/float/float2int_doubleprec_intbig.py index 84fa2d25360e1..418eddb60781a 100644 --- a/tests/float/float2int_doubleprec_intbig.py +++ b/tests/float/float2int_doubleprec_intbig.py @@ -2,10 +2,10 @@ try: import ustruct as struct + import usys as sys except: import struct - -import sys + import sys maxsize_bits = 0 maxsize = sys.maxsize diff --git a/tests/float/float2int_fp30_intbig.py b/tests/float/float2int_fp30_intbig.py index 75ff52f39dd49..fcbe2e309f0be 100644 --- a/tests/float/float2int_fp30_intbig.py +++ b/tests/float/float2int_fp30_intbig.py @@ -2,10 +2,10 @@ try: import ustruct as struct + import usys as sys except: import struct - -import sys + import sys maxsize_bits = 0 maxsize = sys.maxsize diff --git a/tests/float/float2int_intbig.py b/tests/float/float2int_intbig.py index 62aca39634968..865aeea7b9171 100644 --- a/tests/float/float2int_intbig.py +++ b/tests/float/float2int_intbig.py @@ -2,10 +2,10 @@ try: import ustruct as struct + import usys as sys except: import struct - -import sys + import sys maxsize_bits = 0 maxsize = sys.maxsize diff --git a/tests/float/inf_nan_arith.py b/tests/float/inf_nan_arith.py new file mode 100644 index 0000000000000..c27e38bc520be --- /dev/null +++ b/tests/float/inf_nan_arith.py @@ -0,0 +1,20 @@ +# Test behaviour of inf and nan in basic float operations + +inf = float("inf") +nan = float("nan") + +values = (-2, -1, 0, 1, 2, inf, nan) + +for x in values: + for y in values: + print(x, y) + print(" + - *", x + y, x - y, x * y) + try: + print(" /", x / y) + except ZeroDivisionError: + print(" / ZeroDivisionError") + try: + print(" ** pow", x ** y, pow(x, y)) + except ZeroDivisionError: + print(" ** pow ZeroDivisionError") + print(" == != < <= > >=", x == y, x != y, x < y, x <= y, x > y, x >= y) diff --git a/tests/float/math_domain.py b/tests/float/math_domain.py index 2d4670f75dd48..0c25dc08b6649 100644 --- a/tests/float/math_domain.py +++ b/tests/float/math_domain.py @@ -38,9 +38,9 @@ # double argument functions for name, f, args in ( - ("pow", math.pow, ((0, 2), (-1, 2), (0, -1), (-1, 2.3))), - ("fmod", math.fmod, ((1.2, inf), (1.2, 0), (inf, 1.2))), - ("atan2", math.atan2, ((0, 0),)), + ("pow", math.pow, ((0, 2), (-1, 2), (0, -1), (-1, 2.3), (nan, 0), (1, nan))), + ("fmod", math.fmod, ((1.2, inf), (1.2, -inf), (1.2, 0), (inf, 1.2))), + ("atan2", math.atan2, ((0, 0), (-inf, inf), (-inf, -inf), (inf, -inf))), ("copysign", math.copysign, ()), ): for x in args + ((0, inf), (inf, 0), (inf, inf), (inf, nan), (nan, inf), (nan, nan)): diff --git a/tests/float/math_fun.py b/tests/float/math_fun.py index 7b6bb86489b7d..0d443a4754521 100644 --- a/tests/float/math_fun.py +++ b/tests/float/math_fun.py @@ -62,10 +62,10 @@ copysign, [(23.0, 42.0), (-23.0, 42.0), (23.0, -42.0), (-23.0, -42.0), (1.0, 0.0), (1.0, -0.0)], ), - ("pow", pow, ((1.0, 0.0), (0.0, 1.0), (2.0, 0.5), (-3.0, 5.0), (-3.0, -4.0),)), - ("atan2", atan2, ((1.0, 0.0), (0.0, 1.0), (2.0, 0.5), (-3.0, 5.0), (-3.0, -4.0),)), - ("fmod", fmod, ((1.0, 1.0), (0.0, 1.0), (2.0, 0.5), (-3.0, 5.0), (-3.0, -4.0),)), - ("ldexp", ldexp, ((1.0, 0), (0.0, 1), (2.0, 2), (3.0, -2), (-3.0, -4),)), + ("pow", pow, ((1.0, 0.0), (0.0, 1.0), (2.0, 0.5), (-3.0, 5.0), (-3.0, -4.0))), + ("atan2", atan2, ((1.0, 0.0), (0.0, 1.0), (2.0, 0.5), (-3.0, 5.0), (-3.0, -4.0))), + ("fmod", fmod, ((1.0, 1.0), (0.0, 1.0), (2.0, 0.5), (-3.0, 5.0), (-3.0, -4.0))), + ("ldexp", ldexp, ((1.0, 0), (0.0, 1), (2.0, 2), (3.0, -2), (-3.0, -4))), ( "log", log, diff --git a/tests/float/math_fun_special.py b/tests/float/math_fun_special.py index c101a7e5019db..614470c0f15dd 100644 --- a/tests/float/math_fun_special.py +++ b/tests/float/math_fun_special.py @@ -40,7 +40,7 @@ ("erf", erf, test_values), ("erfc", erfc, test_values), ("gamma", gamma, pos_test_values), - ("lgamma", lgamma, pos_test_values + [50.0, 100.0,]), + ("lgamma", lgamma, pos_test_values + [50.0, 100.0]), ] for function_name, function, test_vals in functions: diff --git a/tests/io/argv.py b/tests/io/argv.py index 53254da11906b..834292504d05d 100644 --- a/tests/io/argv.py +++ b/tests/io/argv.py @@ -1,3 +1,6 @@ -import sys +try: + import usys as sys +except ImportError: + import sys print(sys.argv) diff --git a/tests/io/builtin_print_file.py b/tests/io/builtin_print_file.py index 822356a6cc305..e5c20b64e4bec 100644 --- a/tests/io/builtin_print_file.py +++ b/tests/io/builtin_print_file.py @@ -1,6 +1,9 @@ # test builtin print function, using file= argument -import sys +try: + import usys as sys +except ImportError: + import sys try: sys.stdout diff --git a/tests/io/file_stdio.py b/tests/io/file_stdio.py index cbdb070163c31..6c08f35d78784 100644 --- a/tests/io/file_stdio.py +++ b/tests/io/file_stdio.py @@ -1,4 +1,7 @@ -import sys +try: + import usys as sys +except ImportError: + import sys print(sys.stdin.fileno()) print(sys.stdout.fileno()) diff --git a/tests/io/resource_stream.py b/tests/io/resource_stream.py index 5656205b692d6..b589ff99bf2ba 100644 --- a/tests/io/resource_stream.py +++ b/tests/io/resource_stream.py @@ -1,5 +1,5 @@ import uio -import sys +import usys try: uio.resource_stream @@ -11,5 +11,5 @@ print(buf.read()) # resource_stream(None, ...) look ups from current dir, hence sys.path[0] hack -buf = uio.resource_stream(None, sys.path[0] + "/data/file2") +buf = uio.resource_stream(None, usys.path[0] + "/data/file2") print(buf.read()) diff --git a/tests/micropython/emg_exc.py b/tests/micropython/emg_exc.py index bca4d2d9fbaca..b8df94b071af8 100644 --- a/tests/micropython/emg_exc.py +++ b/tests/micropython/emg_exc.py @@ -1,7 +1,7 @@ # test that emergency exceptions work import micropython -import sys +import usys try: import uio @@ -26,7 +26,7 @@ def f(): # print the exception buf = uio.StringIO() - sys.print_exception(exc, buf) + usys.print_exception(exc, buf) for l in buf.getvalue().split("\n"): if l.startswith(" File "): print(l.split('"')[2]) diff --git a/tests/micropython/heapalloc_traceback.py b/tests/micropython/heapalloc_traceback.py index eabd09388b90b..09a2ad2c14d6e 100644 --- a/tests/micropython/heapalloc_traceback.py +++ b/tests/micropython/heapalloc_traceback.py @@ -1,7 +1,7 @@ # test that we can generate a traceback without allocating import micropython -import sys +import usys try: import uio @@ -33,7 +33,7 @@ def test(): # print the exception that was raised buf = uio.StringIO() -sys.print_exception(global_exc, buf) +usys.print_exception(global_exc, buf) for l in buf.getvalue().split("\n"): # uPy on pyboard prints as file, so remove filename. if l.startswith(" File "): diff --git a/tests/import/mpy_invalid.py b/tests/micropython/import_mpy_invalid.py similarity index 80% rename from tests/import/mpy_invalid.py rename to tests/micropython/import_mpy_invalid.py index d6d01e7f1fc0c..02fd4b1254971 100644 --- a/tests/import/mpy_invalid.py +++ b/tests/micropython/import_mpy_invalid.py @@ -1,11 +1,9 @@ # test importing of invalid .mpy files -import sys, uio - try: - uio.IOBase - import uos + import usys, uio, uos + uio.IOBase uos.mount except (ImportError, AttributeError): print("SKIP") @@ -14,18 +12,13 @@ class UserFile(uio.IOBase): def __init__(self, data): - self.data = data + self.data = memoryview(data) self.pos = 0 - def read(self): - return self.data - def readinto(self, buf): - n = 0 - while n < len(buf) and self.pos < len(self.data): - buf[n] = self.data[self.pos] - n += 1 - self.pos += 1 + n = min(len(buf), len(self.data) - self.pos) + buf[:n] = self.data[self.pos : self.pos + n] + self.pos += n return n def ioctl(self, req, arg): @@ -61,7 +54,7 @@ def open(self, path, mode): # create and mount a user filesystem uos.mount(UserFS(user_files), "/userfs") -sys.path.append("/userfs") +usys.path.append("/userfs") # import .mpy files from the user filesystem for i in range(len(user_files)): @@ -73,4 +66,4 @@ def open(self, path, mode): # unmount and undo path addition uos.umount("/userfs") -sys.path.pop() +usys.path.pop() diff --git a/tests/import/mpy_invalid.py.exp b/tests/micropython/import_mpy_invalid.py.exp similarity index 100% rename from tests/import/mpy_invalid.py.exp rename to tests/micropython/import_mpy_invalid.py.exp diff --git a/tests/micropython/import_mpy_native_gc.py b/tests/micropython/import_mpy_native_gc.py new file mode 100644 index 0000000000000..e8fac8f179371 --- /dev/null +++ b/tests/micropython/import_mpy_native_gc.py @@ -0,0 +1,91 @@ +# Test that native code loaded from a .mpy file is retained after a GC. + +try: + import gc, sys, uio, uos + + sys.implementation.mpy + uio.IOBase + uos.mount +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + + +class UserFile(uio.IOBase): + def __init__(self, data): + self.data = memoryview(data) + self.pos = 0 + + def readinto(self, buf): + n = min(len(buf), len(self.data) - self.pos) + buf[:n] = self.data[self.pos : self.pos + n] + self.pos += n + return n + + def ioctl(self, req, arg): + return 0 + + +class UserFS: + def __init__(self, files): + self.files = files + + def mount(self, readonly, mksfs): + pass + + def umount(self): + pass + + def stat(self, path): + if path in self.files: + return (32768, 0, 0, 0, 0, 0, 0, 0, 0, 0) + raise OSError + + def open(self, path, mode): + return UserFile(self.files[path]) + + +# Pre-compiled examples/natmod/features0 example for various architectures, keyed +# by the required value of sys.implementation.mpy. +features0_file_contents = { + # -march=x64 -mcache-lookup-bc + 0xB05: b'M\x05\x0b\x1f \x84b\xe9/\x00\x00\x00SH\x8b\x1ds\x00\x00\x00\xbe\x02\x00\x00\x00\xffS\x18\xbf\x01\x00\x00\x00H\x85\xc0u\x0cH\x8bC \xbe\x02\x00\x00\x00[\xff\xe0H\x0f\xaf\xf8H\xff\xc8\xeb\xe6ATUSH\x8b\x1dA\x00\x00\x00H\x8b\x7f\x08L\x8bc(A\xff\xd4H\x8d5\x1f\x00\x00\x00H\x89\xc5H\x8b\x05-\x00\x00\x00\x0f\xb78\xffShH\x89\xefA\xff\xd4H\x8b\x03[]A\\\xc3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x90\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x84@\x12factorial\x10\x00\x00\r \x01"\x9f\x1c\x01\x1e\xff', + # -march=armv7m + 0x1605: b"M\x05\x16\x1f \x84\x12\x1a\xe0\x00\x00\x13\xb5\nK\nJ{D\x9cX\x02!\xe3h\x98G\x03F\x01 3\xb9\x02!#i\x01\x93\x02\xb0\xbd\xe8\x10@\x18GXC\x01;\xf4\xe7\x00\xbfj\x00\x00\x00\x00\x00\x00\x00\xf8\xb5\tN\tK~D\xf4X@hgi\xb8G\x05F\x07K\x07I\xf2XyD\x10\x88ck\x98G(F\xb8G h\xf8\xbd6\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x1c\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x01\x84\x00\x12factorial\x10\x00\x00\r<\x01>\x9f8\x01:\xff", +} + +# Populate other armv7m-derived archs based on armv7m. +for arch in (0x1A05, 0x1E05, 0x2205): + features0_file_contents[arch] = features0_file_contents[0x1605] + +if sys.implementation.mpy not in features0_file_contents: + print("SKIP") + raise SystemExit + +# These are the test .mpy files. +user_files = {"/features0.mpy": features0_file_contents[sys.implementation.mpy]} + +# Create and mount a user filesystem. +uos.mount(UserFS(user_files), "/userfs") +sys.path.append("/userfs") + +# Import the native function. +gc.collect() +from features0 import factorial + +# Free the module that contained the function. +del sys.modules["features0"] + +# Run a GC cycle which should reclaim the module but not the function. +gc.collect() + +# Allocate lots of fragmented memory to overwrite anything that was just freed by the GC. +for i in range(1000): + [] + +# Run the native function, it should not have been freed or overwritten. +print(factorial(10)) + +# Unmount and undo path addition. +uos.umount("/userfs") +sys.path.pop() diff --git a/tests/micropython/import_mpy_native_gc.py.exp b/tests/micropython/import_mpy_native_gc.py.exp new file mode 100644 index 0000000000000..3fbd4a8698fa3 --- /dev/null +++ b/tests/micropython/import_mpy_native_gc.py.exp @@ -0,0 +1 @@ +3628800 diff --git a/tests/import/mpy_native.py b/tests/micropython/import_mpy_native_x64.py similarity index 88% rename from tests/import/mpy_native.py rename to tests/micropython/import_mpy_native_x64.py index 5d7bdab4a2aaa..3e7b2058eb812 100644 --- a/tests/import/mpy_native.py +++ b/tests/micropython/import_mpy_native_x64.py @@ -1,35 +1,28 @@ # test importing of .mpy files with native code (x64 only) -import sys, uio - try: - uio.IOBase - import uos + import usys, uio, uos + uio.IOBase uos.mount except (ImportError, AttributeError): print("SKIP") raise SystemExit -if not (sys.platform == "linux" and sys.maxsize > 2 ** 32): +if not (usys.platform == "linux" and usys.maxsize > 2 ** 32): print("SKIP") raise SystemExit class UserFile(uio.IOBase): def __init__(self, data): - self.data = data + self.data = memoryview(data) self.pos = 0 - def read(self): - return self.data - def readinto(self, buf): - n = 0 - while n < len(buf) and self.pos < len(self.data): - buf[n] = self.data[self.pos] - n += 1 - self.pos += 1 + n = min(len(buf), len(self.data) - self.pos) + buf[:n] = self.data[self.pos : self.pos + n] + self.pos += n return n def ioctl(self, req, arg): @@ -108,7 +101,7 @@ def open(self, path, mode): # create and mount a user filesystem uos.mount(UserFS(user_files), "/userfs") -sys.path.append("/userfs") +usys.path.append("/userfs") # import .mpy files from the user filesystem for i in range(len(user_files)): @@ -121,4 +114,4 @@ def open(self, path, mode): # unmount and undo path addition uos.umount("/userfs") -sys.path.pop() +usys.path.pop() diff --git a/tests/import/mpy_native.py.exp b/tests/micropython/import_mpy_native_x64.py.exp similarity index 100% rename from tests/import/mpy_native.py.exp rename to tests/micropython/import_mpy_native_x64.py.exp diff --git a/tests/micropython/opt_level_lineno.py b/tests/micropython/opt_level_lineno.py index d8253e54b41f0..1cbf2fb1a8548 100644 --- a/tests/micropython/opt_level_lineno.py +++ b/tests/micropython/opt_level_lineno.py @@ -3,4 +3,4 @@ # 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)") +exec("try:\n xyz\nexcept NameError as er:\n import usys\n usys.print_exception(er)") diff --git a/tests/micropython/viper_binop_arith_uint.py b/tests/micropython/viper_binop_arith_uint.py new file mode 100644 index 0000000000000..e4270a10a79a9 --- /dev/null +++ b/tests/micropython/viper_binop_arith_uint.py @@ -0,0 +1,32 @@ +# test arithmetic operators with uint type + + +@micropython.viper +def add(x: uint, y: uint): + return x + y, y + x + + +print("add") +print(*add(1, 2)) +print(*(x & 0xFFFFFFFF for x in add(-1, -2))) + + +@micropython.viper +def sub(x: uint, y: uint): + return x - y, y - x + + +print("sub") +print(*(x & 0xFFFFFFFF for x in sub(1, 2))) +print(*(x & 0xFFFFFFFF for x in sub(-1, -2))) + + +@micropython.viper +def mul(x: uint, y: uint): + return x * y, y * x + + +print("mul") +print(*mul(2, 3)) +print(*(x & 0xFFFFFFFF for x in mul(2, -3))) +print(*mul(-2, -3)) diff --git a/tests/micropython/viper_binop_arith_uint.py.exp b/tests/micropython/viper_binop_arith_uint.py.exp new file mode 100644 index 0000000000000..72f84b716a4dd --- /dev/null +++ b/tests/micropython/viper_binop_arith_uint.py.exp @@ -0,0 +1,10 @@ +add +3 3 +4294967293 4294967293 +sub +4294967295 1 +1 4294967295 +mul +6 6 +4294967290 4294967290 +6 6 diff --git a/tests/micropython/viper_binop_bitwise_uint.py b/tests/micropython/viper_binop_bitwise_uint.py new file mode 100644 index 0000000000000..3bc7ba8d1113f --- /dev/null +++ b/tests/micropython/viper_binop_bitwise_uint.py @@ -0,0 +1,58 @@ +# test bitwise operators on uint type + + +@micropython.viper +def shl(x: uint, y: uint) -> uint: + return x << y + + +print("shl") +print(shl(1, 0)) +print(shl(1, 30)) +print(shl(-1, 10) & 0xFFFFFFFF) + + +@micropython.viper +def shr(x: uint, y: uint) -> uint: + return x >> y + + +print("shr") +print(shr(1, 0)) +print(shr(16, 3)) +print(shr(-1, 1) in (0x7FFFFFFF, 0x7FFFFFFF_FFFFFFFF)) + + +@micropython.viper +def and_(x: uint, y: uint): + return x & y, y & x + + +print("and") +print(*and_(1, 0)) +print(*and_(1, 3)) +print(*and_(-1, 2)) +print(*(x & 0xFFFFFFFF for x in and_(-1, -2))) + + +@micropython.viper +def or_(x: uint, y: uint): + return x | y, y | x + + +print("or") +print(*or_(1, 0)) +print(*or_(1, 2)) +print(*(x & 0xFFFFFFFF for x in or_(-1, 2))) + + +@micropython.viper +def xor(x: uint, y: uint): + return x ^ y, y ^ x + + +print("xor") +print(*xor(1, 0)) +print(*xor(1, 3)) +print(*(x & 0xFFFFFFFF for x in xor(-1, 3))) +print(*xor(-1, -3)) diff --git a/tests/micropython/viper_binop_bitwise_uint.py.exp b/tests/micropython/viper_binop_bitwise_uint.py.exp new file mode 100644 index 0000000000000..3ad6469a2fc78 --- /dev/null +++ b/tests/micropython/viper_binop_bitwise_uint.py.exp @@ -0,0 +1,22 @@ +shl +1 +1073741824 +4294966272 +shr +1 +2 +True +and +0 0 +1 1 +2 2 +4294967294 4294967294 +or +1 1 +3 3 +4294967295 4294967295 +xor +1 1 +2 2 +4294967292 4294967292 +2 2 diff --git a/tests/micropython/viper_binop_comp_uint.py b/tests/micropython/viper_binop_comp_uint.py new file mode 100644 index 0000000000000..85aa32c78c733 --- /dev/null +++ b/tests/micropython/viper_binop_comp_uint.py @@ -0,0 +1,31 @@ +# test comparison operators with uint type + + +@micropython.viper +def f(x: uint, y: uint): + if x < y: + print(" <", end="") + if x > y: + print(" >", end="") + if x == y: + print(" ==", end="") + if x <= y: + print(" <=", end="") + if x >= y: + print(" >=", end="") + if x != y: + print(" !=", end="") + + +def test(a, b): + print(a, b, end="") + f(a, b) + print() + + +test(1, 1) +test(2, 1) +test(1, 2) +test(2, -1) +test(-2, 1) +test(-2, -1) diff --git a/tests/micropython/viper_binop_comp_uint.py.exp b/tests/micropython/viper_binop_comp_uint.py.exp new file mode 100644 index 0000000000000..cacce62b6e380 --- /dev/null +++ b/tests/micropython/viper_binop_comp_uint.py.exp @@ -0,0 +1,6 @@ +1 1 == <= >= +2 1 > >= != +1 2 < <= != +2 -1 < <= != +-2 1 > >= != +-2 -1 < <= != diff --git a/tests/micropython/viper_error.py b/tests/micropython/viper_error.py index 790f3d75c4f0e..80617af0c1f29 100644 --- a/tests/micropython/viper_error.py +++ b/tests/micropython/viper_error.py @@ -52,6 +52,7 @@ def f(): # can't do binary op between incompatible types test("@micropython.viper\ndef f(): 1 + []") +test("@micropython.viper\ndef f(x:int, y:uint): x < y") # can't load test("@micropython.viper\ndef f(): 1[0]") @@ -73,6 +74,8 @@ def f(): test("@micropython.viper\ndef f(x:int): ~x") # binary op not implemented +test("@micropython.viper\ndef f(x:uint, y:uint): res = x // y") +test("@micropython.viper\ndef f(x:uint, y:uint): res = x % y") test("@micropython.viper\ndef f(x:int): res = x in x") # yield (from) not implemented diff --git a/tests/micropython/viper_error.py.exp b/tests/micropython/viper_error.py.exp index da9a0ca93ee34..31c85b1d872af 100644 --- a/tests/micropython/viper_error.py.exp +++ b/tests/micropython/viper_error.py.exp @@ -6,6 +6,7 @@ ViperTypeError("local 'x' has type 'int' but source is 'object'",) ViperTypeError("can't implicitly convert 'ptr' to 'bool'",) ViperTypeError("return expected 'int' but got 'object'",) ViperTypeError("can't do binary op between 'int' and 'object'",) +ViperTypeError('comparison of int and uint',) ViperTypeError("can't load from 'int'",) ViperTypeError("can't load from 'int'",) ViperTypeError("can't store to 'int'",) @@ -17,6 +18,8 @@ ViperTypeError('must raise an object',) ViperTypeError('unary op __pos__ not implemented',) ViperTypeError('unary op __neg__ not implemented',) ViperTypeError('unary op __invert__ not implemented',) +ViperTypeError('div/mod not implemented for uint',) +ViperTypeError('div/mod not implemented for uint',) ViperTypeError('binary op not implemented',) NotImplementedError('native yield',) NotImplementedError('native yield',) diff --git a/tests/micropython/viper_misc_intbig.py b/tests/micropython/viper_misc_intbig.py index 055c08d8e53dc..eda873ca63dee 100644 --- a/tests/micropython/viper_misc_intbig.py +++ b/tests/micropython/viper_misc_intbig.py @@ -6,6 +6,6 @@ def viper_uint() -> uint: return uint(-1) -import sys +import usys -print(viper_uint() == (sys.maxsize << 1 | 1)) +print(viper_uint() == (usys.maxsize << 1 | 1)) diff --git a/tests/misc/print_exception.py b/tests/misc/print_exception.py index f26c1fd5ac867..9b3467b0a0f84 100644 --- a/tests/misc/print_exception.py +++ b/tests/misc/print_exception.py @@ -1,10 +1,10 @@ -import sys - try: try: import uio as io + import usys as sys except ImportError: import io + import sys except ImportError: print("SKIP") raise SystemExit diff --git a/tests/misc/sys_atexit.py b/tests/misc/sys_atexit.py index e9c5693f975e3..141b24cc9f62b 100644 --- a/tests/misc/sys_atexit.py +++ b/tests/misc/sys_atexit.py @@ -1,9 +1,9 @@ # test sys.atexit() function -import sys +import usys try: - sys.atexit + usys.atexit except AttributeError: print("SKIP") raise SystemExit @@ -15,7 +15,7 @@ def do_at_exit(): print("done at exit:", some_var) -sys.atexit(do_at_exit) +usys.atexit(do_at_exit) some_var = "ok" print("done before exit") diff --git a/tests/misc/sys_exc_info.py b/tests/misc/sys_exc_info.py index d7e8a2d943b5e..3a8c4a6c8d3ed 100644 --- a/tests/misc/sys_exc_info.py +++ b/tests/misc/sys_exc_info.py @@ -1,4 +1,7 @@ -import sys +try: + import usys as sys +except ImportError: + import sys try: sys.exc_info diff --git a/tests/multi_bluetooth/ble_characteristic.py b/tests/multi_bluetooth/ble_characteristic.py index b5dfefc840dd6..026b9d551c2b9 100644 --- a/tests/multi_bluetooth/ble_characteristic.py +++ b/tests/multi_bluetooth/ble_characteristic.py @@ -5,21 +5,25 @@ TIMEOUT_MS = 5000 -_IRQ_CENTRAL_CONNECT = const(1 << 0) -_IRQ_CENTRAL_DISCONNECT = const(1 << 1) -_IRQ_GATTS_WRITE = const(1 << 2) -_IRQ_PERIPHERAL_CONNECT = const(1 << 6) -_IRQ_PERIPHERAL_DISCONNECT = const(1 << 7) -_IRQ_GATTC_CHARACTERISTIC_RESULT = const(1 << 9) -_IRQ_GATTC_READ_RESULT = const(1 << 11) -_IRQ_GATTC_WRITE_STATUS = const(1 << 12) -_IRQ_GATTC_NOTIFY = const(1 << 13) +_IRQ_CENTRAL_CONNECT = const(1) +_IRQ_CENTRAL_DISCONNECT = const(2) +_IRQ_GATTS_WRITE = const(3) +_IRQ_PERIPHERAL_CONNECT = const(7) +_IRQ_PERIPHERAL_DISCONNECT = const(8) +_IRQ_GATTC_CHARACTERISTIC_RESULT = const(11) +_IRQ_GATTC_CHARACTERISTIC_DONE = const(12) +_IRQ_GATTC_READ_RESULT = const(15) +_IRQ_GATTC_READ_DONE = const(16) +_IRQ_GATTC_WRITE_DONE = const(17) +_IRQ_GATTC_NOTIFY = const(18) +_IRQ_GATTC_INDICATE = const(19) +_IRQ_GATTS_INDICATE_DONE = const(20) SERVICE_UUID = bluetooth.UUID("A5A5A5A5-FFFF-9999-1111-5A5A5A5A5A5A") CHAR_UUID = bluetooth.UUID("00000000-1111-2222-3333-444444444444") CHAR = ( CHAR_UUID, - bluetooth.FLAG_READ | bluetooth.FLAG_WRITE | bluetooth.FLAG_NOTIFY, + bluetooth.FLAG_READ | bluetooth.FLAG_WRITE | bluetooth.FLAG_NOTIFY | bluetooth.FLAG_INDICATE, ) SERVICE = ( SERVICE_UUID, @@ -27,15 +31,13 @@ ) SERVICES = (SERVICE,) -last_event = None -last_data = None +waiting_event = None +waiting_data = None value_handle = 0 def irq(event, data): - global last_event, last_data, value_handle - last_event = event - last_data = data + global waiting_event, waiting_data, value_handle if event == _IRQ_CENTRAL_CONNECT: print("_IRQ_CENTRAL_CONNECT") elif event == _IRQ_CENTRAL_DISCONNECT: @@ -52,22 +54,37 @@ def irq(event, data): print("_IRQ_GATTC_CHARACTERISTIC_RESULT", data[-1]) value_handle = data[2] elif event == _IRQ_GATTC_READ_RESULT: - print("_IRQ_GATTC_READ_RESULT", data[-1]) - elif event == _IRQ_GATTC_WRITE_STATUS: - print("_IRQ_GATTC_WRITE_STATUS", data[-1]) + print("_IRQ_GATTC_READ_RESULT", bytes(data[-1])) + elif event == _IRQ_GATTC_READ_DONE: + print("_IRQ_GATTC_READ_DONE", data[-1]) + elif event == _IRQ_GATTC_WRITE_DONE: + print("_IRQ_GATTC_WRITE_DONE", data[-1]) elif event == _IRQ_GATTC_NOTIFY: - print("_IRQ_GATTC_NOTIFY", data[-1]) + print("_IRQ_GATTC_NOTIFY", bytes(data[-1])) + elif event == _IRQ_GATTC_INDICATE: + print("_IRQ_GATTC_INDICATE", bytes(data[-1])) + elif event == _IRQ_GATTS_INDICATE_DONE: + print("_IRQ_GATTS_INDICATE_DONE", data[-1]) + + if waiting_event is not None: + if (isinstance(waiting_event, int) and event == waiting_event) or ( + not isinstance(waiting_event, int) and waiting_event(event, data) + ): + waiting_event = None + waiting_data = data def wait_for_event(event, timeout_ms): + global waiting_event, waiting_data + waiting_event = event + waiting_data = None + t0 = time.ticks_ms() while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: - if isinstance(event, int): - if last_event == event: - break - elif event(): - break + if waiting_data: + return True machine.idle() + return False # Acting in peripheral role. @@ -82,17 +99,18 @@ def instance0(): ble.gatts_write(char_handle, "periph0") # Wait for central to connect to us. - wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS) - if last_event != _IRQ_CENTRAL_CONNECT: + if not wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS): return - conn_handle, _, _ = last_data + conn_handle, _, _ = waiting_data # Wait for a write to the characteristic from the central. wait_for_event(_IRQ_GATTS_WRITE, TIMEOUT_MS) # Wait a bit, then write the characteristic and notify it. time.sleep_ms(1000) + print("gatts_write") ble.gatts_write(char_handle, "periph1") + print("gatts_notify") ble.gatts_notify(conn_handle, char_handle) # Wait for a write to the characteristic from the central. @@ -100,8 +118,22 @@ def instance0(): # Wait a bit, then notify a new value on the characteristic. time.sleep_ms(1000) + print("gatts_notify") ble.gatts_notify(conn_handle, char_handle, "periph2") + # Wait for a write to the characteristic from the central. + wait_for_event(_IRQ_GATTS_WRITE, TIMEOUT_MS) + + # Wait a bit, then notify a new value on the characteristic. + time.sleep_ms(1000) + print("gatts_write") + ble.gatts_write(char_handle, "periph3") + print("gatts_indicate") + ble.gatts_indicate(conn_handle, char_handle) + + # Wait for the indicate ack. + wait_for_event(_IRQ_GATTS_INDICATE_DONE, TIMEOUT_MS) + # Wait for the central to disconnect. wait_for_event(_IRQ_CENTRAL_DISCONNECT, TIMEOUT_MS) finally: @@ -114,15 +146,14 @@ def instance1(): try: # Connect to peripheral and then disconnect. print("gap_connect") - ble.gap_connect(0, BDADDR) - wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS) - if last_event != _IRQ_PERIPHERAL_CONNECT: + ble.gap_connect(*BDADDR) + if not wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS): return - conn_handle, _, _ = last_data + conn_handle, _, _ = waiting_data # Discover characteristics. ble.gattc_discover_characteristics(conn_handle, 1, 65535) - wait_for_event(lambda: value_handle, TIMEOUT_MS) + wait_for_event(lambda event, data: value_handle, TIMEOUT_MS) # Issue read of characteristic, should get initial value. print("gattc_read") @@ -132,7 +163,7 @@ def instance1(): # Write to the characteristic, and ask for a response. print("gattc_write") ble.gattc_write(conn_handle, value_handle, "central0", 1) - wait_for_event(_IRQ_GATTC_WRITE_STATUS, TIMEOUT_MS) + wait_for_event(_IRQ_GATTC_WRITE_DONE, TIMEOUT_MS) # Wait for a notify, then read new value. wait_for_event(_IRQ_GATTC_NOTIFY, TIMEOUT_MS) @@ -143,7 +174,7 @@ def instance1(): # Write to the characteristic, and ask for a response. print("gattc_write") ble.gattc_write(conn_handle, value_handle, "central1", 1) - wait_for_event(_IRQ_GATTC_WRITE_STATUS, TIMEOUT_MS) + wait_for_event(_IRQ_GATTC_WRITE_DONE, TIMEOUT_MS) # Wait for a notify (should have new data), then read old value (should be unchanged). wait_for_event(_IRQ_GATTC_NOTIFY, TIMEOUT_MS) @@ -151,6 +182,17 @@ def instance1(): ble.gattc_read(conn_handle, value_handle) wait_for_event(_IRQ_GATTC_READ_RESULT, TIMEOUT_MS) + # Write to the characteristic, and ask for a response. + print("gattc_write") + ble.gattc_write(conn_handle, value_handle, "central2", 1) + wait_for_event(_IRQ_GATTC_WRITE_DONE, TIMEOUT_MS) + + # Wait for a indicate (should have new data), then read new value. + wait_for_event(_IRQ_GATTC_INDICATE, TIMEOUT_MS) + print("gattc_read") + ble.gattc_read(conn_handle, value_handle) + wait_for_event(_IRQ_GATTC_READ_RESULT, TIMEOUT_MS) + # Disconnect from peripheral. print("gap_disconnect:", ble.gap_disconnect(conn_handle)) wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, TIMEOUT_MS) diff --git a/tests/multi_bluetooth/ble_characteristic.py.exp b/tests/multi_bluetooth/ble_characteristic.py.exp index 1532574d9882c..90dc46c06fd57 100644 --- a/tests/multi_bluetooth/ble_characteristic.py.exp +++ b/tests/multi_bluetooth/ble_characteristic.py.exp @@ -2,23 +2,39 @@ gap_advertise _IRQ_CENTRAL_CONNECT _IRQ_GATTS_WRITE b'central0' +gatts_write +gatts_notify _IRQ_GATTS_WRITE b'central1' +gatts_notify +_IRQ_GATTS_WRITE b'central2' +gatts_write +gatts_indicate +_IRQ_GATTS_INDICATE_DONE 0 _IRQ_CENTRAL_DISCONNECT --- instance1 --- gap_connect _IRQ_PERIPHERAL_CONNECT -_IRQ_GATTC_CHARACTERISTIC_RESULT UUID128('00000000-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000000-1111-2222-3333-444444444444') gattc_read _IRQ_GATTC_READ_RESULT b'periph0' +_IRQ_GATTC_READ_DONE 0 gattc_write -_IRQ_GATTC_WRITE_STATUS 0 +_IRQ_GATTC_WRITE_DONE 0 _IRQ_GATTC_NOTIFY b'periph1' gattc_read _IRQ_GATTC_READ_RESULT b'periph1' +_IRQ_GATTC_READ_DONE 0 gattc_write -_IRQ_GATTC_WRITE_STATUS 0 +_IRQ_GATTC_WRITE_DONE 0 _IRQ_GATTC_NOTIFY b'periph2' gattc_read _IRQ_GATTC_READ_RESULT b'central1' +_IRQ_GATTC_READ_DONE 0 +gattc_write +_IRQ_GATTC_WRITE_DONE 0 +_IRQ_GATTC_INDICATE b'periph3' +gattc_read +_IRQ_GATTC_READ_RESULT b'periph3' +_IRQ_GATTC_READ_DONE 0 gap_disconnect: True _IRQ_PERIPHERAL_DISCONNECT diff --git a/tests/multi_bluetooth/ble_gap_advertise.py b/tests/multi_bluetooth/ble_gap_advertise.py index 644b1fe28d3e8..bb6ff8e425461 100644 --- a/tests/multi_bluetooth/ble_gap_advertise.py +++ b/tests/multi_bluetooth/ble_gap_advertise.py @@ -3,8 +3,8 @@ from micropython import const import time, machine, bluetooth -_IRQ_SCAN_RESULT = const(1 << 4) -_IRQ_SCAN_COMPLETE = const(1 << 5) +_IRQ_SCAN_RESULT = const(5) +_IRQ_SCAN_DONE = const(6) ADV_TIME_S = 3 @@ -36,14 +36,14 @@ def instance1(): def irq(ev, data): nonlocal finished, adv_types, adv_data if ev == _IRQ_SCAN_RESULT: - if data[1] == BDADDR: + if data[0] == BDADDR[0] and data[1] == BDADDR[1]: adv_types.add(data[2]) if adv_data is None: adv_data = bytes(data[4]) else: if adv_data != data[4]: adv_data = b"MISMATCH" - elif ev == _IRQ_SCAN_COMPLETE: + elif ev == _IRQ_SCAN_DONE: finished = True ble.config(rxbuf=2000) diff --git a/tests/multi_bluetooth/ble_gap_connect.py b/tests/multi_bluetooth/ble_gap_connect.py index def256f2c8f51..8b40a29163119 100644 --- a/tests/multi_bluetooth/ble_gap_connect.py +++ b/tests/multi_bluetooth/ble_gap_connect.py @@ -5,86 +5,106 @@ TIMEOUT_MS = 4000 -_IRQ_CENTRAL_CONNECT = const(1 << 0) -_IRQ_CENTRAL_DISCONNECT = const(1 << 1) -_IRQ_PERIPHERAL_CONNECT = const(1 << 6) -_IRQ_PERIPHERAL_DISCONNECT = const(1 << 7) +_IRQ_CENTRAL_CONNECT = const(1) +_IRQ_CENTRAL_DISCONNECT = const(2) +_IRQ_PERIPHERAL_CONNECT = const(7) +_IRQ_PERIPHERAL_DISCONNECT = const(8) -last_event = None -last_data = None +central_connected = False +central_disconnected = False +peripheral_connected = False +peripheral_disconnected = False +conn_handle = None def irq(event, data): - global last_event, last_data - last_event = event - last_data = data + global central_connected, central_disconnected, peripheral_connected, peripheral_disconnected, conn_handle if event == _IRQ_CENTRAL_CONNECT: print("_IRQ_CENTRAL_CONNECT") + central_connected = True + conn_handle = data[0] elif event == _IRQ_CENTRAL_DISCONNECT: print("_IRQ_CENTRAL_DISCONNECT") + central_disconnected = True elif event == _IRQ_PERIPHERAL_CONNECT: print("_IRQ_PERIPHERAL_CONNECT") + peripheral_connected = True + conn_handle = data[0] elif event == _IRQ_PERIPHERAL_DISCONNECT: print("_IRQ_PERIPHERAL_DISCONNECT") + peripheral_disconnected = True + remote_addr = data[0] -def wait_for_event(event, timeout_ms): +def wait_for(fn, timeout_ms): t0 = time.ticks_ms() - while last_event != event and time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: + while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: + if fn(): + return True machine.idle() + return False # Acting in peripheral role. def instance0(): + global central_connected, central_disconnected + multitest.globals(BDADDR=ble.config("mac")) print("gap_advertise") ble.gap_advertise(20_000, b"\x02\x01\x06\x04\xffMPY") multitest.next() try: # Wait for central to connect, then wait for it to disconnect. - wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS) - wait_for_event(_IRQ_CENTRAL_DISCONNECT, TIMEOUT_MS) - if last_event != _IRQ_CENTRAL_DISCONNECT: + if not wait_for(lambda: central_connected, TIMEOUT_MS): + return + if not wait_for(lambda: central_disconnected, TIMEOUT_MS): return + central_connected = False + central_disconnected = False + # Start advertising again. + print("gap_advertise") ble.gap_advertise(20_000, b"\x02\x01\x06\x04\xffMPY") # Wait for central to connect, then disconnect it. - wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS) - if last_event != _IRQ_CENTRAL_CONNECT: + if not wait_for(lambda: central_connected, TIMEOUT_MS): + return + print("gap_disconnect:", ble.gap_disconnect(conn_handle)) + if not wait_for(lambda: central_disconnected, TIMEOUT_MS): return - print("gap_disconnect:", ble.gap_disconnect(last_data[0])) - wait_for_event(_IRQ_CENTRAL_DISCONNECT, TIMEOUT_MS) finally: ble.active(0) # Acting in central role. def instance1(): + global peripheral_connected, peripheral_disconnected + multitest.next() try: # Connect to peripheral and then disconnect. print("gap_connect") - ble.gap_connect(0, BDADDR) - wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS) - if last_event != _IRQ_PERIPHERAL_CONNECT: + ble.gap_connect(*BDADDR) + if not wait_for(lambda: peripheral_connected, TIMEOUT_MS): return - print("gap_disconnect:", ble.gap_disconnect(last_data[0])) - wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, TIMEOUT_MS) - if last_event != _IRQ_PERIPHERAL_DISCONNECT: + print("gap_disconnect:", ble.gap_disconnect(conn_handle)) + if not wait_for(lambda: peripheral_disconnected, TIMEOUT_MS): return + peripheral_connected = False + peripheral_disconnected = False + # Wait for peripheral to start advertising again. time.sleep_ms(100) # Connect to peripheral and then let the peripheral disconnect us. print("gap_connect") - ble.gap_connect(0, BDADDR) - wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS) - if last_event != _IRQ_PERIPHERAL_CONNECT: + ble.gap_connect(*BDADDR) + if not wait_for(lambda: peripheral_connected, TIMEOUT_MS): + return + if not wait_for(lambda: peripheral_disconnected, TIMEOUT_MS): return - wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, TIMEOUT_MS) finally: ble.active(0) diff --git a/tests/multi_bluetooth/ble_gap_connect.py.exp b/tests/multi_bluetooth/ble_gap_connect.py.exp index 1c96f1d48ac07..d0dc020703214 100644 --- a/tests/multi_bluetooth/ble_gap_connect.py.exp +++ b/tests/multi_bluetooth/ble_gap_connect.py.exp @@ -2,6 +2,7 @@ gap_advertise _IRQ_CENTRAL_CONNECT _IRQ_CENTRAL_DISCONNECT +gap_advertise _IRQ_CENTRAL_CONNECT gap_disconnect: True _IRQ_CENTRAL_DISCONNECT diff --git a/tests/multi_bluetooth/ble_gap_device_name.py b/tests/multi_bluetooth/ble_gap_device_name.py index ee4a7a544042c..fbc9d80baeb08 100644 --- a/tests/multi_bluetooth/ble_gap_device_name.py +++ b/tests/multi_bluetooth/ble_gap_device_name.py @@ -5,24 +5,24 @@ TIMEOUT_MS = 5000 -_IRQ_CENTRAL_CONNECT = const(1 << 0) -_IRQ_CENTRAL_DISCONNECT = const(1 << 1) -_IRQ_PERIPHERAL_CONNECT = const(1 << 6) -_IRQ_PERIPHERAL_DISCONNECT = const(1 << 7) -_IRQ_GATTC_CHARACTERISTIC_RESULT = const(1 << 9) -_IRQ_GATTC_READ_RESULT = const(1 << 11) +_IRQ_CENTRAL_CONNECT = const(1) +_IRQ_CENTRAL_DISCONNECT = const(2) +_IRQ_PERIPHERAL_CONNECT = const(7) +_IRQ_PERIPHERAL_DISCONNECT = const(8) +_IRQ_GATTC_CHARACTERISTIC_RESULT = const(11) +_IRQ_GATTC_CHARACTERISTIC_DONE = const(12) +_IRQ_GATTC_READ_RESULT = const(15) +_IRQ_GATTC_READ_DONE = const(16) GAP_DEVICE_NAME_UUID = bluetooth.UUID(0x2A00) -last_event = None -last_data = None +waiting_event = None +waiting_data = None value_handle = 0 def irq(event, data): - global last_event, last_data, value_handle - last_event = event - last_data = data + global waiting_event, waiting_data, value_handle if event == _IRQ_CENTRAL_CONNECT: print("_IRQ_CENTRAL_CONNECT") elif event == _IRQ_CENTRAL_DISCONNECT: @@ -36,18 +36,25 @@ def irq(event, data): print("_IRQ_GATTC_CHARACTERISTIC_RESULT", data[-1]) value_handle = data[2] elif event == _IRQ_GATTC_READ_RESULT: - print("_IRQ_GATTC_READ_RESULT", data[-1]) + print("_IRQ_GATTC_READ_RESULT", bytes(data[-1])) + + if waiting_event is not None: + if isinstance(waiting_event, int) and event == waiting_event: + waiting_event = None + waiting_data = data def wait_for_event(event, timeout_ms): + global waiting_event, waiting_data + waiting_event = event + waiting_data = None + t0 = time.ticks_ms() while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: - if isinstance(event, int): - if last_event == event: - break - elif event(): - break + if waiting_data: + return True machine.idle() + return False # Acting in peripheral role. @@ -72,9 +79,9 @@ def instance0(): ble.gap_advertise(20_000) # Wait for central to connect, then wait for it to disconnect. - wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS) - wait_for_event(_IRQ_CENTRAL_DISCONNECT, 4 * TIMEOUT_MS) - if last_event != _IRQ_CENTRAL_DISCONNECT: + if not wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS): + return + if not wait_for_event(_IRQ_CENTRAL_DISCONNECT, 4 * TIMEOUT_MS): return finally: ble.active(0) @@ -90,11 +97,10 @@ def instance1(): # Connect to peripheral. print("gap_connect") - ble.gap_connect(0, BDADDR) - wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS) - if last_event != _IRQ_PERIPHERAL_CONNECT: + ble.gap_connect(*BDADDR) + if not wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS): return - conn_handle, _, _ = last_data + conn_handle, _, _ = waiting_data if iteration == 0: print("gattc_discover_characteristics") @@ -111,8 +117,7 @@ def instance1(): # Disconnect from peripheral. print("gap_disconnect:", ble.gap_disconnect(conn_handle)) - wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, TIMEOUT_MS) - if last_event != _IRQ_PERIPHERAL_DISCONNECT: + if not wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, TIMEOUT_MS): return finally: ble.active(0) diff --git a/tests/multi_bluetooth/ble_gap_device_name.py.exp b/tests/multi_bluetooth/ble_gap_device_name.py.exp index 377df90dee356..0f4ee6e3dc576 100644 --- a/tests/multi_bluetooth/ble_gap_device_name.py.exp +++ b/tests/multi_bluetooth/ble_gap_device_name.py.exp @@ -11,7 +11,7 @@ _IRQ_CENTRAL_DISCONNECT gap_connect _IRQ_PERIPHERAL_CONNECT gattc_discover_characteristics -_IRQ_GATTC_CHARACTERISTIC_RESULT UUID16(0x2a00) +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID(0x2a00) gattc_read _IRQ_GATTC_READ_RESULT b'GAP_NAME0' gap_disconnect: True diff --git a/tests/multi_bluetooth/ble_gatt_data_transfer.py b/tests/multi_bluetooth/ble_gatt_data_transfer.py index 1be557f9b938a..944c9e2d2a55f 100644 --- a/tests/multi_bluetooth/ble_gatt_data_transfer.py +++ b/tests/multi_bluetooth/ble_gatt_data_transfer.py @@ -5,16 +5,19 @@ TIMEOUT_MS = 5000 -_IRQ_CENTRAL_CONNECT = const(1 << 0) -_IRQ_CENTRAL_DISCONNECT = const(1 << 1) -_IRQ_GATTS_WRITE = const(1 << 2) -_IRQ_PERIPHERAL_CONNECT = const(1 << 6) -_IRQ_PERIPHERAL_DISCONNECT = const(1 << 7) -_IRQ_GATTC_SERVICE_RESULT = const(1 << 8) -_IRQ_GATTC_CHARACTERISTIC_RESULT = const(1 << 9) -_IRQ_GATTC_READ_RESULT = const(1 << 11) -_IRQ_GATTC_WRITE_STATUS = const(1 << 12) -_IRQ_GATTC_NOTIFY = const(1 << 13) +_IRQ_CENTRAL_CONNECT = const(1) +_IRQ_CENTRAL_DISCONNECT = const(2) +_IRQ_GATTS_WRITE = const(3) +_IRQ_PERIPHERAL_CONNECT = const(7) +_IRQ_PERIPHERAL_DISCONNECT = const(8) +_IRQ_GATTC_SERVICE_RESULT = const(9) +_IRQ_GATTC_SERVICE_DONE = const(10) +_IRQ_GATTC_CHARACTERISTIC_RESULT = const(11) +_IRQ_GATTC_CHARACTERISTIC_DONE = const(12) +_IRQ_GATTC_READ_RESULT = const(15) +_IRQ_GATTC_READ_DONE = const(16) +_IRQ_GATTC_WRITE_DONE = const(17) +_IRQ_GATTC_NOTIFY = const(18) SERVICE_UUID = bluetooth.UUID("00000001-1111-2222-3333-444444444444") CHAR_CTRL_UUID = bluetooth.UUID("00000002-1111-2222-3333-444444444444") @@ -26,17 +29,15 @@ SERVICE = (SERVICE_UUID, (CHAR_CTRL, CHAR_RX, CHAR_TX)) SERVICES = (SERVICE,) -last_event = None -last_data = None +waiting_event = None +waiting_data = None ctrl_value_handle = 0 rx_value_handle = 0 tx_value_handle = 0 def irq(event, data): - global last_event, last_data, ctrl_value_handle, rx_value_handle, tx_value_handle - last_event = event - last_data = data + global waiting_event, waiting_data, ctrl_value_handle, rx_value_handle, tx_value_handle if event == _IRQ_CENTRAL_CONNECT: print("_IRQ_CENTRAL_CONNECT") elif event == _IRQ_CENTRAL_DISCONNECT: @@ -49,31 +50,44 @@ def irq(event, data): print("_IRQ_PERIPHERAL_DISCONNECT") elif event == _IRQ_GATTC_CHARACTERISTIC_RESULT: if data[-1] == CHAR_CTRL_UUID: - print("_IRQ_GATTC_SERVICE_RESULT", data[-1]) + print("_IRQ_GATTC_CHARACTERISTIC_RESULT", data[-1]) ctrl_value_handle = data[2] elif data[-1] == CHAR_RX_UUID: - print("_IRQ_GATTC_SERVICE_RESULT", data[-1]) + print("_IRQ_GATTC_CHARACTERISTIC_RESULT", data[-1]) rx_value_handle = data[2] elif data[-1] == CHAR_TX_UUID: - print("_IRQ_GATTC_SERVICE_RESULT", data[-1]) + print("_IRQ_GATTC_CHARACTERISTIC_RESULT", data[-1]) tx_value_handle = data[2] + elif event == _IRQ_GATTC_CHARACTERISTIC_DONE: + print("_IRQ_GATTC_CHARACTERISTIC_DONE") elif event == _IRQ_GATTC_READ_RESULT: print("_IRQ_GATTC_READ_RESULT", data[-1]) - elif event == _IRQ_GATTC_WRITE_STATUS: - print("_IRQ_GATTC_WRITE_STATUS", data[-1]) + elif event == _IRQ_GATTC_READ_DONE: + print("_IRQ_GATTC_READ_DONE", data[-1]) + elif event == _IRQ_GATTC_WRITE_DONE: + print("_IRQ_GATTC_WRITE_DONE", data[-1]) elif event == _IRQ_GATTC_NOTIFY: - print("_IRQ_GATTC_NOTIFY", data[-1]) + print("_IRQ_GATTC_NOTIFY", bytes(data[-1])) + + if waiting_event is not None: + if (isinstance(waiting_event, int) and event == waiting_event) or ( + not isinstance(waiting_event, int) and waiting_event(event, data) + ): + waiting_event = None + waiting_data = data def wait_for_event(event, timeout_ms): + global waiting_event, waiting_data + waiting_event = event + waiting_data = None + t0 = time.ticks_ms() while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: - if isinstance(event, int): - if last_event == event: - break - elif event(): - break + if waiting_data: + return True machine.idle() + return False # Acting in peripheral role. @@ -89,14 +103,13 @@ def instance0(): multitest.next() try: # Wait for central to connect to us. - wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS) - if last_event != _IRQ_CENTRAL_CONNECT: + if not wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS): return - conn_handle, _, _ = last_data + conn_handle, _, _ = waiting_data # Wait for the central to signal that it's done with its part of the test. wait_for_event( - lambda: last_event == _IRQ_GATTS_WRITE and last_data[1] == char_ctrl_handle, + lambda event, data: event == _IRQ_GATTS_WRITE and data[1] == char_ctrl_handle, 2 * TIMEOUT_MS, ) @@ -124,33 +137,34 @@ def instance1(): try: # Connect to peripheral and then disconnect. print("gap_connect") - ble.gap_connect(0, BDADDR) - wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS) - if last_event != _IRQ_PERIPHERAL_CONNECT: + ble.gap_connect(*BDADDR) + if not wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS): return - conn_handle, _, _ = last_data + conn_handle, _, _ = waiting_data # Discover characteristics. ble.gattc_discover_characteristics(conn_handle, 1, 65535) wait_for_event( - lambda: ctrl_value_handle and rx_value_handle and tx_value_handle, TIMEOUT_MS + lambda event, data: ctrl_value_handle and rx_value_handle and tx_value_handle, + TIMEOUT_MS, ) + wait_for_event(_IRQ_GATTC_CHARACTERISTIC_DONE, TIMEOUT_MS) # Write to the characteristic a few times, with and without response. for i in range(4): print("gattc_write") ble.gattc_write(conn_handle, rx_value_handle, "central{}".format(i), i & 1) if i & 1: - wait_for_event(_IRQ_GATTC_WRITE_STATUS, TIMEOUT_MS) + wait_for_event(_IRQ_GATTC_WRITE_DONE, TIMEOUT_MS) time.sleep_ms(400) # Write to say that we are done with our part of the test. ble.gattc_write(conn_handle, ctrl_value_handle, "OK", 1) - wait_for_event(_IRQ_GATTC_WRITE_STATUS, TIMEOUT_MS) + wait_for_event(_IRQ_GATTC_WRITE_DONE, TIMEOUT_MS) # Wait for notification that peripheral is done with its part of the test. wait_for_event( - lambda: last_event == _IRQ_GATTC_NOTIFY and last_data[1] == ctrl_value_handle, + lambda event, data: event == _IRQ_GATTC_NOTIFY and data[1] == ctrl_value_handle, TIMEOUT_MS, ) diff --git a/tests/multi_bluetooth/ble_gatt_data_transfer.py.exp b/tests/multi_bluetooth/ble_gatt_data_transfer.py.exp index 6ecd48225f5fe..94a0718108c1e 100644 --- a/tests/multi_bluetooth/ble_gatt_data_transfer.py.exp +++ b/tests/multi_bluetooth/ble_gatt_data_transfer.py.exp @@ -11,16 +11,17 @@ _IRQ_CENTRAL_DISCONNECT --- instance1 --- gap_connect _IRQ_PERIPHERAL_CONNECT -_IRQ_GATTC_SERVICE_RESULT UUID128('00000002-1111-2222-3333-444444444444') -_IRQ_GATTC_SERVICE_RESULT UUID128('00000003-1111-2222-3333-444444444444') -_IRQ_GATTC_SERVICE_RESULT UUID128('00000004-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000002-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000003-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000004-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_DONE gattc_write gattc_write -_IRQ_GATTC_WRITE_STATUS 0 +_IRQ_GATTC_WRITE_DONE 0 gattc_write gattc_write -_IRQ_GATTC_WRITE_STATUS 0 -_IRQ_GATTC_WRITE_STATUS 0 +_IRQ_GATTC_WRITE_DONE 0 +_IRQ_GATTC_WRITE_DONE 0 _IRQ_GATTC_NOTIFY b'message0' _IRQ_GATTC_NOTIFY b'message1' _IRQ_GATTC_NOTIFY b'message2' diff --git a/tests/multi_bluetooth/ble_gattc_discover_services.py b/tests/multi_bluetooth/ble_gattc_discover_services.py index b01a890c72038..57f95bf0125e7 100644 --- a/tests/multi_bluetooth/ble_gattc_discover_services.py +++ b/tests/multi_bluetooth/ble_gattc_discover_services.py @@ -5,11 +5,12 @@ TIMEOUT_MS = 5000 -_IRQ_CENTRAL_CONNECT = const(1 << 0) -_IRQ_CENTRAL_DISCONNECT = const(1 << 1) -_IRQ_PERIPHERAL_CONNECT = const(1 << 6) -_IRQ_PERIPHERAL_DISCONNECT = const(1 << 7) -_IRQ_GATTC_SERVICE_RESULT = const(1 << 8) +_IRQ_CENTRAL_CONNECT = const(1) +_IRQ_CENTRAL_DISCONNECT = const(2) +_IRQ_PERIPHERAL_CONNECT = const(7) +_IRQ_PERIPHERAL_DISCONNECT = const(8) +_IRQ_GATTC_SERVICE_RESULT = const(9) +_IRQ_GATTC_SERVICE_DONE = const(10) UUID_A = bluetooth.UUID(0x180D) UUID_B = bluetooth.UUID("A5A5A5A5-FFFF-9999-1111-5A5A5A5A5A5A") @@ -23,15 +24,13 @@ ) SERVICES = (SERVICE_A, SERVICE_B) -last_event = None -last_data = None +waiting_event = None +waiting_data = None num_service_result = 0 def irq(event, data): - global last_event, last_data, num_service_result - last_event = event - last_data = data + global waiting_event, waiting_data, num_service_result if event == _IRQ_CENTRAL_CONNECT: print("_IRQ_CENTRAL_CONNECT") elif event == _IRQ_CENTRAL_DISCONNECT: @@ -45,16 +44,25 @@ def irq(event, data): print("_IRQ_GATTC_SERVICE_RESULT", data[3]) num_service_result += 1 + if waiting_event is not None: + if (isinstance(waiting_event, int) and event == waiting_event) or ( + not isinstance(waiting_event, int) and waiting_event(event, data) + ): + waiting_event = None + waiting_data = data + def wait_for_event(event, timeout_ms): + global waiting_event, waiting_data + waiting_event = event + waiting_data = None + t0 = time.ticks_ms() while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: - if isinstance(event, int): - if last_event == event: - break - elif event(): - break + if waiting_data: + return True machine.idle() + return False # Acting in peripheral role. @@ -77,15 +85,14 @@ def instance1(): try: # Connect to peripheral and then disconnect. print("gap_connect") - ble.gap_connect(0, BDADDR) - wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS) - if last_event != _IRQ_PERIPHERAL_CONNECT: + ble.gap_connect(*BDADDR) + if not wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS): return - conn_handle, _, _ = last_data + conn_handle, _, _ = waiting_data # Discover services. ble.gattc_discover_services(conn_handle) - wait_for_event(lambda: num_service_result == 2, TIMEOUT_MS) + wait_for_event(lambda event, data: num_service_result == 2, TIMEOUT_MS) # Disconnect from peripheral. print("gap_disconnect:", ble.gap_disconnect(conn_handle)) diff --git a/tests/multi_bluetooth/ble_gattc_discover_services.py.exp b/tests/multi_bluetooth/ble_gattc_discover_services.py.exp index 06c36b249368a..7f4d8da81ad8c 100644 --- a/tests/multi_bluetooth/ble_gattc_discover_services.py.exp +++ b/tests/multi_bluetooth/ble_gattc_discover_services.py.exp @@ -5,7 +5,7 @@ _IRQ_CENTRAL_DISCONNECT --- instance1 --- gap_connect _IRQ_PERIPHERAL_CONNECT -_IRQ_GATTC_SERVICE_RESULT UUID16(0x180d) -_IRQ_GATTC_SERVICE_RESULT UUID128('a5a5a5a5-ffff-9999-1111-5a5a5a5a5a5a') +_IRQ_GATTC_SERVICE_RESULT UUID(0x180d) +_IRQ_GATTC_SERVICE_RESULT UUID('a5a5a5a5-ffff-9999-1111-5a5a5a5a5a5a') gap_disconnect: True _IRQ_PERIPHERAL_DISCONNECT diff --git a/tests/multi_bluetooth/ble_mtu.py b/tests/multi_bluetooth/ble_mtu.py new file mode 100644 index 0000000000000..ef3199c5bbb04 --- /dev/null +++ b/tests/multi_bluetooth/ble_mtu.py @@ -0,0 +1,208 @@ +# Test MTU exchange (initiated by both central and peripheral) and the effect on +# notify and write size. + +# Seven connections are made (four central->peripheral, three peripheral->central). +# +# Test | Requested | Preferred | Result | Notes +# 0 | 300 (C) | 256 (P) | 256 | +# 1 | 300 (C) | 200 (P) | 200 | +# 2 | 300 (C) | 400 (P) | 300 | +# 3 | 300 (C) | 50 (P) | 50 | Shorter than 64 so the notification is truncated. +# 4 | 290 (P) | 256 (C) | 256 | +# 5 | 290 (P) | 190 (C) | 190 | +# 6 | 290 (P) | 350 (C) | 290 | +# +# For each connection a notification is sent by the server (peripheral) and a characteristic +# is written by the client (central) to ensure that the expected size is transmitted. +# +# Note: This currently fails on btstack for two reasons: +# - btstack doesn't truncate writes to the MTU (it fails instead) +# - btstack (in central mode) doesn't handle the peripheral initiating the MTU exchange + +from micropython import const +import time, machine, bluetooth + +TIMEOUT_MS = 5000 + +_IRQ_CENTRAL_CONNECT = const(1) +_IRQ_CENTRAL_DISCONNECT = const(2) +_IRQ_GATTS_WRITE = const(3) +_IRQ_PERIPHERAL_CONNECT = const(7) +_IRQ_PERIPHERAL_DISCONNECT = const(8) +_IRQ_GATTC_CHARACTERISTIC_RESULT = const(11) +_IRQ_GATTC_CHARACTERISTIC_DONE = const(12) +_IRQ_GATTC_WRITE_DONE = const(17) +_IRQ_GATTC_NOTIFY = const(18) +_IRQ_MTU_EXCHANGED = const(21) + +SERVICE_UUID = bluetooth.UUID("A5A5A5A5-FFFF-9999-1111-5A5A5A5A5A5A") +CHAR_UUID = bluetooth.UUID("00000000-1111-2222-3333-444444444444") +CHAR = ( + CHAR_UUID, + bluetooth.FLAG_READ | bluetooth.FLAG_WRITE | bluetooth.FLAG_NOTIFY, +) +SERVICE = ( + SERVICE_UUID, + (CHAR,), +) +SERVICES = (SERVICE,) + +waiting_events = {} + + +def irq(event, data): + global value_handle + if event == _IRQ_CENTRAL_CONNECT: + print("_IRQ_CENTRAL_CONNECT") + waiting_events[event] = data[0] + elif event == _IRQ_CENTRAL_DISCONNECT: + print("_IRQ_CENTRAL_DISCONNECT") + elif event == _IRQ_GATTS_WRITE: + print("_IRQ_GATTS_WRITE") + elif event == _IRQ_PERIPHERAL_CONNECT: + print("_IRQ_PERIPHERAL_CONNECT") + waiting_events[event] = data[0] + elif event == _IRQ_PERIPHERAL_DISCONNECT: + print("_IRQ_PERIPHERAL_DISCONNECT") + elif event == _IRQ_GATTC_CHARACTERISTIC_RESULT: + if data[-1] == CHAR_UUID: + print("_IRQ_GATTC_CHARACTERISTIC_RESULT", data[-1]) + waiting_events[event] = data[2] + elif event == _IRQ_GATTC_CHARACTERISTIC_DONE: + print("_IRQ_GATTC_CHARACTERISTIC_DONE") + elif event == _IRQ_GATTC_WRITE_DONE: + print("_IRQ_GATTC_WRITE_DONE") + elif event == _IRQ_GATTC_NOTIFY: + print("_IRQ_GATTC_NOTIFY", len(data[-1]), chr(data[-1][0])) + elif event == _IRQ_MTU_EXCHANGED: + print("_IRQ_MTU_EXCHANGED", data[-1]) + waiting_events[event] = data[-1] + + if event not in waiting_events: + waiting_events[event] = None + + +def wait_for_event(event, timeout_ms): + t0 = time.ticks_ms() + while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: + if event in waiting_events: + return True + machine.idle() + return False + + +# Acting in peripheral role. +def instance0(): + multitest.globals(BDADDR=ble.config("mac")) + ((char_handle,),) = ble.gatts_register_services(SERVICES) + ble.gatts_set_buffer(char_handle, 500, False) + print("gap_advertise") + ble.gap_advertise(20_000, b"\x02\x01\x06\x04\xffMPY") + multitest.next() + try: + for i in range(7): + waiting_events.clear() + + if i == 1: + ble.config(mtu=200) + elif i == 2: + ble.config(mtu=400) + elif i == 3: + ble.config(mtu=50) + elif i >= 4: + ble.config(mtu=290) + else: + # This is the NimBLE default. + ble.config(mtu=256) + + # Wait for central to connect to us. + if not wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS): + return + conn_handle = waiting_events[_IRQ_CENTRAL_CONNECT] + + if i >= 4: + print("gattc_exchange_mtu") + ble.gattc_exchange_mtu(conn_handle) + + if not wait_for_event(_IRQ_MTU_EXCHANGED, TIMEOUT_MS): + return + mtu = waiting_events[_IRQ_MTU_EXCHANGED] + + print("gatts_notify") + ble.gatts_notify(conn_handle, char_handle, str(i) * 64) + + if not wait_for_event(_IRQ_GATTS_WRITE, TIMEOUT_MS): + return + + print("gatts_read") + data = ble.gatts_read(char_handle) + print("characteristic len:", len(data), chr(data[0])) + + # Wait for the central to disconnect. + if not wait_for_event(_IRQ_CENTRAL_DISCONNECT, TIMEOUT_MS): + return + + print("gap_advertise") + ble.gap_advertise(20_000, b"\x02\x01\x06\x04\xffMPY") + + finally: + ble.active(0) + + +# Acting in central role. +def instance1(): + multitest.next() + try: + for i in range(7): + waiting_events.clear() + + if i < 4: + ble.config(mtu=300) + elif i == 5: + ble.config(mtu=190) + elif i == 6: + ble.config(mtu=350) + else: + ble.config(mtu=256) + + # Connect to peripheral and then disconnect. + print("gap_connect") + ble.gap_connect(*BDADDR) + if not wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS): + return + conn_handle = waiting_events[_IRQ_PERIPHERAL_CONNECT] + + if i < 4: + print("gattc_exchange_mtu") + ble.gattc_exchange_mtu(conn_handle) + + if not wait_for_event(_IRQ_MTU_EXCHANGED, TIMEOUT_MS): + return + mtu = waiting_events[_IRQ_MTU_EXCHANGED] + + if not wait_for_event(_IRQ_GATTC_NOTIFY, TIMEOUT_MS): + return + + print("gattc_discover_characteristics") + ble.gattc_discover_characteristics(conn_handle, 1, 65535) + if not wait_for_event(_IRQ_GATTC_CHARACTERISTIC_DONE, TIMEOUT_MS): + return + value_handle = waiting_events[_IRQ_GATTC_CHARACTERISTIC_RESULT] + + # Write 20 more than the MTU to test truncation. + print("gattc_write") + ble.gattc_write(conn_handle, value_handle, chr(ord("a") + i) * (mtu + 20), 1) + if not wait_for_event(_IRQ_GATTC_WRITE_DONE, TIMEOUT_MS): + return + + # Disconnect from peripheral. + print("gap_disconnect:", ble.gap_disconnect(conn_handle)) + if not wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, TIMEOUT_MS): + return + finally: + ble.active(0) + + +ble = bluetooth.BLE() +ble.active(1) +ble.irq(irq) diff --git a/tests/multi_bluetooth/ble_mtu.py.exp b/tests/multi_bluetooth/ble_mtu.py.exp new file mode 100644 index 0000000000000..1039a5da13c22 --- /dev/null +++ b/tests/multi_bluetooth/ble_mtu.py.exp @@ -0,0 +1,143 @@ +--- instance0 --- +gap_advertise +_IRQ_CENTRAL_CONNECT +_IRQ_MTU_EXCHANGED 256 +gatts_notify +_IRQ_GATTS_WRITE +gatts_read +characteristic len: 253 a +_IRQ_CENTRAL_DISCONNECT +gap_advertise +_IRQ_CENTRAL_CONNECT +_IRQ_MTU_EXCHANGED 200 +gatts_notify +_IRQ_GATTS_WRITE +gatts_read +characteristic len: 197 b +_IRQ_CENTRAL_DISCONNECT +gap_advertise +_IRQ_CENTRAL_CONNECT +_IRQ_MTU_EXCHANGED 300 +gatts_notify +_IRQ_GATTS_WRITE +gatts_read +characteristic len: 297 c +_IRQ_CENTRAL_DISCONNECT +gap_advertise +_IRQ_CENTRAL_CONNECT +_IRQ_MTU_EXCHANGED 50 +gatts_notify +_IRQ_GATTS_WRITE +gatts_read +characteristic len: 47 d +_IRQ_CENTRAL_DISCONNECT +gap_advertise +_IRQ_CENTRAL_CONNECT +gattc_exchange_mtu +_IRQ_MTU_EXCHANGED 256 +gatts_notify +_IRQ_GATTS_WRITE +gatts_read +characteristic len: 253 e +_IRQ_CENTRAL_DISCONNECT +gap_advertise +_IRQ_CENTRAL_CONNECT +gattc_exchange_mtu +_IRQ_MTU_EXCHANGED 190 +gatts_notify +_IRQ_GATTS_WRITE +gatts_read +characteristic len: 187 f +_IRQ_CENTRAL_DISCONNECT +gap_advertise +_IRQ_CENTRAL_CONNECT +gattc_exchange_mtu +_IRQ_MTU_EXCHANGED 290 +gatts_notify +_IRQ_GATTS_WRITE +gatts_read +characteristic len: 287 g +_IRQ_CENTRAL_DISCONNECT +gap_advertise +--- instance1 --- +gap_connect +_IRQ_PERIPHERAL_CONNECT +gattc_exchange_mtu +_IRQ_MTU_EXCHANGED 256 +_IRQ_GATTC_NOTIFY 64 0 +gattc_discover_characteristics +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000000-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_DONE +gattc_write +_IRQ_GATTC_WRITE_DONE +gap_disconnect: True +_IRQ_PERIPHERAL_DISCONNECT +gap_connect +_IRQ_PERIPHERAL_CONNECT +gattc_exchange_mtu +_IRQ_MTU_EXCHANGED 200 +_IRQ_GATTC_NOTIFY 64 1 +gattc_discover_characteristics +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000000-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_DONE +gattc_write +_IRQ_GATTC_WRITE_DONE +gap_disconnect: True +_IRQ_PERIPHERAL_DISCONNECT +gap_connect +_IRQ_PERIPHERAL_CONNECT +gattc_exchange_mtu +_IRQ_MTU_EXCHANGED 300 +_IRQ_GATTC_NOTIFY 64 2 +gattc_discover_characteristics +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000000-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_DONE +gattc_write +_IRQ_GATTC_WRITE_DONE +gap_disconnect: True +_IRQ_PERIPHERAL_DISCONNECT +gap_connect +_IRQ_PERIPHERAL_CONNECT +gattc_exchange_mtu +_IRQ_MTU_EXCHANGED 50 +_IRQ_GATTC_NOTIFY 47 3 +gattc_discover_characteristics +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000000-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_DONE +gattc_write +_IRQ_GATTC_WRITE_DONE +gap_disconnect: True +_IRQ_PERIPHERAL_DISCONNECT +gap_connect +_IRQ_PERIPHERAL_CONNECT +_IRQ_MTU_EXCHANGED 256 +_IRQ_GATTC_NOTIFY 64 4 +gattc_discover_characteristics +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000000-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_DONE +gattc_write +_IRQ_GATTC_WRITE_DONE +gap_disconnect: True +_IRQ_PERIPHERAL_DISCONNECT +gap_connect +_IRQ_PERIPHERAL_CONNECT +_IRQ_MTU_EXCHANGED 190 +_IRQ_GATTC_NOTIFY 64 5 +gattc_discover_characteristics +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000000-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_DONE +gattc_write +_IRQ_GATTC_WRITE_DONE +gap_disconnect: True +_IRQ_PERIPHERAL_DISCONNECT +gap_connect +_IRQ_PERIPHERAL_CONNECT +_IRQ_MTU_EXCHANGED 290 +_IRQ_GATTC_NOTIFY 64 6 +gattc_discover_characteristics +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000000-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_DONE +gattc_write +_IRQ_GATTC_WRITE_DONE +gap_disconnect: True +_IRQ_PERIPHERAL_DISCONNECT diff --git a/tests/multi_net/uasyncio_tcp_readexactly.py b/tests/multi_net/uasyncio_tcp_readexactly.py new file mode 100644 index 0000000000000..71d8c6d0e9323 --- /dev/null +++ b/tests/multi_net/uasyncio_tcp_readexactly.py @@ -0,0 +1,68 @@ +# Test uasyncio stream readexactly() method using TCP server/client + +try: + import uasyncio as asyncio +except ImportError: + try: + import asyncio + except ImportError: + print("SKIP") + raise SystemExit + +PORT = 8000 + + +async def handle_connection(reader, writer): + writer.write(b"a") + await writer.drain() + + # Split the first 2 bytes up so the client must wait for the second one + await asyncio.sleep(0.1) + + writer.write(b"b") + await writer.drain() + + writer.write(b"c") + await writer.drain() + + writer.write(b"d") + await writer.drain() + + print("close") + writer.close() + await writer.wait_closed() + + print("done") + ev.set() + + +async def tcp_server(): + global ev + ev = asyncio.Event() + server = await asyncio.start_server(handle_connection, "0.0.0.0", PORT) + print("server running") + multitest.next() + async with server: + await asyncio.wait_for(ev.wait(), 10) + + +async def tcp_client(): + reader, writer = await asyncio.open_connection(IP, PORT) + print(await reader.readexactly(2)) + print(await reader.readexactly(0)) + print(await reader.readexactly(1)) + try: + print(await reader.readexactly(2)) + except EOFError as er: + print("EOFError") + print(await reader.readexactly(0)) + + +def instance0(): + multitest.globals(IP=multitest.get_network_ip()) + asyncio.run(tcp_server()) + + +def instance1(): + multitest.next() + asyncio.run(tcp_client()) diff --git a/tests/multi_net/uasyncio_tcp_readexactly.py.exp b/tests/multi_net/uasyncio_tcp_readexactly.py.exp new file mode 100644 index 0000000000000..65ce6d628dd4f --- /dev/null +++ b/tests/multi_net/uasyncio_tcp_readexactly.py.exp @@ -0,0 +1,10 @@ +--- instance0 --- +server running +close +done +--- instance1 --- +b'ab' +b'' +b'c' +EOFError +b'' diff --git a/tests/net_inet/test_tls_sites.py b/tests/net_inet/test_tls_sites.py index 876343acfc1d3..d2cb928c8d5b9 100644 --- a/tests/net_inet/test_tls_sites.py +++ b/tests/net_inet/test_tls_sites.py @@ -54,7 +54,7 @@ def main(): test_one(site, opts) print(site, "ok") except Exception as e: - print(site, repr(e)) + print(site, e) main() diff --git a/tests/net_inet/tls_num_errors.py b/tests/net_inet/tls_num_errors.py new file mode 100644 index 0000000000000..dd7f714e6ef7e --- /dev/null +++ b/tests/net_inet/tls_num_errors.py @@ -0,0 +1,44 @@ +# test that modtls produces a numerical error message when out of heap + +try: + import usocket as socket, ussl as ssl, sys +except: + import socket, ssl, sys +try: + from micropython import alloc_emergency_exception_buf, heap_lock, heap_unlock +except: + print("SKIP") + raise SystemExit + + +# test with heap locked to see it switch to number-only error message +def test(addr): + alloc_emergency_exception_buf(256) + s = socket.socket() + s.connect(addr) + try: + s.setblocking(False) + s = ssl.wrap_socket(s, do_handshake=False) + heap_lock() + print("heap is locked") + while True: + ret = s.write("foo") + if ret: + break + heap_unlock() + print("wrap: no exception") + except OSError as e: + heap_unlock() + # mbedtls produces "-29184" + # axtls produces "RECORD_OVERFLOW" + ok = "-29184" in str(e) or "RECORD_OVERFLOW" in str(e) + print("wrap:", ok) + if not ok: + print("got exception:", e) + s.close() + + +if __name__ == "__main__": + # connect to plain HTTP port, oops! + addr = socket.getaddrinfo("micropython.org", 80)[0][-1] + test(addr) diff --git a/tests/net_inet/tls_num_errors.py.exp b/tests/net_inet/tls_num_errors.py.exp new file mode 100644 index 0000000000000..e6a15634d00ae --- /dev/null +++ b/tests/net_inet/tls_num_errors.py.exp @@ -0,0 +1,2 @@ +heap is locked +wrap: True diff --git a/tests/net_inet/tls_text_errors.py b/tests/net_inet/tls_text_errors.py new file mode 100644 index 0000000000000..9e8ccfaf9ec77 --- /dev/null +++ b/tests/net_inet/tls_text_errors.py @@ -0,0 +1,33 @@ +# test that modtls produces a text error message + +try: + import usocket as socket, ussl as ssl, sys +except: + import socket, ssl, sys + + +def test(addr): + s = socket.socket() + s.connect(addr) + try: + s = ssl.wrap_socket(s) + print("wrap: no exception") + except OSError as e: + # mbedtls produces "mbedtls -0x7200: SSL - An invalid SSL record was received" + # axtls produces "RECORD_OVERFLOW" but also prints "TLS buffer overflow,..." + # CPython produces "[SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1108)" + ok = ( + "SSL_INVALID_RECORD" in str(e) + or "RECORD_OVERFLOW" in str(e) + or "wrong version" in str(e) + ) + print("wrap:", ok) + if not ok: + print("got exception:", e) + s.close() + + +if __name__ == "__main__": + # connect to plain HTTP port, oops! + addr = socket.getaddrinfo("micropython.org", 80)[0][-1] + test(addr) diff --git a/tests/run-multitests.py b/tests/run-multitests.py index c83895fb65f37..ad032df38b238 100755 --- a/tests/run-multitests.py +++ b/tests/run-multitests.py @@ -7,6 +7,7 @@ import sys, os, time, re, select import argparse import subprocess +import tempfile sys.path.append("../tools") import pyboard @@ -18,6 +19,9 @@ CPYTHON3 = os.getenv("MICROPY_CPYTHON3", "python3") MICROPYTHON = os.getenv("MICROPY_MICROPYTHON", "../ports/unix/micropython") +# For diff'ing test output +DIFF = os.getenv("MICROPY_DIFF", "diff -u") + PYTHON_TRUTH = CPYTHON3 INSTANCE_READ_TIMEOUT_S = 10 @@ -92,20 +96,25 @@ def start_file(self, filename, prepend="", append=""): class PyInstanceSubProcess(PyInstance): - def __init__(self, cmd): - self.cmd = cmd + def __init__(self, argv, env=None): + self.argv = argv + self.env = {n: v for n, v in (i.split("=") for i in env)} if env else None self.popen = None self.finished = True def __str__(self): - return self.cmd[0].rsplit("/")[-1] + return self.argv[0].rsplit("/")[-1] def run_script(self, script): output = b"" err = None try: p = subprocess.run( - self.cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, input=script + self.argv, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + input=script, + env=self.env, ) output = p.stdout except subprocess.CalledProcessError as er: @@ -114,7 +123,11 @@ def run_script(self, script): def start_script(self, script): self.popen = subprocess.Popen( - self.cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT + self.argv, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + env=self.env, ) self.popen.stdin.write(script) self.popen.stdin.close() @@ -289,7 +302,7 @@ def run_test_on_instances(test_file, num_instances, instances): continue num_output += 1 last_read_time[idx] = time.time() - if out is not None: + if out is not None and not any(m in out for m in IGNORE_OUTPUT_MATCHES): trace_instance_output(idx, out) output[idx].append(out) if err is not None: @@ -314,6 +327,18 @@ def run_test_on_instances(test_file, num_instances, instances): return error, skip, output_str +def print_diff(a, b): + a_fd, a_path = tempfile.mkstemp(text=True) + b_fd, b_path = tempfile.mkstemp(text=True) + os.write(a_fd, a.encode()) + os.write(b_fd, b.encode()) + os.close(a_fd) + os.close(b_fd) + subprocess.run(DIFF.split(" ") + [a_path, b_path]) + os.unlink(a_path) + os.unlink(b_path) + + def run_tests(test_files, instances_truth, instances_test): skipped_tests = [] passed_tests = [] @@ -363,6 +388,8 @@ def run_tests(test_files, instances_truth, instances_test): print(output_test, end="") print("### TRUTH ###") print(output_truth, end="") + print("### DIFF ###") + print_diff(output_test, output_truth) if cmd_args.show_output: print() @@ -404,16 +431,20 @@ def main(): instances_test = [] for i in cmd_args.instance: - if i.startswith("exec:"): - instances_test.append(PyInstanceSubProcess([i[len("exec:") :]])) - elif i == "micropython": - instances_test.append(PyInstanceSubProcess([MICROPYTHON])) - elif i == "cpython": - instances_test.append(PyInstanceSubProcess([CPYTHON3])) - elif i.startswith("pyb:"): - instances_test.append(PyInstancePyboard(i[len("pyb:") :])) + # Each instance arg is ,ENV=VAR,ENV=VAR... + i = i.split(",") + cmd = i[0] + env = i[1:] + if cmd.startswith("exec:"): + instances_test.append(PyInstanceSubProcess([cmd[len("exec:") :]], env)) + elif cmd == "micropython": + instances_test.append(PyInstanceSubProcess([MICROPYTHON], env)) + elif cmd == "cpython": + instances_test.append(PyInstanceSubProcess([CPYTHON3], env)) + elif cmd.startswith("pyb:"): + instances_test.append(PyInstancePyboard(cmd[len("pyb:") :])) else: - print("unknown instance string: {}".format(i), file=sys.stderr) + print("unknown instance string: {}".format(cmd), file=sys.stderr) sys.exit(1) for _ in range(max_instances - len(instances_test)): diff --git a/tests/run-natmodtests.py b/tests/run-natmodtests.py index f88ca0b788971..8eb27169c4ca1 100755 --- a/tests/run-natmodtests.py +++ b/tests/run-natmodtests.py @@ -30,7 +30,7 @@ # Code to allow a target MicroPython to import an .mpy from RAM injected_import_hook_code = """\ -import sys, uos, uio +import usys, uos, uio class __File(uio.IOBase): def __init__(self): self.off = 0 @@ -54,7 +54,7 @@ def open(self, path, mode): return __File() uos.mount(__FS(), '/__remote') uos.chdir('/__remote') -sys.modules['{}'] = __import__('__injected') +usys.modules['{}'] = __import__('__injected') """ diff --git a/tests/run-tests b/tests/run-tests index fb6bd1dc0bc10..56da1a3579708 100755 --- a/tests/run-tests +++ b/tests/run-tests @@ -5,21 +5,33 @@ import subprocess import sys import platform import argparse +import inspect import re from glob import glob +# See stackoverflow.com/questions/2632199: __file__ nor sys.argv[0] +# are guaranteed to always work, this one should though. +BASEPATH = os.path.dirname(os.path.abspath(inspect.getsourcefile(lambda: None))) + +def base_path(*p): + return os.path.abspath(os.path.join(BASEPATH, *p)).replace('\\', '/') + # Tests require at least CPython 3.3. If your default python3 executable # is of lower version, you can point MICROPY_CPYTHON3 environment var # to the correct executable. if os.name == 'nt': CPYTHON3 = os.getenv('MICROPY_CPYTHON3', 'python3.exe') - MICROPYTHON = os.getenv('MICROPY_MICROPYTHON', '../ports/windows/micropython.exe') + MICROPYTHON = os.getenv('MICROPY_MICROPYTHON', base_path('../ports/windows/micropython.exe')) else: CPYTHON3 = os.getenv('MICROPY_CPYTHON3', 'python3') - MICROPYTHON = os.getenv('MICROPY_MICROPYTHON', '../ports/unix/micropython') + MICROPYTHON = os.getenv('MICROPY_MICROPYTHON', base_path('../ports/unix/micropython')) + +# Use CPython options to not save .pyc files, to only access the core standard library +# (not site packages which may clash with u-module names), and improve start up time. +CPYTHON3_CMD = [CPYTHON3, "-BS"] # mpy-cross is only needed if --via-mpy command-line arg is passed -MPYCROSS = os.getenv('MICROPY_MPYCROSS', '../mpy-cross/mpy-cross') +MPYCROSS = os.getenv('MICROPY_MPYCROSS', base_path('../mpy-cross/mpy-cross')) # For diff'ing test output DIFF = os.getenv('MICROPY_DIFF', 'diff -u') @@ -61,7 +73,7 @@ def run_micropython(pyb, args, test_file, is_special=False): had_crash = False if pyb is None: # run on PC - if test_file.startswith(('cmdline/', 'feature_check/')) or test_file in special_tests: + if test_file.startswith(('cmdline/', base_path('feature_check/'))) or test_file in special_tests: # special handling for tests of the unix cmdline program is_special = True @@ -215,10 +227,10 @@ def run_feature_check(pyb, args, base_path, test_file): if pyb is not None and test_file.startswith("repl_"): # REPL feature tests will not run via pyboard because they require prompt interactivity return b"" - return run_micropython(pyb, args, base_path + "/feature_check/" + test_file, is_special=True) + return run_micropython(pyb, args, base_path("feature_check", test_file), is_special=True) -def run_tests(pyb, tests, args, base_path="."): +def run_tests(pyb, tests, args, result_dir): test_count = 0 testcase_count = 0 passed_count = 0 @@ -244,6 +256,10 @@ def run_tests(pyb, tests, args, base_path="."): # If we're asked to --list-tests, we can't assume that there's a # connection to target, so we can't run feature checks usefully. if not (args.list_tests or args.write_exp): + # Even if we run completely different tests in a different directory, + # we need to access feature_checks from the same directory as the + # run-tests script itself so use base_path. + # Check if micropython.native is supported, and skip such tests if it's not output = run_feature_check(pyb, args, base_path, 'native_check.py') if output == b'CRASH': @@ -307,7 +323,7 @@ def run_tests(pyb, tests, args, base_path="."): upy_float_precision = int(upy_float_precision) has_complex = run_feature_check(pyb, args, base_path, 'complex.py') == b'complex\n' has_coverage = run_feature_check(pyb, args, base_path, 'coverage.py') == b'coverage\n' - cpy_byteorder = subprocess.check_output([CPYTHON3, base_path + '/feature_check/byteorder.py']) + cpy_byteorder = subprocess.check_output(CPYTHON3_CMD + [base_path('feature_check/byteorder.py')]) skip_endian = (upy_byteorder != cpy_byteorder) # These tests don't test slice explicitly but rather use it to perform the test @@ -355,6 +371,7 @@ def run_tests(pyb, tests, args, base_path="."): if not has_complex: skip_tests.add('float/complex1.py') skip_tests.add('float/complex1_intbig.py') + skip_tests.add('float/complex_special_methods.py') skip_tests.add('float/int_big_float.py') skip_tests.add('float/true_value.py') skip_tests.add('float/types.py') @@ -414,6 +431,7 @@ def run_tests(pyb, tests, args, base_path="."): skip_tests.update({'basics/%s.py' % t for t in 'gen_yield_from_close generator_name'.split()}) # require raise_varargs, generator name skip_tests.update({'basics/async_%s.py' % t for t in 'with with2 with_break with_return'.split()}) # require async_with 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 @@ -462,7 +480,7 @@ def run_tests(pyb, tests, args, base_path="."): skip_it |= skip_slice and is_slice skip_it |= skip_async and is_async skip_it |= skip_const and is_const - skip_it |= skip_revops and test_name.startswith("class_reverse_op") + skip_it |= skip_revops and "reverse_op" in test_name skip_it |= skip_io_module and is_io_module if args.list_tests: @@ -484,7 +502,7 @@ def run_tests(pyb, tests, args, base_path="."): else: # run CPython to work out expected output try: - output_expected = subprocess.check_output([CPYTHON3, '-B', test_file]) + output_expected = subprocess.check_output(CPYTHON3_CMD + [test_file]) if args.write_exp: with open(test_file_expected, 'wb') as f: f.write(output_expected) @@ -507,8 +525,8 @@ def run_tests(pyb, tests, args, base_path="."): testcase_count += len(output_expected.splitlines()) - filename_expected = test_basename + ".exp" - filename_mupy = test_basename + ".out" + filename_expected = os.path.join(result_dir, test_basename + ".exp") + filename_mupy = os.path.join(result_dir, test_basename + ".out") if output_expected == output_mupy: print("pass ", test_file) @@ -561,10 +579,14 @@ def main(): formatter_class=argparse.RawDescriptionHelpFormatter, description='''Run and manage tests for MicroPython. +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 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 produces a pair of .out and .exp files in the current +If a test fails, run-tests produces a pair of .out and .exp files in the result directory with the MicroPython output and the expectations, respectively. ''', epilog='''\ @@ -581,6 +603,7 @@ the last matching regex is used: 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('-r', '--result-dir', default=base_path('results'), help='directory for test results') cmd_parser.add_argument('-e', '--exclude', action=append_filter, metavar='REGEX', dest='filters', help='exclude test by regex on path/name.py') cmd_parser.add_argument('-i', '--include', action=append_filter, metavar='REGEX', dest='filters', help='include test by regex on path/name.py') cmd_parser.add_argument('--write-exp', action='store_true', help='use CPython to generate .exp files to run tests w/o CPython') @@ -596,10 +619,8 @@ the last matching regex is used: args = cmd_parser.parse_args() if args.print_failures: - os.chdir(os.path.abspath(os.path.dirname(__file__))) - - for exp in glob("*.exp"): - testbase = os.path.basename(exp)[:-4] + for exp in glob(os.path.join(args.result_dir, "*.exp")): + testbase = exp[:-4] print() print("FAILURE {0}".format(testbase)) os.system("{0} {1}.exp {1}.out".format(DIFF, testbase)) @@ -607,9 +628,7 @@ the last matching regex is used: sys.exit(0) if args.clean_failures: - os.chdir(os.path.abspath(os.path.dirname(__file__))) - - for f in glob("*.exp") + glob("*.out"): + for f in glob(os.path.join(args.result_dir, "*.exp")) + glob(os.path.join(args.result_dir, "*.out")): os.remove(f) sys.exit(0) @@ -620,7 +639,7 @@ the last matching regex is used: pyb = None elif args.target in EXTERNAL_TARGETS: global pyboard - sys.path.append('../tools') + sys.path.append(base_path('../tools')) import pyboard pyb = pyboard.Pyboard(args.device, args.baudrate, args.user, args.password) pyb.enter_raw_repl() @@ -657,14 +676,11 @@ the last matching regex is used: if not args.keep_path: # clear search path to make sure tests use only builtin modules and those in extmod - os.environ['MICROPYPATH'] = os.pathsep + '../extmod' + os.environ['MICROPYPATH'] = os.pathsep + base_path('../extmod') - # Even if we run completely different tests in a different directory, - # we need to access feature_check's from the same directory as the - # run-tests script itself. - base_path = os.path.dirname(sys.argv[0]) or "." try: - res = run_tests(pyb, tests, args, base_path) + os.makedirs(args.result_dir, exist_ok=True) + res = run_tests(pyb, tests, args, args.result_dir) finally: if pyb: pyb.close() diff --git a/tests/run-tests-exp.py b/tests/run-tests-exp.py index 34c6f650f83b6..ac32fe9869c36 100644 --- a/tests/run-tests-exp.py +++ b/tests/run-tests-exp.py @@ -5,7 +5,7 @@ # This script is intended to be run by the same interpreter executable # which is to be tested, so should use minimal language functionality. # -import sys +import usys as sys import uos as os diff --git a/tests/thread/thread_stacksize1.py b/tests/thread/thread_stacksize1.py index 5827237a175eb..5d25509b763a0 100644 --- a/tests/thread/thread_stacksize1.py +++ b/tests/thread/thread_stacksize1.py @@ -1,8 +1,10 @@ # test setting the thread stack size # # MIT license; Copyright (c) 2016 Damien P. George on behalf of Pycom Ltd - -import sys +try: + import usys as sys +except ImportError: + import sys import _thread # different implementations have different minimum sizes diff --git a/tools/codeformat.py b/tools/codeformat.py index 0a8bf2e0f4e8c..81a3cdcf8e5a2 100755 --- a/tools/codeformat.py +++ b/tools/codeformat.py @@ -36,6 +36,9 @@ PATHS = [ # C "extmod/*.[ch]", + "extmod/btstack/*.[ch]", + "extmod/nimble/*.[ch]", + "lib/mbedtls_errors/tester.c", "lib/netutils/*.[ch]", "lib/timeutils/*.[ch]", "lib/utils/*.[ch]", @@ -76,9 +79,6 @@ PY_EXTS = (".py",) -FIXUP_REPLACEMENTS = ((re.compile("sizeof\(([a-z_]+)\) \*\(([a-z_]+)\)"), r"sizeof(\1) * (\2)"),) - - def list_files(paths, exclusions=None, prefix=""): files = set() for pattern in paths: @@ -124,10 +124,6 @@ def fixup_c(filename): if directive == "endif": dedent_stack.pop() - # Apply general regex-based fixups. - for regex, replacement in FIXUP_REPLACEMENTS: - l = regex.sub(replacement, l) - # Write out line. f.write(l) @@ -138,6 +134,7 @@ def main(): cmd_parser = argparse.ArgumentParser(description="Auto-format C and Python files.") cmd_parser.add_argument("-c", action="store_true", help="Format C code only") cmd_parser.add_argument("-p", action="store_true", help="Format Python code only") + cmd_parser.add_argument("-v", action="store_true", help="Enable verbose output") cmd_parser.add_argument("files", nargs="*", help="Run on specific globs") args = cmd_parser.parse_args() @@ -168,13 +165,21 @@ def batch(cmd, files, N=200): # Format C files with uncrustify. if format_c: - batch(["uncrustify", "-c", UNCRUSTIFY_CFG, "-lC", "--no-backup"], lang_files(C_EXTS)) + command = ["uncrustify", "-c", UNCRUSTIFY_CFG, "-lC", "--no-backup"] + if not args.v: + command.append("-q") + batch(command, lang_files(C_EXTS)) for file in lang_files(C_EXTS): fixup_c(file) # Format Python files with black. if format_py: - batch(["black", "-q", "--fast", "--line-length=99"], lang_files(PY_EXTS)) + command = ["black", "--fast", "--line-length=99"] + if args.v: + command.append("-v") + else: + command.append("-q") + batch(command, lang_files(PY_EXTS)) if __name__ == "__main__": diff --git a/tools/make-frozen.py b/tools/make-frozen.py index a8706bf784fdc..bc35d383429d6 100755 --- a/tools/make-frozen.py +++ b/tools/make-frozen.py @@ -50,7 +50,7 @@ def module_name(f): for f, st in modules: print("%d," % st.st_size) -print("};") +print("0};") print("const char mp_frozen_str_content[] = {") for f, st in modules: @@ -82,4 +82,4 @@ def module_name(f): chrs.append('\\0"') print("".join(chrs)) -print("};") +print('"\\0"};') diff --git a/tools/makemanifest.py b/tools/makemanifest.py index 2cee6aebc796a..9ef03682607dc 100644 --- a/tools/makemanifest.py +++ b/tools/makemanifest.py @@ -76,9 +76,10 @@ def freeze(path, script=None, opt=0): If `script` is an iterable then freeze() is called on all items of the iterable (with the same `path` and `opt` passed through). - If `script` is a string then it specifies the filename to freeze, and - can include extra directories before the file. The file will be - searched for in `path`. + If `script` is a string then it specifies the file or directory to + freeze, and can include extra directories before the file or last + directory. The file or directory will be searched for in `path`. If + `script` is a directory then all files in that directory will be frozen. `opt` is the optimisation level to pass to mpy-cross when compiling .py to .mpy. @@ -163,17 +164,10 @@ def get_timestamp_newest(path): return ts_newest -def mkdir(path): - cur_path = "" - for p in path.split("/")[:-1]: - cur_path += p + "/" - try: - os.mkdir(cur_path) - except OSError as er: - if er.args[0] == 17: # file exists - pass - else: - raise er +def mkdir(filename): + path = os.path.dirname(filename) + if not os.path.isdir(path): + os.makedirs(path) def freeze_internal(kind, path, script, opt): @@ -182,14 +176,22 @@ def freeze_internal(kind, path, script, opt): if any(f[0] == KIND_AS_STR for f in manifest_list): raise FreezeError("can only freeze one str directory") manifest_list.append((KIND_AS_STR, path, script, opt)) - elif script is None: - for dirpath, dirnames, filenames in os.walk(path, followlinks=True): + elif script is None or isinstance(script, str) and script.find(".") == -1: + # Recursively search `path` for files to freeze, optionally restricted + # to a subdirectory specified by `script` + if script is None: + subdir = "" + else: + subdir = "/" + script + for dirpath, dirnames, filenames in os.walk(path + subdir, followlinks=True): for f in filenames: freeze_internal(kind, path, (dirpath + "/" + f)[len(path) + 1 :], opt) elif not isinstance(script, str): + # `script` is an iterable of items to freeze for s in script: freeze_internal(kind, path, s, opt) else: + # `script` should specify an individual file to be frozen extension_kind = {KIND_AS_MPY: ".py", KIND_MPY: ".mpy"} if kind == KIND_AUTO: for k, ext in extension_kind.items(): @@ -278,7 +280,8 @@ def main(): + ["-o", outfile, "-s", script, "-O{}".format(opt), infile] ) if res != 0: - print("error compiling {}: {}".format(infile, out)) + print("error compiling {}:".format(infile)) + sys.stdout.buffer.write(out) raise SystemExit(1) ts_outfile = get_timestamp(outfile) mpy_files.append(outfile) @@ -324,7 +327,7 @@ def main(): b" (qstr_pool_t*)&mp_qstr_const_pool, MP_QSTRnumber_of, 0, 0\n" b"};\n" b'const char mp_frozen_mpy_names[1] = {"\\0"};\n' - b"const mp_raw_code_t *const mp_frozen_mpy_content[0] = {};\n" + b"const mp_raw_code_t *const mp_frozen_mpy_content[1] = {NULL};\n" ) # Generate output diff --git a/tools/mpy-tool.py b/tools/mpy-tool.py index 43d35503eb564..de7cfe5d631c7 100755 --- a/tools/mpy-tool.py +++ b/tools/mpy-tool.py @@ -253,6 +253,7 @@ def __init__(self, code_kind, bytecode, prelude_offset, qstrs, objs, raw_codes): self.ip, self.ip2, self.prelude = extract_prelude(self.bytecode, self.prelude_offset) self.simple_name = self._unpack_qstr(self.ip2) self.source_file = self._unpack_qstr(self.ip2 + 2) + self.line_info_offset = self.ip2 + 4 def _unpack_qstr(self, ip): qst = self.bytecode[ip] | self.bytecode[ip + 1] << 8 @@ -404,7 +405,10 @@ def freeze_module(self, qstr_links=(), type_sig=0): print(" .n_def_pos_args = %u," % self.prelude[5]) print(" .qstr_block_name = %s," % self.simple_name.qstr_id) print(" .qstr_source_file = %s," % self.source_file.qstr_id) - print(" .line_info = fun_data_%s + %u," % (self.escaped_name, 0)) # TODO + print( + " .line_info = fun_data_%s + %u," + % (self.escaped_name, self.line_info_offset) + ) print(" .opcodes = fun_data_%s + %u," % (self.escaped_name, self.ip)) print(" },") print(" .line_of_definition = %u," % 0) # TODO @@ -934,7 +938,7 @@ def merge_mpy(raw_codes, output_file): merged_mpy.extend(header) bytecode = bytearray() - bytecode_len = 6 + len(raw_codes) * 4 + 2 + bytecode_len = 6 + len(raw_codes) * 5 + 2 bytecode.append(bytecode_len << 2) # kind and length bytecode.append(0b00000000) # signature prelude bytecode.append(0b00001000) # size prelude @@ -943,7 +947,7 @@ def merge_mpy(raw_codes, output_file): for idx in range(len(raw_codes)): bytecode.append(0x32) # MP_BC_MAKE_FUNCTION bytecode.append(idx) # index raw code - bytecode.extend(b"\x34\x00") # MP_BC_CALL_FUNCTION, 0 args + bytecode.extend(b"\x34\x00\x59") # MP_BC_CALL_FUNCTION, 0 args, MP_BC_POP_TOP bytecode.extend(b"\x51\x63") # MP_BC_LOAD_NONE, MP_BC_RETURN_VALUE bytecode.append(0) # n_obj diff --git a/tools/mpy_ld.py b/tools/mpy_ld.py index 32bc176cb71c4..774966a7f68a0 100755 --- a/tools/mpy_ld.py +++ b/tools/mpy_ld.py @@ -908,7 +908,7 @@ def build_mpy(env, entry_offset, fmpy, native_qstr_vals, native_qstr_objs): # MPY: header out.write_bytes( bytearray( - [ord("M"), MPY_VERSION, env.arch.mpy_feature, MP_SMALL_INT_BITS, QSTR_WINDOW_SIZE,] + [ord("M"), MPY_VERSION, env.arch.mpy_feature, MP_SMALL_INT_BITS, QSTR_WINDOW_SIZE] ) ) diff --git a/tools/pyboard.py b/tools/pyboard.py index 6376b13c7e333..c97ddbe487be3 100755 --- a/tools/pyboard.py +++ b/tools/pyboard.py @@ -70,6 +70,7 @@ import sys import time import os +import ast try: stdout = sys.stdout.buffer @@ -426,7 +427,12 @@ def fs_get(self, src, dest, chunk_size=256): data = bytearray() self.exec_("print(r(%u))" % chunk_size, data_consumer=lambda d: data.extend(d)) assert data.endswith(b"\r\n\x04") - data = eval(str(data[:-3], "ascii")) + try: + data = ast.literal_eval(str(data[:-3], "ascii")) + if not isinstance(data, bytes): + raise ValueError("Not bytes") + except (UnicodeError, ValueError) as e: + raise PyboardError("fs_get: Could not interpret received data: %s" % str(e)) if not data: break f.write(data) diff --git a/tools/uncrustify.cfg b/tools/uncrustify.cfg index b0b1239b1a1fb..80542b903e79e 100644 --- a/tools/uncrustify.cfg +++ b/tools/uncrustify.cfg @@ -1,4 +1,4 @@ -# Uncrustify-0.70.1 +# Uncrustify-0.71.0_f # # General options @@ -37,13 +37,18 @@ string_replace_tab_chars = false # true/false # Improvements to template detection may make this option obsolete. tok_split_gte = false # true/false +# Disable formatting of NL_CONT ('\\n') ended lines (e.g. multiline macros) +disable_processing_nl_cont = false # true/false + # Specify the marker used in comments to disable processing of part of the # file. +# The comment should be used alone in one line. # # Default: *INDENT-OFF* disable_processing_cmt = " *FORMAT-OFF*" # string # Specify the marker used in comments to (re)enable processing in a file. +# The comment should be used alone in one line. # # Default: *INDENT-ON* enable_processing_cmt = " *FORMAT-ON*" # string @@ -507,35 +512,35 @@ sp_func_proto_paren = remove # ignore/add/remove/force # Add or remove space between function name and '()' on function declaration # without parameters. -sp_func_proto_paren_empty = ignore # ignore/add/remove/force +sp_func_proto_paren_empty = remove # ignore/add/remove/force # Add or remove space between function name and '(' with a typedef specifier. -sp_func_type_paren = ignore # ignore/add/remove/force +sp_func_type_paren = remove # ignore/add/remove/force # Add or remove space between alias name and '(' of a non-pointer function type typedef. -sp_func_def_paren = ignore # ignore/add/remove/force +sp_func_def_paren = remove # ignore/add/remove/force # Add or remove space between function name and '()' on function definition # without parameters. -sp_func_def_paren_empty = ignore # ignore/add/remove/force +sp_func_def_paren_empty = remove # ignore/add/remove/force # Add or remove space inside empty function '()'. # Overrides sp_after_angle unless use_sp_after_angle_always is set to true. -sp_inside_fparens = ignore # ignore/add/remove/force +sp_inside_fparens = remove # ignore/add/remove/force # Add or remove space inside function '(' and ')'. sp_inside_fparen = remove # ignore/add/remove/force # Add or remove space inside the first parentheses in a function type, as in # 'void (*x)(...)'. -sp_inside_tparen = ignore # ignore/add/remove/force +sp_inside_tparen = remove # ignore/add/remove/force # Add or remove space between the ')' and '(' in a function type, as in # 'void (*x)(...)'. -sp_after_tparen_close = ignore # ignore/add/remove/force +sp_after_tparen_close = remove # ignore/add/remove/force # Add or remove space between ']' and '(' when part of a function call. -sp_square_fparen = ignore # ignore/add/remove/force +sp_square_fparen = remove # ignore/add/remove/force # Add or remove space between ')' and '{' of function. sp_fparen_brace = force # ignore/add/remove/force @@ -550,11 +555,11 @@ sp_fparen_brace_initializer = ignore # ignore/add/remove/force sp_fparen_dbrace = ignore # ignore/add/remove/force # Add or remove space between function name and '(' on function calls. -sp_func_call_paren = ignore # ignore/add/remove/force +sp_func_call_paren = remove # ignore/add/remove/force # Add or remove space between function name and '()' on function calls without # parameters. If set to ignore (the default), sp_func_call_paren is used. -sp_func_call_paren_empty = ignore # ignore/add/remove/force +sp_func_call_paren_empty = remove # ignore/add/remove/force # Add or remove space between the user function name and '(' on function # calls. You need to set a keyword to be a user function in the config file, @@ -604,6 +609,10 @@ sp_catch_paren = ignore # ignore/add/remove/force # in '@catch (something) { }'. If set to ignore, sp_catch_paren is used. sp_oc_catch_paren = ignore # ignore/add/remove/force +# (OC) Add or remove space before Objective-C protocol list +# as in '@protocol Protocol' or '@interface MyClass : NSObject'. +sp_before_oc_proto_list = ignore # ignore/add/remove/force + # (OC) Add or remove space between class name and '(' # in '@interface className(categoryName):BaseClass' sp_oc_classname_paren = ignore # ignore/add/remove/force @@ -671,9 +680,7 @@ sp_getset_brace = ignore # ignore/add/remove/force # Add or remove space between a variable and '{' for C++ uniform # initialization. -# -# Default: add -sp_word_brace = add # ignore/add/remove/force +sp_word_brace_init_lst = ignore # ignore/add/remove/force # Add or remove space between a variable and '{' for a namespace. # @@ -1113,6 +1120,10 @@ indent_member_single = false # true/false # Spaces to indent single line ('//') comments on lines before code. indent_sing_line_comments = 0 # unsigned number +# When opening a paren for a control statement (if, for, while, etc), increase +# the indent level by this value. Negative values decrease the indent level. +indent_sparen_extra = 0 # number + # Whether to indent trailing single line ('//') comments relative to the code # instead of trying to keep the same absolute column. indent_relative_single_line_comments = false # true/false @@ -1219,12 +1230,19 @@ indent_preserve_sql = false # true/false # Default: true indent_align_assign = false # true/false +# If true, the indentation of the chunks after a '=' sequence will be set at +# LHS token indentation column before '='. +indent_off_after_assign = false # true/false + # Whether to align continued statements at the '('. If false or the '(' is # followed by a newline, the next line indent is one tab. # # Default: true indent_align_paren = false # true/false +# (OC) Whether to indent Objective-C code inside message selectors. +indent_oc_inside_msg_sel = false # true/false + # (OC) Whether to indent Objective-C blocks at brace level instead of usual # rules. indent_oc_block = false # true/false @@ -1284,6 +1302,15 @@ indent_token_after_brace = true # true/false # Whether to indent the body of a C++11 lambda. indent_cpp_lambda_body = false # true/false +# How to indent compound literals that are being returned. +# true: add both the indent from return & the compound literal open brace (ie: +# 2 indent levels) +# false: only indent 1 level, don't add the indent for the open brace, only add +# the indent for the return. +# +# Default: true +indent_compound_literal_return = true # true/false + # (C#) Whether to indent a 'using' block if no braces are used. # # Default: true @@ -1296,6 +1323,12 @@ indent_using_block = true # true/false # 2: When the `:` is a continuation, indent it under `?` indent_ternary_operator = 0 # unsigned number +# Whether to indent the statments inside ternary operator. +indent_inside_ternary_operator = false # true/false + +# If true, the indentation of the chunks after a `return` sequence will be set at return indentation column. +indent_off_after_return = false # true/false + # If true, the indentation of the chunks after a `return new` sequence will be set at return indentation column. indent_off_after_return_new = false # true/false @@ -1329,7 +1362,7 @@ nl_getset_leave_one_liners = false # true/false nl_cs_property_leave_one_liners = false # true/false # Don't split one-line function definitions, as in 'int foo() { return 0; }'. -# night modify nl_func_type_name +# might modify nl_func_type_name nl_func_leave_one_liners = false # true/false # Don't split one-line C++11 lambdas, as in '[]() { return 0; }'. @@ -1436,6 +1469,9 @@ nl_else_brace = remove # ignore/add/remove/force # Add or remove newline between 'else' and 'if'. nl_else_if = ignore # ignore/add/remove/force +# Add or remove newline before '{' opening brace +nl_before_opening_brace_func_class_def = ignore # ignore/add/remove/force + # Add or remove newline before 'if'/'else if' closing parenthesis. nl_before_if_closing_paren = ignore # ignore/add/remove/force @@ -1684,6 +1720,9 @@ nl_func_decl_args = ignore # ignore/add/remove/force # Add or remove newline after each ',' in a function definition. nl_func_def_args = ignore # ignore/add/remove/force +# Add or remove newline after each ',' in a function call. +nl_func_call_args = ignore # ignore/add/remove/force + # Whether to add a newline after each ',' in a function declaration if '(' # and ')' are in different lines. If false, nl_func_decl_args is used instead. nl_func_decl_args_multi_line = false # true/false @@ -1725,6 +1764,9 @@ nl_func_call_empty = ignore # ignore/add/remove/force # has preference over nl_func_call_start_multi_line. nl_func_call_start = ignore # ignore/add/remove/force +# Whether to add a newline before ')' in a function call. +nl_func_call_end = ignore # ignore/add/remove/force + # Whether to add a newline after '(' in a function call if '(' and ')' are in # different lines. nl_func_call_start_multi_line = false # true/false @@ -1737,6 +1779,9 @@ nl_func_call_args_multi_line = false # true/false # different lines. nl_func_call_end_multi_line = false # true/false +# Whether to respect nl_func_call_XXX option incase of closure args. +nl_func_call_args_multi_line_ignore_closures = false # true/false + # Whether to add a newline after '<' of a template parameter list. nl_template_start = false # true/false @@ -1871,7 +1916,7 @@ nl_before_return = false # true/false # close brace. nl_after_return = false # true/false -# (Java) Whether to put a blank line before a member '.' or '->' operators. +# Whether to put a blank line before a member '.' or '->' operators. nl_before_member = ignore # ignore/add/remove/force # (Java) Whether to put a blank line after a member '.' or '->' operators. @@ -2134,6 +2179,26 @@ nl_after_annotation = ignore # ignore/add/remove/force # (Java) Add or remove newline between two annotations. nl_between_annotation = ignore # ignore/add/remove/force +# The number of newlines before a whole-file #ifdef. +# +# 0: No change (default). +nl_before_whole_file_ifdef = 0 # unsigned number + +# The number of newlines after a whole-file #ifdef. +# +# 0: No change (default). +nl_after_whole_file_ifdef = 0 # unsigned number + +# The number of newlines before a whole-file #endif. +# +# 0: No change (default). +nl_before_whole_file_endif = 0 # unsigned number + +# The number of newlines after a whole-file #endif. +# +# 0: No change (default). +nl_after_whole_file_endif = 0 # unsigned number + # # Positioning options # @@ -2503,6 +2568,11 @@ align_oc_msg_colon_first = false # true/false # on the ':'. align_oc_decl_colon = false # true/false +# (OC) Whether to not align parameters in an Objectve-C message call if first +# colon is not on next line of the message call (the same way Xcode does +# aligment) +align_oc_msg_colon_xcode_like = false # true/false + # # Comment modification options # @@ -2734,6 +2804,25 @@ mod_sort_using = false # true/false # break your code if your includes/imports have ordering dependencies. mod_sort_include = false # true/false +# Whether to prioritize '#include' and '#import' statements that contain +# filename without extension when sorting is enabled. +mod_sort_incl_import_prioritize_filename = false # true/false + +# Whether to prioritize '#include' and '#import' statements that does not +# contain extensions when sorting is enabled. +mod_sort_incl_import_prioritize_extensionless = false # true/false + +# Whether to prioritize '#include' and '#import' statements that contain +# angle over quotes when sorting is enabled. +mod_sort_incl_import_prioritize_angle_over_quotes = false # true/false + +# Whether to ignore file extension in '#include' and '#import' statements +# for sorting comparison. +mod_sort_incl_import_ignore_extension = false # true/false + +# Whether to group '#include' and '#import' statements when sorting is enabled. +mod_sort_incl_import_grouping_enabled = false # true/false + # Whether to move a 'break' that appears after a fully braced 'case' before # the close brace, as in 'case X: { ... } break;' => 'case X: { ... break; }'. mod_move_case_break = false # true/false @@ -2920,6 +3009,11 @@ use_sp_after_angle_always = false # true/false # Default: true use_options_overriding_for_qt_macros = true # true/false +# If true: the form feed character is removed from the list +# of whitespace characters. +# See https://en.cppreference.com/w/cpp/string/byte/isspace +use_form_feed_no_more_as_whitespace_character = false # true/false + # # Warn levels - 1: error, 2: warning (default), 3: note # @@ -2930,6 +3024,16 @@ use_options_overriding_for_qt_macros = true # true/false # Default: 2 warn_level_tabs_found_in_verbatim_string_literals = 2 # unsigned number +# Limit the number of loops. +# Used by uncrustify.cpp to exit from infinite loop. +# 0: no limit. +debug_max_number_of_loops = 0 # number + +# Set the number of the line to protocol; +# Used in the function prot_the_line if the 2. parameter is zero. +# 0: nothing protocol. +debug_line_number_to_protocol = 0 # number + # Meaning of the settings: # Ignore - do not do any changes # Add - makes sure there is 1 or more space/brace/newline/etc @@ -2982,7 +3086,7 @@ warn_level_tabs_found_in_verbatim_string_literals = 2 # unsigned number # `macro-close END_MESSAGE_MAP` # # -# option(s) with 'not default' value: 0 +# option(s) with 'not default' value: 67 # # Custom types for MicroPython diff --git a/tools/upip.py b/tools/upip.py index 0036eac25db7b..aa8aecedfc23f 100644 --- a/tools/upip.py +++ b/tools/upip.py @@ -129,7 +129,11 @@ def url_open(url): proto, _, host, urlpath = url.split("/", 3) try: - ai = usocket.getaddrinfo(host, 443, 0, usocket.SOCK_STREAM) + port = 443 + if ":" in host: + host, port = host.split(":") + port = int(port) + ai = usocket.getaddrinfo(host, port, 0, usocket.SOCK_STREAM) except OSError as e: fatal("Unable to resolve %s (no Internet?)" % host, e) # print("Address infos:", ai) @@ -147,7 +151,7 @@ def url_open(url): warn_ussl = False # MicroPython rawsocket module supports file interface directly - s.write("GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n" % (urlpath, host)) + s.write("GET /%s HTTP/1.0\r\nHost: %s:%s\r\n\r\n" % (urlpath, host, port)) l = s.readline() protover, status, msg = l.split(None, 2) if status != b"200":