diff --git a/.travis.yml b/.travis.yml index 0f57bc3fe..36998300d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -62,7 +62,10 @@ jobs: - stage: test env: NAME="unix coverage build and tests" install: + - sudo apt-get install python3-pip - sudo pip install cpp-coveralls + - sudo pip3 install setuptools + - sudo pip3 install pyelftools - gcc --version - python3 --version script: @@ -78,6 +81,18 @@ jobs: - (cd tests && MICROPY_CPYTHON3=python3 MICROPY_MICROPYTHON=../ports/unix/micropython_coverage ./run-tests --via-mpy --emit native -d basics float micropython) # test when input script comes from stdin - cat tests/basics/0prelim.py | ports/unix/micropython_coverage | grep -q 'abc' + # test building native mpy modules + - make -C examples/natmod/features1 ARCH=x64 + - make -C examples/natmod/features2 ARCH=x64 + - make -C examples/natmod/btree ARCH=x64 + - make -C examples/natmod/framebuf ARCH=x64 + - make -C examples/natmod/uheapq ARCH=x64 + - make -C examples/natmod/urandom ARCH=x64 + - make -C examples/natmod/ure ARCH=x64 + - make -C examples/natmod/uzlib ARCH=x64 + # test importing .mpy generated by mpy_ld.py + - MICROPYPATH=examples/natmod/features2 ./ports/unix/micropython_coverage -m features2 + - (cd tests && ./run-natmodtests.py extmod/{btree*,framebuf*,uheapq*,ure*,uzlib*}.py) # run coveralls coverage analysis (try to, even if some builds/tests failed) - (cd ports/unix && coveralls --root ../.. --build-root . --gcov $(which gcov) --gcov-options '\-o build-coverage/' --include py --include extmod) after_failure: diff --git a/docs/conf.py b/docs/conf.py index dbd1d0c56..36f99cd2c 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -74,7 +74,7 @@ copyright = '2014-2019, Damien P. George, Paul Sokolovsky, and contributors' # # 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.11' +version = release = '1.12' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/develop/cmodules.rst b/docs/develop/cmodules.rst index ba43c3dc9..a4c473347 100644 --- a/docs/develop/cmodules.rst +++ b/docs/develop/cmodules.rst @@ -1,3 +1,5 @@ +.. _cmodules: + MicroPython external C modules ============================== @@ -17,6 +19,10 @@ more sense to keep this external to the main MicroPython repository. This chapter describes how to compile such external modules into the MicroPython executable or firmware image. +An alternative approach is to use :ref:`natmod` which allows writing custom C +code that is placed in a .mpy file, which can be imported dynamically in to +a running MicroPython system without the need to recompile the main firmware. + Structure of an external C module --------------------------------- diff --git a/docs/develop/index.rst b/docs/develop/index.rst index fff3e43d7..f1fd0692e 100644 --- a/docs/develop/index.rst +++ b/docs/develop/index.rst @@ -11,3 +11,4 @@ See the `getting started guide cmodules.rst qstr.rst + natmod.rst diff --git a/docs/develop/natmod.rst b/docs/develop/natmod.rst new file mode 100644 index 000000000..1ce238164 --- /dev/null +++ b/docs/develop/natmod.rst @@ -0,0 +1,202 @@ +.. _natmod: + +Native machine code in .mpy files +================================= + +This section describes how to build and work with .mpy files that contain native +machine code from a language other than Python. This allows you to +write code in a language like C, compile and link it into a .mpy file, and then +import this file like a normal Python module. This can be used for implementing +functionality which is performance critical, or for including an existing +library written in another language. + +One of the main advantages of using native .mpy files is that native machine code +can be imported by a script dynamically, without the need to rebuild the main +MicroPython firmware. This is in contrast to :ref:`cmodules` which also allows +defining custom modules in C but they must be compiled into the main firmware image. + +The focus here is on using C to build native modules, but in principle any +language which can be compiled to stand-alone machine code can be put into a +.mpy file. + +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. + +Supported features and limitations +---------------------------------- + +A .mpy file can contain MicroPython bytecode and/or native machine code. If it +contains native machine code then the .mpy file has a specific architecture +associated with it. Current supported architectures are (these are the valid +options for the ``ARCH`` variable, see below): + +* ``x86`` (32 bit) +* ``x64`` (64 bit x86) +* ``armv7m`` (ARM Thumb 2, eg Cortex-M3) +* ``armv7emsp`` (ARM Thumb 2, single precision float, eg Cortex-M4F, Cortex-M7) +* ``armv7emdp`` (ARM Thumb 2, double precision float, eg Cortex-M7) +* ``xtensa`` (non-windowed, eg ESP8266) +* ``xtensawin`` (windowed with window size 8, eg ESP32) + +When compiling and linking the native .mpy file the architecture must be chosen +and the corresponding file can only be imported on that architecture. For more +details about .mpy files see :ref:`mpy_files`. + +Native code must be compiled as position independent code (PIC) and use a global +offset table (GOT), although the details of this varies from architecture to +architecture. When importing .mpy files with native code the import machinery +is able to do some basic relocation of the native code. This includes +relocating text, rodata and BSS sections. + +Supported features of the linker and dynamic loader are: + +* executable code (text) +* read-only data (rodata), including strings and constant data (arrays, structs, etc) +* zeroed data (BSS) +* pointers in text to text, rodata and BSS +* pointers in rodata to text, rodata and BSS + +The known limitations are: + +* data sections are not supported; workaround: use BSS data and initialise the + data values explicitly + +* static BSS variables are not supported; workaround: use global BSS variables + +So, if your C code has writable data, make sure the data is defined globally, +without an initialiser, and only written to within functions. + +Defining a native module +------------------------ + +A native .mpy module is defined by a set of files that are used to build the .mpy. +The filesystem layout consists of two main parts, the source files and the Makefile: + +* In the simplest case only a single C source file is required, which contains all + the code that will be compiled into the .mpy module. This C source code must + include the ``py/dynruntime.h`` file to access the MicroPython dynamic API, and + must at least define a function called ``mpy_init``. This function will be the + entry point of the module, called when the module is imported. + + The module can be split into multiple C source files if desired. Parts of the + module can also be implemented in Python. All source files should be listed in + the Makefile, by adding them to the ``SRC`` variable (see below). This includes + both C source files as well as any Python files which will be included in the + resulting .mpy file. + +* The ``Makefile`` contains the build configuration for the module and list the + source files used to build the .mpy module. It should define ``MPY_DIR`` as the + location of the MicroPython repository (to find header files, the relevant Makefile + fragment, and the ``mpy_ld.py`` tool), ``MOD`` as the name of the module, ``SRC`` + as the list of source files, optionally specify the machine architecture via ``ARCH``, + and then include ``py/dynruntime.mk``. + +Minimal example +--------------- + +This section provides a fully working example of a simple module named ``factorial``. +This module provides a single function ``factorial.factorial(x)`` which computes the +factorial of the input and returns the result. + +Directory layout:: + + factorial/ + ├── factorial.c + └── Makefile + +The file ``factorial.c`` contains: + +.. code-block:: c + + // Include the header file to get access to the MicroPython API + #include "py/dynruntime.h" + + // Helper function to compute factorial + STATIC mp_int_t factorial_helper(mp_int_t x) { + if (x == 0) { + return 1; + } + return x * factorial_helper(x - 1); + } + + // This is the function which will be called from Python, as factorial(x) + STATIC mp_obj_t factorial(mp_obj_t x_obj) { + // Extract the integer from the MicroPython input object + mp_int_t x = mp_obj_get_int(x_obj); + // Calculate the factorial + mp_int_t result = factorial_helper(x); + // Convert the result to a MicroPython integer object and return it + return mp_obj_new_int(result); + } + // Define a Python reference to the function above + STATIC MP_DEFINE_CONST_FUN_OBJ_1(factorial_obj, factorial); + + // This is the entry point and is called when the module is imported + mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *args) { + // This must be first, it sets up the globals dict and other things + MP_DYNRUNTIME_INIT_ENTRY + + // Make the function available in the module's namespace + mp_store_global(MP_QSTR_factorial, MP_OBJ_FROM_PTR(&factorial_obj)); + + // This must be last, it restores the globals dict + MP_DYNRUNTIME_INIT_EXIT + } + +The file ``Makefile`` contains: + +.. code-block:: make + + # Location of top-level MicroPython directory + MPY_DIR = ../../.. + + # Name of module + MOD = features0 + + # Source files (.c or .py) + SRC = features0.c + + # Architecture to build for (x86, x64, armv7m, xtensa, xtensawin) + ARCH = x64 + + # Include to get the rules for compiling and linking the module + include $(MPY_DIR)/py/dynruntime.mk + +Compiling the module +-------------------- + +Be sure to select the correct ``ARCH`` for the target you are going to run on. +Then build with:: + + $ make + +Without modifying the Makefile you can specify the target architecture via:: + + $ make ARCH=armv7m + +Module usage in MicroPython +--------------------------- + +Once the module is built there should be a file called ``factorial.mpy``. Copy +this so it is accessible on the filesystem of your MicroPython system and can be +found in the import path. The module con now be accessed in Python just like any +other module, for example:: + + import factorial + print(factorial.factorial(10)) + # should display 3628800 + +Further examples +---------------- + +See ``examples/natmod/`` for further examples which show many of the available +features of native .mpy modules. Such features include: + +* using multiple C source files +* including Python code alongside C code +* rodata and BSS data +* memory allocation +* use of floating point +* exception handling +* including external C libraries diff --git a/docs/esp32/general.rst b/docs/esp32/general.rst index 51918d4e1..8137c042d 100644 --- a/docs/esp32/general.rst +++ b/docs/esp32/general.rst @@ -52,6 +52,7 @@ For your convenience, some of technical specifications are provided below: * I2S: 2 * ADC: 12-bit SAR ADC up to 18 channels * DAC: 2 8-bit DACs +* RMT: 8 channels allowing accurate pulse transmit/receive * Programming: using BootROM bootloader from UART - due to external FlashROM and always-available BootROM bootloader, the ESP32 is not brickable diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index 20e728d1c..cfe31664e 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -359,12 +359,26 @@ Notes: To further reduce power consumption it is possible to disable the internal pullups:: p1 = Pin(4, Pin.IN, Pin.PULL_HOLD) - + After leaving deepsleep it may be necessary to un-hold the pin explicitly (e.g. if it is an output pin) via:: - + p1 = Pin(4, Pin.OUT, None) +RMT +--- + +The RMT is ESP32-specific and allows generation of accurate digital pulses with +12.5ns resolution. See :ref:`esp32.RMT ` for details. Usage is:: + + import esp32 + from machine import Pin + + r = esp32.RMT(0, pin=Pin(18), clock_div=8) + r # RMT(channel=0, pin=18, source_freq=80000000, clock_div=8) + # The channel resolution is 100ns (1/(source_freq/clock_div)). + r.write_pulses((1, 20, 2, 40), start=0) # Send 0 for 100ns, 1 for 2000ns, 0 for 200ns, 1 for 4000ns + OneWire driver -------------- diff --git a/docs/esp8266/quickref.rst b/docs/esp8266/quickref.rst index a19766504..9c7db3205 100644 --- a/docs/esp8266/quickref.rst +++ b/docs/esp8266/quickref.rst @@ -249,6 +249,10 @@ See :ref:`machine.RTC ` :: ntptime.settime() # set the rtc datetime from the remote server rtc.datetime() # get the date and time in UTC +.. note:: Not all methods are implemented: `RTC.now()`, `RTC.irq(handler=*) ` + (using a custom handler), `RTC.init()` and `RTC.deinit()` are + currently not supported. + Deep-sleep mode --------------- diff --git a/docs/esp8266/tutorial/network_tcp.rst b/docs/esp8266/tutorial/network_tcp.rst index 852fc82f3..d479f62e6 100644 --- a/docs/esp8266/tutorial/network_tcp.rst +++ b/docs/esp8266/tutorial/network_tcp.rst @@ -44,7 +44,7 @@ Now that we are connected we can download and display the data:: ... data = s.recv(500) ... print(str(data, 'utf8'), end='') ... - + When this loop executes it should start showing the animation (use ctrl-C to interrupt it). diff --git a/docs/library/esp32.rst b/docs/library/esp32.rst index a593965ae..467af0ff0 100644 --- a/docs/library/esp32.rst +++ b/docs/library/esp32.rst @@ -1,3 +1,5 @@ +.. currentmodule:: esp32 + :mod:`esp32` --- functionality specific to the ESP32 ==================================================== @@ -56,10 +58,14 @@ This class gives access to the partitions in the device's flash memory. Returns a 6-tuple ``(type, subtype, addr, size, label, encrypted)``. .. method:: Partition.readblocks(block_num, buf) +.. method:: Partition.readblocks(block_num, buf, offset) .. method:: Partition.writeblocks(block_num, buf) +.. method:: Partition.writeblocks(block_num, buf, offset) .. method:: Partition.ioctl(cmd, arg) - These methods implement the block protocol defined by :class:`uos.AbstractBlockDev`. + These methods implement the simple and :ref:`extended + ` block protocol defined by + :class:`uos.AbstractBlockDev`. .. method:: Partition.set_boot() @@ -82,6 +88,91 @@ Constants Used in `Partition.find` to specify the partition type. + +.. _esp32.RMT: + +RMT +--- + +The RMT (Remote Control) module, specific to the ESP32, was originally designed +to send and receive infrared remote control signals. However, due to a flexible +design and very accurate (as low as 12.5ns) pulse generation, it can also be +used to transmit or receive many other types of digital signals:: + + import esp32 + from machine import Pin + + r = esp32.RMT(0, pin=Pin(18), clock_div=8) + r # RMT(channel=0, pin=18, source_freq=80000000, clock_div=8) + # 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 + +The input to the RMT module is an 80MHz clock (in the future it may be able to +configure the input clock but, for now, it's fixed). ``clock_div`` *divides* +the clock input which determines the resolution of the RMT channel. The +numbers specificed in ``write_pulses`` are multiplied by the resolution to +define the pulses. + +``clock_div`` is an 8-bit divider (0-255) and each pulse can be defined by +multiplying the resolution by a 15-bit (0-32,768) number. There are eight +channels (0-7) and each can have a different clock divider. + +So, in the example above, the 80MHz clock is divided by 8. Thus the +resolution is (1/(80Mhz/8)) 100ns. Since the ``start`` level is 0 and toggles +with each number, the bitstream is ``0101`` with durations of [100ns, 2000ns, +100ns, 4000ns]. + +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 + *beta feature* and the interface may change in the future. + + +.. class:: RMT(channel, \*, pin=None, clock_div=8) + + 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. + +.. method:: RMT.source_freq() + + Returns the source clock frequency. Currently the source clock is not + configurable so this will always return 80MHz. + +.. method:: RMT.clock_div() + + Return the clock divider. Note that the channel resolution is + ``1 / (source_freq / clock_div)``. + +.. method:: RMT.wait_done(timeout=0) + + Returns True if `RMT.write_pulses` has completed. + + 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. + +.. 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. + +.. method:: RMT.write_pulses(pulses, start) + + Begin sending *pulses*, a list or tuple defining the stream of pulses. The + length of each pulse is defined by a number to be multiplied by the channel + resolution ``(1 / (source_freq / clock_div))``. *start* defines whether the + stream starts at 0 or 1. + + The Ultra-Low-Power co-processor -------------------------------- diff --git a/docs/library/lcd160cr.rst b/docs/library/lcd160cr.rst index bb8e6da22..e31ed9465 100644 --- a/docs/library/lcd160cr.rst +++ b/docs/library/lcd160cr.rst @@ -340,7 +340,7 @@ Advanced commands .. method:: LCD160CR.set_scroll_win_param(win, param, value) Set a single parameter of a scrolling window region: - + - *win* is the window id, 0..8. - *param* is the parameter number to configure, 0..7, and corresponds to the parameters in the `set_scroll_win` method. diff --git a/docs/library/machine.RTC.rst b/docs/library/machine.RTC.rst index 95fa2b4ce..548ad007e 100644 --- a/docs/library/machine.RTC.rst +++ b/docs/library/machine.RTC.rst @@ -27,7 +27,7 @@ Methods .. method:: RTC.init(datetime) Initialise the RTC. Datetime is a tuple of the form: - + ``(year, month, day[, hour[, minute[, second[, microsecond[, tzinfo]]]]])`` .. method:: RTC.now() diff --git a/docs/library/machine.TimerWiPy.rst b/docs/library/machine.TimerWiPy.rst index abbcc28ff..904a958c6 100644 --- a/docs/library/machine.TimerWiPy.rst +++ b/docs/library/machine.TimerWiPy.rst @@ -47,9 +47,9 @@ Methods tim.init(Timer.ONE_SHOT, width=32) # one shot 32-bit timer Keyword arguments: - + - ``mode`` can be one of: - + - ``TimerWiPy.ONE_SHOT`` - The timer runs once until the configured period of the channel expires. - ``TimerWiPy.PERIODIC`` - The timer runs periodically at the configured @@ -70,7 +70,7 @@ Methods object is returned (or ``None`` if there is no previous channel). Otherwise, a TimerChannel object is initialized and returned. - + The operating mode is is the one configured to the Timer object that was used to create the channel. diff --git a/docs/library/pyb.ADC.rst b/docs/library/pyb.ADC.rst index a95f2d537..1b16e0c2f 100644 --- a/docs/library/pyb.ADC.rst +++ b/docs/library/pyb.ADC.rst @@ -19,7 +19,7 @@ Usage:: val = adc.read_core_vref() # read MCU VREF val = adc.read_vref() # read MCU supply voltage - + Constructors ------------ diff --git a/docs/library/pyb.Accel.rst b/docs/library/pyb.Accel.rst index 9ade5c5c8..d5c0ca863 100644 --- a/docs/library/pyb.Accel.rst +++ b/docs/library/pyb.Accel.rst @@ -19,7 +19,7 @@ Constructors .. class:: pyb.Accel() Create and return an accelerometer object. - + Methods ------- diff --git a/docs/library/pyb.CAN.rst b/docs/library/pyb.CAN.rst index 09b97a187..ba935abfd 100644 --- a/docs/library/pyb.CAN.rst +++ b/docs/library/pyb.CAN.rst @@ -92,7 +92,7 @@ Methods Force a software restart of the CAN controller without resetting its configuration. - + If the controller enters the bus-off state then it will no longer participate in bus activity. If the controller is not configured to automatically restart (see :meth:`~CAN.init()`) then this method can be used to trigger a restart, diff --git a/docs/library/pyb.ExtInt.rst b/docs/library/pyb.ExtInt.rst index 814217cef..7741cc51e 100644 --- a/docs/library/pyb.ExtInt.rst +++ b/docs/library/pyb.ExtInt.rst @@ -54,7 +54,7 @@ Constructors .. class:: pyb.ExtInt(pin, mode, pull, callback) Create an ExtInt object: - + - ``pin`` is the pin on which to enable the interrupt (can be a pin object or any valid pin name). - ``mode`` can be one of: - ``ExtInt.IRQ_RISING`` - trigger on a rising edge; diff --git a/docs/library/pyb.Flash.rst b/docs/library/pyb.Flash.rst new file mode 100644 index 000000000..2d2f83da1 --- /dev/null +++ b/docs/library/pyb.Flash.rst @@ -0,0 +1,52 @@ +.. currentmodule:: pyb +.. _pyb.Flash: + +class Flash -- access to built-in flash storage +=============================================== + +The Flash class allows direct access to the primary flash device on the pyboard. + +In most cases, to store persistent data on the device, you'll want to use a +higher-level abstraction, for example the filesystem via Python's standard file +API, but this interface is useful to :ref:`customise the filesystem +configuration ` or implement a low-level storage system for your +application. + +Constructors +------------ + +.. class:: pyb.Flash() + + Create and return a block device that represents the flash device presented + to the USB mass storage interface. + + It includes a virtual partition table at the start, and the actual flash + starts at block ``0x100``. + + This constructor is deprecated and will be removed in a future version of MicroPython. + +.. class:: pyb.Flash(\*, start=-1, len=-1) + + Create and return a block device that accesses the flash at the specified offset. The length defaults to the remaining size of the device. + + The *start* and *len* offsets are in bytes, and must be a multiple of the block size (typically 512 for internal flash). + +Methods +------- + +.. method:: Flash.readblocks(block_num, buf) +.. method:: Flash.readblocks(block_num, buf, offset) +.. method:: Flash.writeblocks(block_num, buf) +.. method:: Flash.writeblocks(block_num, buf, offset) +.. method:: Flash.ioctl(cmd, arg) + + These methods implement the simple and :ref:`extended + ` block protocol defined by + :class:`uos.AbstractBlockDev`. + +Hardware Note +------------- + +On boards with external spiflash (e.g. Pyboard D), the MicroPython firmware will +be configured to use that as the primary flash storage. On all other boards, the +internal flash inside the :term:`MCU` will be used. diff --git a/docs/library/pyb.LCD.rst b/docs/library/pyb.LCD.rst index 5ab127edc..018902ca6 100644 --- a/docs/library/pyb.LCD.rst +++ b/docs/library/pyb.LCD.rst @@ -63,13 +63,13 @@ Methods .. method:: LCD.fill(colour) Fill the screen with the given colour (0 or 1 for white or black). - + This method writes to the hidden buffer. Use ``show()`` to show the buffer. .. method:: LCD.get(x, y) Get the pixel at the position ``(x, y)``. Returns 0 or 1. - + This method reads from the visible buffer. .. method:: LCD.light(value) @@ -79,7 +79,7 @@ Methods .. method:: LCD.pixel(x, y, colour) Set the pixel at ``(x, y)`` to the given colour (0 or 1). - + This method writes to the hidden buffer. Use ``show()`` to show the buffer. .. method:: LCD.show() @@ -89,7 +89,7 @@ Methods .. method:: LCD.text(str, x, y, colour) Draw the given text to the position ``(x, y)`` using the given colour (0 or 1). - + This method writes to the hidden buffer. Use ``show()`` to show the buffer. .. method:: LCD.write(str) diff --git a/docs/library/pyb.LED.rst b/docs/library/pyb.LED.rst index 1ab73a69c..d3ab965e7 100644 --- a/docs/library/pyb.LED.rst +++ b/docs/library/pyb.LED.rst @@ -13,7 +13,7 @@ Constructors .. class:: pyb.LED(id) Create an LED object associated with the given LED: - + - ``id`` is the LED number, 1-4. diff --git a/docs/library/pyb.RTC.rst b/docs/library/pyb.RTC.rst index 286268655..4c736597c 100644 --- a/docs/library/pyb.RTC.rst +++ b/docs/library/pyb.RTC.rst @@ -28,13 +28,13 @@ Methods .. method:: RTC.datetime([datetimetuple]) Get or set the date and time of the RTC. - + With no arguments, this method returns an 8-tuple with the current date and time. With 1 argument (being an 8-tuple) it sets the date and time (and ``subseconds`` is reset to 255). - + The 8-tuple has the following format: - + (year, month, day, weekday, hours, minutes, seconds, subseconds) ``weekday`` is 1-7 for Monday through Sunday. diff --git a/docs/library/pyb.Timer.rst b/docs/library/pyb.Timer.rst index 977ba8890..bb7e7e52d 100644 --- a/docs/library/pyb.Timer.rst +++ b/docs/library/pyb.Timer.rst @@ -112,7 +112,7 @@ Methods .. method:: Timer.deinit() Deinitialises the timer. - + Disables the callback (and the associated irq). Disables any channel callbacks (and the associated irq). @@ -191,7 +191,7 @@ Methods - Read the encoder value using the timer.counter() method. - Only works on CH1 and CH2 (and not on CH1N or CH2N) - The channel number is ignored when setting the encoder mode. - + PWM Example:: timer = pyb.Timer(2, freq=1000) diff --git a/docs/library/pyb.USB_HID.rst b/docs/library/pyb.USB_HID.rst index 702704435..649dc3df4 100644 --- a/docs/library/pyb.USB_HID.rst +++ b/docs/library/pyb.USB_HID.rst @@ -24,11 +24,11 @@ Methods .. method:: USB_HID.recv(data, \*, timeout=5000) Receive data on the bus: - + - ``data`` can be an integer, which is the number of bytes to receive, or a mutable buffer, which will be filled with received bytes. - ``timeout`` is the timeout in milliseconds to wait for the receive. - + 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. diff --git a/docs/library/pyb.USB_VCP.rst b/docs/library/pyb.USB_VCP.rst index 94ea41848..b16924cf6 100644 --- a/docs/library/pyb.USB_VCP.rst +++ b/docs/library/pyb.USB_VCP.rst @@ -92,21 +92,21 @@ Methods .. method:: USB_VCP.recv(data, \*, timeout=5000) Receive data on the bus: - + - ``data`` can be an integer, which is the number of bytes to receive, or a mutable buffer, which will be filled with received bytes. - ``timeout`` is the timeout in milliseconds to wait for the receive. - + 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) Send data over the USB VCP: - + - ``data`` is the data to send (an integer to send, or a buffer object). - ``timeout`` is the timeout in milliseconds to wait for the send. - + Return value: number of bytes sent. diff --git a/docs/library/pyb.rst b/docs/library/pyb.rst index 9ba7e991e..34103195d 100644 --- a/docs/library/pyb.rst +++ b/docs/library/pyb.rst @@ -20,7 +20,7 @@ Time related functions .. function:: millis() Returns the number of milliseconds since the board was last reset. - + The result is always a MicroPython smallint (31-bit signed number), so after 2^30 milliseconds (about 12.4 days) this will start to return negative numbers. @@ -32,7 +32,7 @@ Time related functions .. function:: micros() Returns the number of microseconds since the board was last reset. - + The result is always a MicroPython smallint (31-bit signed number), so after 2^30 microseconds (about 17.8 minutes) this will start to return negative numbers. @@ -44,10 +44,10 @@ Time related functions .. function:: elapsed_millis(start) Returns the number of milliseconds which have elapsed since ``start``. - + This function takes care of counter wrap, and always returns a positive number. This means it can be used to measure periods up to about 12.4 days. - + Example:: start = pyb.millis() @@ -57,10 +57,10 @@ Time related functions .. function:: elapsed_micros(start) Returns the number of microseconds which have elapsed since ``start``. - + This function takes care of counter wrap, and always returns a positive number. This means it can be used to measure periods up to about 17.8 minutes. - + Example:: start = pyb.micros() @@ -309,6 +309,7 @@ Classes pyb.CAN.rst pyb.DAC.rst pyb.ExtInt.rst + pyb.Flash.rst pyb.I2C.rst pyb.LCD.rst pyb.LED.rst diff --git a/docs/library/ubluetooth.rst b/docs/library/ubluetooth.rst index 2d3af1bb6..1a62d1e4b 100644 --- a/docs/library/ubluetooth.rst +++ b/docs/library/ubluetooth.rst @@ -12,6 +12,9 @@ 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. +.. note:: This module is still under development and its classes, functions, + methods and constants are subject to change. + class BLE --------- @@ -32,14 +35,27 @@ Configuration The radio must be made active before using any other methods on this class. -.. method:: BLE.config(name) +.. method:: BLE.config('param') + BLE.config(param=value, ...) - Queries a configuration value by *name*. Currently supported values are: + 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 + queried at a time. To set values use the keyword syntax, and one ore more + parameter can be set at a time. + + 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. + - ``'rxbuf'``: Set the size in bytes of the internal buffer used to store + 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. + Event Handling -------------- @@ -310,12 +326,23 @@ Central Role (GATT Client) On success, the ``_IRQ_GATTC_READ_RESULT`` event will be raised. -.. method:: BLE.gattc_write(conn_handle, value_handle, data) +.. method:: BLE.gattc_write(conn_handle, value_handle, data, mode=0) Issue a remote write to a connected peripheral for the specified characteristic or descriptor handle. - On success, the ``_IRQ_GATTC_WRITE_STATUS`` event will be raised. + 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 + returned, and no event will be raised. + * ``mode=1`` is a write-with-response: the remote peripheral 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. class UUID diff --git a/docs/library/uos.rst b/docs/library/uos.rst index d8d8b7a97..bb7e491cc 100644 --- a/docs/library/uos.rst +++ b/docs/library/uos.rst @@ -178,6 +178,43 @@ represented by VFS classes. Build a FAT filesystem on *block_dev*. +.. class:: VfsLfs1(block_dev) + + Create a filesystem object that uses the `littlefs v1 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`. + + See :ref:`filesystem` for more information. + + .. staticmethod:: mkfs(block_dev) + + 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) + + 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`. + + See :ref:`filesystem` for more information. + + .. staticmethod:: mkfs(block_dev) + + Build a Lfs2 filesystem on *block_dev*. + + .. note:: There are reports of littlefs v2 failing in certain situations, + for details see `littlefs issue 295`_. + +.. _littlefs v1 filesystem format: https://github.com/ARMmbed/littlefs/tree/v1 +.. _littlefs v2 filesystem format: https://github.com/ARMmbed/littlefs +.. _littlefs issue 295: https://github.com/ARMmbed/littlefs/issues/295 +.. _littlefs issue 347: https://github.com/ARMmbed/littlefs/issues/347 + Block devices ------------- @@ -187,9 +224,19 @@ implementation of this class will usually allow access to the memory-like functionality a piece of hardware (like flash memory). A block device can be used by a particular filesystem driver to store the data for its filesystem. +.. _block-device-interface: + +Simple and extended interface +............................. + There are two compatible signatures for the ``readblocks`` and ``writeblocks`` methods (see below), in order to support a variety of use cases. A given block -device may implement one form or the other, or both at the same time. +device may implement one form or the other, or both at the same time. The second +form (with the offset parameter) is referred to as the "extended interface". + +Some filesystems (such as littlefs) that require more control over write +operations, for example writing to sub-block regions without erasing, may require +that the block device supports the extended interface. .. class:: AbstractBlockDev(...) @@ -247,64 +294,5 @@ device may implement one form or the other, or both at the same time. (*arg* is unused) - 6 -- erase a block, *arg* is the block number to erase -By way of example, the following class will implement a block device that stores -its data in RAM using a ``bytearray``:: - - class RAMBlockDev: - def __init__(self, block_size, num_blocks): - self.block_size = block_size - self.data = bytearray(block_size * num_blocks) - - def readblocks(self, block_num, buf): - for i in range(len(buf)): - buf[i] = self.data[block_num * self.block_size + i] - - def writeblocks(self, block_num, buf): - for i in range(len(buf)): - self.data[block_num * self.block_size + i] = buf[i] - - def ioctl(self, op, arg): - if op == 4: # get number of blocks - return len(self.data) // self.block_size - if op == 5: # get block size - return self.block_size - -It can be used as follows:: - - import uos - - bdev = RAMBlockDev(512, 50) - uos.VfsFat.mkfs(bdev) - vfs = uos.VfsFat(bdev) - uos.mount(vfs, '/ramdisk') - -An example of a block device that supports both signatures and behaviours of -the :meth:`readblocks` and :meth:`writeblocks` methods is:: - - class RAMBlockDev: - def __init__(self, block_size, num_blocks): - self.block_size = block_size - self.data = bytearray(block_size * num_blocks) - - def readblocks(self, block, buf, offset=0): - addr = block_num * self.block_size + offset - for i in range(len(buf)): - buf[i] = self.data[addr + i] - - def writeblocks(self, block_num, buf, offset=None): - if offset is None: - # do erase, then write - for i in range(len(buf) // self.block_size): - self.ioctl(6, block_num + i) - offset = 0 - addr = block_num * self.block_size + offset - 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.block_size - if op == 5: # block size - return self.block_size - if op == 6: # block erase - return 0 +See :ref:`filesystem` for example implementations of block devices using both +protocols. diff --git a/docs/pyboard/quickref.rst b/docs/pyboard/quickref.rst index b1195ce7c..3dbd09304 100644 --- a/docs/pyboard/quickref.rst +++ b/docs/pyboard/quickref.rst @@ -66,7 +66,7 @@ See :ref:`pyb.LED `. :: led.toggle() led.on() led.off() - + # LEDs 3 and 4 support PWM intensity (0-255) LED(4).intensity() # get intensity LED(4).intensity(128) # set intensity to half diff --git a/docs/pyboard/tutorial/amp_skin.rst b/docs/pyboard/tutorial/amp_skin.rst index 697637f9d..bcb583261 100644 --- a/docs/pyboard/tutorial/amp_skin.rst +++ b/docs/pyboard/tutorial/amp_skin.rst @@ -60,7 +60,7 @@ on your pyboard (either on the flash or the SD card in the top-level directory). or to convert any file you have with the command:: avconv -i original.wav -ar 22050 -codec pcm_u8 test.wav - + Then you can do:: >>> import wave diff --git a/docs/pyboard/tutorial/fading_led.rst b/docs/pyboard/tutorial/fading_led.rst index 8303c9603..79648bee1 100644 --- a/docs/pyboard/tutorial/fading_led.rst +++ b/docs/pyboard/tutorial/fading_led.rst @@ -27,10 +27,10 @@ For this tutorial, we will use the ``X1`` pin. Connect one end of the resistor t Code ---- By examining the :ref:`pyboard_quickref`, we see that ``X1`` is connected to channel 1 of timer 5 (``TIM5 CH1``). Therefore we will first create a ``Timer`` object for timer 5, then create a ``TimerChannel`` object for channel 1:: - + from pyb import Timer from time import sleep - + # timer 5 will be created with a frequency of 100 Hz tim = pyb.Timer(5, freq=100) tchannel = tim.channel(1, Timer.PWM, pin=pyb.Pin.board.X1, pulse_width=0) @@ -47,16 +47,16 @@ To achieve the fading effect shown at the beginning of this tutorial, we want to # how much to change the pulse-width by each step wstep = 1500 cur_width = min_width - + while True: tchannel.pulse_width(cur_width) - + # this determines how often we change the pulse-width. It is # analogous to frames-per-second sleep(0.01) - + cur_width += wstep - + if cur_width > max_width: cur_width = min_width @@ -67,11 +67,11 @@ If we want to have a breathing effect, where the LED fades from dim to bright th while True: tchannel.pulse_width(cur_width) - + sleep(0.01) - + cur_width += wstep - + if cur_width > max_width: cur_width = max_width wstep *= -1 diff --git a/docs/pyboard/tutorial/leds.rst b/docs/pyboard/tutorial/leds.rst index 2b76d17cd..05f3b619e 100644 --- a/docs/pyboard/tutorial/leds.rst +++ b/docs/pyboard/tutorial/leds.rst @@ -17,7 +17,7 @@ This is all very well but we would like this process to be automated. Open the f pyb.delay(1000) When you save, the red light on the pyboard should turn on for about a second. To run the script, do a soft reset (CTRL-D). The pyboard will then restart and you should see a green light continuously flashing on and off. Success, the first step on your path to building an army of evil robots! When you are bored of the annoying flashing light then press CTRL-C at your terminal to stop it running. - + So what does this code do? First we need some terminology. Python is an object-oriented language, almost everything in python is a *class* and when you create an instance of a class you get an *object*. Classes have *methods* associated to them. A method (also called a member function) is used to interact with or control the object. The first line of code creates an LED object which we have then called led. When we create the object, it takes a single parameter which must be between 1 and 4, corresponding to the 4 LEDs on the board. The pyb.LED class has three important member functions that we will use: on(), off() and toggle(). The other function that we use is pyb.delay() this simply waits for a given time in milliseconds. Once we have created the LED object, the statement while True: creates an infinite loop which toggles the led between on and off and waits for 1 second. diff --git a/docs/pyboard/tutorial/repl.rst b/docs/pyboard/tutorial/repl.rst index 3853b1578..973d1846a 100644 --- a/docs/pyboard/tutorial/repl.rst +++ b/docs/pyboard/tutorial/repl.rst @@ -41,7 +41,7 @@ Mac OS X Open a terminal and run:: screen /dev/tty.usbmodem* - + When you are finished and want to exit screen, type CTRL-A CTRL-\\. Linux @@ -50,7 +50,7 @@ Linux Open a terminal and run:: screen /dev/ttyACM0 - + You can also try ``picocom`` or ``minicom`` instead of screen. You may have to use ``/dev/ttyACM1`` or a higher number for ``ttyACM``. And, you may need to give yourself the correct permissions to access this devices (eg group ``uucp`` or ``dialout``, diff --git a/docs/reference/asm_thumb2_float.rst b/docs/reference/asm_thumb2_float.rst index 4acb734ee..46560413c 100644 --- a/docs/reference/asm_thumb2_float.rst +++ b/docs/reference/asm_thumb2_float.rst @@ -31,7 +31,7 @@ Arithmetic * vsqrt(Sd, Sm) ``Sd = sqrt(Sm)`` Registers may be identical: ``vmul(S0, S0, S0)`` will execute ``S0 = S0*S0`` - + Move between ARM core and FPU registers --------------------------------------- @@ -40,7 +40,7 @@ Move between ARM core and FPU registers The FPU has a register known as FPSCR, similar to the ARM core's APSR, which stores condition codes plus other data. The following instructions provide access to this. - + * vmrs(APSR\_nzcv, FPSCR) Move the floating-point N, Z, C, and V flags to the APSR N, Z, C, and V flags. diff --git a/docs/reference/filesystem.rst b/docs/reference/filesystem.rst new file mode 100644 index 000000000..cd8600928 --- /dev/null +++ b/docs/reference/filesystem.rst @@ -0,0 +1,290 @@ +.. _filesystem: + +Working with filesystems +======================== + +.. contents:: + +This tutorial describes how MicroPython provides an on-device filesystem, +allowing standard Python file I/O methods to be used with persistent storage. + +MicroPython automatically creates a default configuration and auto-detects the +primary filesystem, so this tutorial will be mostly useful if you want to modify +the partitioning, filesystem type, or use custom block devices. + +The filesystem is typically backed by internal flash memory on the device, but +can also use external flash, RAM, or a custom block device. + +On some ports (e.g. STM32), the filesystem may also be available over USB MSC to +a host PC. :ref:`pyboard_py` also provides a way for the host PC to access to +the filesystem on all ports. + +Note: This is mainly for use on bare-metal ports like STM32 and ESP32. On ports +with an operating system (e.g. the Unix port) the filesystem is provided by the +host OS. + +VFS +--- + +MicroPython implements a Unix-like Virtual File System (VFS) layer. All mounted +filesystems are combined into a single virtual filesystem, starting at the root +``/``. Filesystems are mounted into directories in this structure, and at +startup the working directory is changed to where the primary filesystem is +mounted. + +On STM32 / Pyboard, the internal flash is mounted at ``/flash``, and optionally +the SDCard at ``/sd``. On ESP8266/ESP32, the primary filesystem is mounted at +``/``. + +Block devices +------------- + +A block device is an instance of a class that implements the +:class:`uos.AbstractBlockDev` protocol. + +Built-in block devices +~~~~~~~~~~~~~~~~~~~~~~ + +Ports provide built-in block devices to access their primary flash. + +On power-on, MicroPython will attempt to detect the filesystem on the default +flash and configure and mount it automatically. If no filesystem is found, +MicroPython will attempt to create a FAT filesystem spanning the entire flash. +Ports can also provide a mechanism to "factory reset" the primary flash, usually +by some combination of button presses at power on. + +STM32 / Pyboard +............... + +The :ref:`pyb.Flash ` class provides access to the internal flash. On some +boards which have larger external flash (e.g. Pyboard D), it will use that +instead. The ``start`` kwarg should always be specified, i.e. +``pyb.Flash(start=0)``. + +Note: For backwards compatibility, when constructed with no arguments (i.e. +``pyb.Flash()``), it only implements the simple block interface and reflects the +virtual device presented to USB MSC (i.e. it includes a virtual partition table +at the start). + +ESP8266 +....... + +The internal flash is exposed as a block device object which is created in the +``flashbdev`` module on start up. This object is by default added as a global +variable so it can usually be accessed simply as ``bdev``. This implements the +extended interface. + +ESP32 +..... + +The :class:`esp32.Partition` class implements a block device for partitions +defined for the board. Like ESP8266, there is a global variable ``bdev`` which +points to the default partition. This implements the extended interface. + +Custom block devices +~~~~~~~~~~~~~~~~~~~~ + +The following class implements a simple block device that stores its data in +RAM using a ``bytearray``:: + + class RAMBlockDev: + def __init__(self, block_size, num_blocks): + self.block_size = block_size + self.data = bytearray(block_size * num_blocks) + + def readblocks(self, block_num, buf): + for i in range(len(buf)): + buf[i] = self.data[block_num * self.block_size + i] + + def writeblocks(self, block_num, buf): + for i in range(len(buf)): + self.data[block_num * self.block_size + i] = buf[i] + + def ioctl(self, op, arg): + if op == 4: # get number of blocks + return len(self.data) // self.block_size + if op == 5: # get block size + return self.block_size + +It can be used as follows:: + + import os + + bdev = RAMBlockDev(512, 50) + os.VfsFat.mkfs(bdev) + os.mount(bdev, '/ramdisk') + +An example of a block device that supports both the simple and extended +interface (i.e. both signatures and behaviours of the +:meth:`uos.AbstractBlockDev.readblocks` and +:meth:`uos.AbstractBlockDev.writeblocks` methods) is:: + + class RAMBlockDev: + def __init__(self, block_size, num_blocks): + self.block_size = block_size + self.data = bytearray(block_size * num_blocks) + + def readblocks(self, block_num, buf, offset=0): + addr = block_num * self.block_size + offset + for i in range(len(buf)): + buf[i] = self.data[addr + i] + + def writeblocks(self, block_num, buf, offset=None): + if offset is None: + # do erase, then write + for i in range(len(buf) // self.block_size): + self.ioctl(6, block_num + i) + offset = 0 + addr = block_num * self.block_size + offset + 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.block_size + if op == 5: # block size + return self.block_size + if op == 6: # block erase + return 0 + +As it supports the extended interface, it can be used with :class:`littlefs +`:: + + import os + + bdev = RAMBlockDev(512, 50) + os.VfsLfs2.mkfs(bdev) + os.mount(bdev, '/ramdisk') + +Once mounted, the filesystem (regardless of its type) can be used as it +normally would be used from Python code, for example:: + + with open('/ramdisk/hello.txt', 'w') as f: + f.write('Hello world') + print(open('/ramdisk/hello.txt').read()) + +Filesystems +----------- + +MicroPython ports can provide implementations of :class:`FAT `, +:class:`littlefs v1 ` and :class:`littlefs v2 `. + +The following table shows which filesystems are included in the firmware by +default for given port/board combinations, however they can be optionally +enabled in a custom firmware build. + +==================== ===== =========== =========== +Board FAT littlefs v1 littlefs v2 +==================== ===== =========== =========== +pyboard 1.0, 1.1, D Yes No Yes +Other STM32 Yes No No +ESP8266 Yes No No +ESP32 Yes No Yes +==================== ===== =========== =========== + +FAT +~~~ + +The main advantage of the FAT filesystem is that it can be accessed over USB MSC +on supported boards (e.g. STM32) without any additional drivers required on the +host PC. + +However, FAT is not tolerant to power failure during writes and this can lead to +filesystem corruption. For applications that do not require USB MSC, it is +recommended to use littlefs instead. + +To format the entire flash using FAT:: + + # ESP8266 and ESP32 + import os + os.umount('/') + os.VfsFat.mkfs(bdev) + os.mount(bdev, '/') + + # STM32 + import os, pyb + os.umount('/flash') + os.VfsFat.mkfs(pyb.Flash(start=0)) + os.mount(pyb.Flash(start=0), '/flash') + os.chdir('/flash') + +Littlefs +~~~~~~~~ + +Littlefs_ is a filesystem designed for flash-based devices, and is much more +resistant to filesystem corruption. + +.. note:: There are reports of littlefs v1 and v2 failing in certain + situations, for details see `littlefs issue 347`_ and + `littlefs issue 295`_. + +Note: It can be still be accessed over USB MSC using the `littlefs FUSE +driver`_. Note that you must use the ``-b=4096`` option to override the block +size. + +.. _littlefs FUSE driver: https://github.com/ARMmbed/littlefs-fuse/tree/master/littlefs +.. _Littlefs: https://github.com/ARMmbed/littlefs +.. _littlefs issue 295: https://github.com/ARMmbed/littlefs/issues/295 +.. _littlefs issue 347: https://github.com/ARMmbed/littlefs/issues/347 + +To format the entire flash using littlefs v2:: + + # ESP8266 and ESP32 + import os + os.umount('/') + os.VfsLfs2.mkfs(bdev) + os.mount(bdev, '/') + + # STM32 + import os, pyb + os.umount('/flash') + os.VfsLfs2.mkfs(pyb.Flash(start=0)) + os.mount(pyb.Flash(start=0), '/flash') + os.chdir('/flash') + +Hybrid (STM32) +~~~~~~~~~~~~~~ + +By using the ``start`` and ``len`` kwargs to :class:`pyb.Flash`, you can create +block devices spanning a subset of the flash device. + +For example, to configure the first 256kiB as FAT (and available over USB MSC), +and the remainder as littlefs:: + + import os, pyb + os.umount('/flash') + p1 = pyb.Flash(start=0, len=256*1024) + p2 = pyb.Flash(start=256*1024) + os.VfsFat.mkfs(p1) + os.VfsLfs2.mkfs(p2) + os.mount(p1, '/flash') + os.mount(p2, '/data') + os.chdir('/flash') + +This might be useful to make your Python files, configuration and other +rarely-modified content available over USB MSC, but allowing for frequently +changing application data to reside on littlefs with better resilience to power +failure, etc. + +The partition at offset ``0`` will be mounted automatically (and the filesystem +type automatically detected), but you can add:: + + import os, pyb + p2 = pyb.Flash(start=256*1024) + os.mount(p2, '/data') + +to ``boot.py`` to mount the data partition. + +Hybrid (ESP32) +~~~~~~~~~~~~~~ + +On ESP32, if you build custom firmware, you can modify ``partitions.csv`` to +define an arbitrary partition layout. + +At boot, the partition named "vfs" will be mounted at ``/`` by default, but any +additional partitions can be mounted in your ``boot.py`` using:: + + import esp32, os + p = esp32.Partition.find(esp32.Partition.TYPE_DATA, label='foo') + os.mount(p, '/foo') + diff --git a/docs/reference/index.rst b/docs/reference/index.rst index d0c7f69de..ed50bf0bf 100644 --- a/docs/reference/index.rst +++ b/docs/reference/index.rst @@ -21,8 +21,11 @@ implementation and the best practices to use them. glossary.rst repl.rst + mpyfiles.rst isr_rules.rst speed_python.rst constrained.rst packages.rst asm_thumb2_index.rst + filesystem.rst + pyboard.py.rst diff --git a/docs/reference/mpyfiles.rst b/docs/reference/mpyfiles.rst new file mode 100644 index 000000000..4791784ac --- /dev/null +++ b/docs/reference/mpyfiles.rst @@ -0,0 +1,178 @@ +.. _mpy_files: + +MicroPython .mpy files +====================== + +MicroPython defines the concept of an .mpy file which is a binary container +file format that holds precompiled code, and which can be imported like a +normal .py module. The file ``foo.mpy`` can be imported via ``import foo``, +as long as ``foo.mpy`` can be found in the usual way by the import machinery. +Usually, each directory listed in ``sys.path`` is searched in order. When +searching a particular directory ``foo.py`` is looked for first and if that +is not found then ``foo.mpy`` is looked for, then the search continues in the +next directory if neither is found. As such, ``foo.py`` will take precedence +over ``foo.mpy``. + +These .mpy files can contain bytecode which is usually generated from Python +source files (.py files) via the ``mpy-cross`` program. For some architectures +an .mpy file can also contain native machine code, which can be generated in +a variety of ways, most notably from C source code. + +Versioning and compatibility of .mpy files +------------------------------------------ + +A given .mpy file may or may not be compatible with a given MicroPython system. +Compatibility is based on the following: + +* Version of the .mpy file: the version of the file must match the version + supported by the system loading it. + +* Bytecode features used in the .mpy file: there are two bytecode features + which must match between the file and the system: unicode support and + inline caching of map lookups in the bytecode. + +* Small integer bits: the .mpy file will require a minimum number of bits in + a small integer and the system loading it must support at least this many + bits. + +* Qstr compression window size: the .mpy file will require a minimum window + size for qstr decompression and the system loading it must have a window + greater or equal to this size. + +* Native architecture: if the .mpy file contains native machine code then + it will specify the architecture of that machine code and the system + loading it must support execution of that architecture's code. + +If a MicroPython system supports importing .mpy files then the +``sys.implementation.mpy`` field will exist and return an integer which +encodes the version (lower 8 bits), features and native architecture. + +Trying to import an .mpy file that fails one of the first four tests will +raise ``ValueError('incompatible .mpy file')``. Trying to import an .mpy +file that fails the native architecture test (if it contains native machine +code) will raise ``ValueError('incompatible .mpy arch')``. + +If importing an .mpy file fails then try the following: + +* Determine the .mpy version and flags supported by your MicroPython system + by executing:: + + import sys + sys_mpy = sys.implementation.mpy + arch = [None, 'x86', 'x64', + 'armv6', 'armv6m', 'armv7m', 'armv7em', 'armv7emsp', 'armv7emdp', + 'xtensa', 'xtensawin'][sys_mpy >> 10] + print('mpy version:', sys_mpy & 0xff) + print('mpy flags:', end='') + if arch: + print(' -march=' + arch, end='') + if sys_mpy & 0x100: + print(' -mcache-lookup-bc', end='') + if not sys_mpy & 0x200: + print(' -mno-unicode', end='') + print() + +* Check the validity of the .mpy file by inspecting the first two bytes of + the file. The first byte should be an uppercase 'M' and the second byte + will be the version number, which should match the system version from above. + If it doesn't match then rebuild the .mpy file. + +* Check if the system .mpy version matches the version emitted by ``mpy-cross`` + that was used to build the .mpy file, found by ``mpy-cross --version``. + If it doesn't match then recompile ``mpy-cross`` from the Git repository + checked out at the tag (or hash) reported by ``mpy-cross --version``. + +* Make sure you are using the correct ``mpy-cross`` flags, found by the code + above, or by inspecting the ``MPY_CROSS_FLAGS`` Makefile variable for the + port that you are using. + +The following table shows the correspondence between MicroPython release +and .mpy version. + +=================== ============ +MicroPython release .mpy version +=================== ============ +v1.12 and up 5 +v1.11 4 +v1.9.3 - v1.10 3 +v1.9 - v1.9.2 2 +v1.5.1 - v1.8.7 0 +=================== ============ + +For completeness, the next table shows the Git commit of the main +MicroPython repository at which the .mpy version was changed. + +=================== ======================================== +.mpy version change Git commit +=================== ======================================== +4 to 5 5716c5cf65e9b2cb46c2906f40302401bdd27517 +3 to 4 9a5f92ea72754c01cc03e5efcdfe94021120531e +2 to 3 ff93fd4f50321c6190e1659b19e64fef3045a484 +1 to 2 dd11af209d226b7d18d5148b239662e30ed60bad +0 to 1 6a11048af1d01c78bdacddadd1b72dc7ba7c6478 +initial version 0 d8c834c95d506db979ec871417de90b7951edc30 +=================== ======================================== + +Binary encoding of .mpy files +----------------------------- + +MicroPython .mpy files are a binary container format with code objects +stored internally in a nested hierarchy. To keep files small while still +providing a large range of possible values it uses the concept of a +variably-encoded-unsigned-integer (vuint) in many places. Similar to utf-8 +encoding, this encoding stores 7 bits per byte with the 8th bit (MSB) set +if one or more bytes follow. The bits of the unsigned integer are stored +in the vuint in LSB form. + +The top-level of an .mpy file consists of two parts: + +* The header. + +* The raw-code for the outer scope of the module. + This outer scope is executed when the .mpy file is imported. + +The header +~~~~~~~~~~ + +The .mpy header is: + +====== ================================ +size field +====== ================================ +byte value 0x4d (ASCII 'M') +byte .mpy version number +byte feature flags +byte number of bits in a small int +vuint size of qstr window +====== ================================ + +Raw code elements +~~~~~~~~~~~~~~~~~ + +A raw-code element contains code, either bytecode or native machine code. Its +contents are: + +====== ================================ +size field +====== ================================ +vuint type and size +... code (bytecode or machine code) +vuint number of constant objects +vuint number of sub-raw-code elements +... constant objects +... sub-raw-code elements +====== ================================ + +The first vuint in a raw-code element encodes the type of code stored in this +element (the two least-significant bits), and the decompressed length of the code +(the amount of RAM to allocate for it). + +Following the vuint comes the code itself. In the case of bytecode it also contains +compressed qstr values. + +Following the code comes a vuint counting the number of constant objects, and +another vuint counting the number of sub-raw-code elements. + +The constant objects are then stored next. + +Finally any sub-raw-code elements are stored, recursively. diff --git a/docs/reference/pyboard.py.rst b/docs/reference/pyboard.py.rst new file mode 100644 index 000000000..60d7509af --- /dev/null +++ b/docs/reference/pyboard.py.rst @@ -0,0 +1,133 @@ +.. _pyboard_py: + +The pyboard.py tool +=================== + +This is a standalone Python tool that runs on your PC that provides a way to: + +* Quickly run a Python script or command on a MicroPython device. This is useful + while developing MicroPython programs to quickly test code without needing to + copy files to/from the device. + +* Access the filesystem on a device. This allows you to deploy your code to the + device (even if the board doesn't support USB MSC). + +Despite the name, ``pyboard.py`` works on all MicroPython ports that support the +raw REPL (including STM32, ESP32, ESP8266, NRF). + +You can download the latest version from `GitHub +`_. The +only dependency is the ``pyserial`` library which can be installed from PiPy or +your system package manager. + +Running ``pyboard.py --help`` gives the following output: + +.. code-block:: text + + usage: pyboard [-h] [--device DEVICE] [-b BAUDRATE] [-u USER] + [-p PASSWORD] [-c COMMAND] [-w WAIT] [--follow] [-f] + [files [files ...]] + + Run scripts on the pyboard. + + positional arguments: + files input files + + optional arguments: + -h, --help show this help message and exit + --device DEVICE the serial device or the IP address of the + pyboard + -b BAUDRATE, --baudrate BAUDRATE + the baud rate of the serial device + -u USER, --user USER the telnet login username + -p PASSWORD, --password PASSWORD + the telnet login password + -c COMMAND, --command COMMAND + program passed in as string + -w WAIT, --wait WAIT seconds to wait for USB connected board to become + available + --follow follow the output after running the scripts + [default if no scripts given] + -f, --filesystem perform a filesystem action + +Running a command on the device +------------------------------- + +This is useful for testing short snippets of code, or to script an interaction +with the device.:: + + $ pyboard.py --device /dev/ttyACM0 -c 'print(1+1)' + 2 + +Running a script on the device +------------------------------ + +If you have a script, ``app.py`` that you want to run on a device, then use:: + + $ pyboard.py --device /dev/ttyACM0 app.py + +Note that this doesn't actually copy app.py to the device's filesystem, it just +loads the code into RAM and executes it. Any output generated by the program +will be displayed. + +If the program app.py does not finish then you'll need to stop ``pyboard.py``, +eg with Ctrl-C. The program ``app.py`` will still continue to run on the +MicroPython device. + +Filesystem access +----------------- + +Using the ``-f`` flag, the following filesystem operations are supported: + +* ``cp src [src...] dest`` Copy files to/from the device. +* ``cat path`` Print the contents of a file on the device. +* ``ls [path]`` List contents of a directory (defaults to current working directory). +* ``rm path`` Remove a file. +* ``mkdir path`` Create a directory. +* ``rmdir path`` Remove a directory. + +The ``cp`` command uses a ``ssh``-like convention for referring to local and +remote files. Any path starting with a ``:`` will be interpreted as on the +device, otherwise it will be local. So:: + + $ pyboard.py --device /dev/ttyACM0 -f cp main.py :main.py + +will copy main.py from the current directory on the PC to a file named main.py +on the device. The filename can be omitted, e.g.:: + + $ pyboard.py --device /dev/ttyACM0 -f cp main.py : + +is equivalent to the above. + +Some more examples:: + + # Copy main.py from the device to the local PC. + $ pyboard.py --device /dev/ttyACM0 -f cp :main.py main.py + # Same, but using . instead. + $ pyboard.py --device /dev/ttyACM0 -f cp :main.py . + + # Copy three files to the device, keeping their names + # and paths (note: `lib` must exist on the device) + $ pyboard.py --device /dev/ttyACM0 -f cp main.py app.py lib/foo.py : + + # Remove a file from the device. + $ pyboard.py --device /dev/ttyACM0 -f rm util.py + + # Print the contents of a file on the device. + $ pyboard.py --device /dev/ttyACM0 -f cat boot.py + ...contents of boot.py... + +Using the pyboard library +------------------------- + +You can also use ``pyboard.py`` as a library for scripting interactions with a +MicroPython board. + +.. code-block:: python + + import pyboard + pyb = pyboard.Pyboard('/dev/ttyACM0', 115200) + pyb.enter_raw_repl() + ret = pyb.exec('print(1+1)') + print(ret) + pyb.exit_raw_repl() diff --git a/docs/wipy/quickref.rst b/docs/wipy/quickref.rst index 6349676cf..a22ea45b5 100644 --- a/docs/wipy/quickref.rst +++ b/docs/wipy/quickref.rst @@ -62,7 +62,7 @@ Timer ``id``'s take values from 0 to 3.:: tim = Timer(0, mode=Timer.PERIODIC) tim_a = tim.channel(Timer.A, freq=1000) tim_a.freq(5) # 5 Hz - + p_out = Pin('GP2', mode=Pin.OUT) tim_a.irq(trigger=Timer.TIMEOUT, handler=lambda t: p_out.toggle()) @@ -75,7 +75,7 @@ See :ref:`machine.Pin ` and :ref:`machine.Timer `. : # timer 1 in PWM mode and width must be 16 buts tim = Timer(1, mode=Timer.PWM, width=16) - + # enable channel A @1KHz with a 50.55% duty cycle tim_a = tim.channel(Timer.A, freq=1000, duty_cycle=5055) diff --git a/docs/wipy/tutorial/repl.rst b/docs/wipy/tutorial/repl.rst index 8c60e2c1d..95b545d09 100644 --- a/docs/wipy/tutorial/repl.rst +++ b/docs/wipy/tutorial/repl.rst @@ -51,7 +51,7 @@ Open a terminal and run:: or:: $ screen /dev/tty.usbmodem* 115200 - + When you are finished and want to exit ``screen``, type CTRL-A CTRL-\\. If your keyboard does not have a \\-key (i.e. you need an obscure combination for \\ like ALT-SHIFT-7) you can remap the ``quit`` command: - create ``~/.screenrc`` diff --git a/drivers/wiznet5k/ethernet/w5200/w5200.c b/drivers/wiznet5k/ethernet/w5200/w5200.c index 8c3780792..a84d3c9fa 100644 --- a/drivers/wiznet5k/ethernet/w5200/w5200.c +++ b/drivers/wiznet5k/ethernet/w5200/w5200.c @@ -52,10 +52,19 @@ #include "w5200.h" +#if WIZCHIP_USE_MAX_BUFFER +// This option is intended to be used when MACRAW mode is enabled, to allow +// the single raw socket to use all the available buffer space. +#define SMASK (16 * 1024 - 1) /* tx buffer mask */ +#define RMASK (16 * 1024 - 1) /* rx buffer mask */ +#define SSIZE (16 * 1024) /* max tx buffer size */ +#define RSIZE (16 * 1024) /* max rx buffer size */ +#else #define SMASK (0x7ff) /* tx buffer mask */ #define RMASK (0x7ff) /* rx buffer mask */ #define SSIZE (2048) /* max tx buffer size */ #define RSIZE (2048) /* max rx buffer size */ +#endif #define TXBUF_BASE (0x8000) #define RXBUF_BASE (0xc000) diff --git a/examples/natmod/btree/Makefile b/examples/natmod/btree/Makefile new file mode 100644 index 000000000..d795102b4 --- /dev/null +++ b/examples/natmod/btree/Makefile @@ -0,0 +1,37 @@ +# Location of top-level MicroPython directory +MPY_DIR = ../../.. + +# Name of module (different to built-in btree so it can coexist) +MOD = btree_$(ARCH) + +# Source files (.c or .py) +SRC = btree_c.c btree_py.py + +# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin) +ARCH = x64 + +BTREE_DIR = $(MPY_DIR)/lib/berkeley-db-1.xx +BTREE_DEFS = -D__DBINTERFACE_PRIVATE=1 -Dmpool_error="(void)" -Dabort=abort_ "-Dvirt_fd_t=void*" $(BTREE_DEFS_EXTRA) +CFLAGS += -I$(BTREE_DIR)/PORT/include +CFLAGS += -Wno-old-style-definition -Wno-sign-compare -Wno-unused-parameter $(BTREE_DEFS) + +SRC += $(addprefix $(realpath $(BTREE_DIR))/,\ + btree/bt_close.c \ + btree/bt_conv.c \ + btree/bt_delete.c \ + btree/bt_get.c \ + btree/bt_open.c \ + btree/bt_overflow.c \ + btree/bt_page.c \ + btree/bt_put.c \ + btree/bt_search.c \ + btree/bt_seq.c \ + btree/bt_split.c \ + btree/bt_utils.c \ + mpool/mpool.c \ + ) + +include $(MPY_DIR)/py/dynruntime.mk + +# btree needs gnu99 defined +CFLAGS += -std=gnu99 diff --git a/examples/natmod/btree/btree_c.c b/examples/natmod/btree/btree_c.c new file mode 100644 index 000000000..c8c5cef47 --- /dev/null +++ b/examples/natmod/btree/btree_c.c @@ -0,0 +1,148 @@ +#define MICROPY_ENABLE_DYNRUNTIME (1) +#define MICROPY_PY_BTREE (1) + +#include "py/dynruntime.h" + +#include + +#if !defined(__linux__) +void *memcpy(void *dst, const void *src, size_t n) { + return mp_fun_table.memmove_(dst, src, n); +} +void *memset(void *s, int c, size_t n) { + return mp_fun_table.memset_(s, c, n); +} +#endif + +void *memmove(void *dest, const void *src, size_t n) { + return mp_fun_table.memmove_(dest, src, n); +} + +void *malloc(size_t n) { + void *ptr = m_malloc(n); + return ptr; +} +void *realloc(void *ptr, size_t n) { + mp_printf(&mp_plat_print, "UNDEF %d\n", __LINE__); + return NULL; +} +void *calloc(size_t n, size_t m) { + void *ptr = m_malloc(n * m); + // memory already cleared by conservative GC + return ptr; +} + +void free(void *ptr) { + m_free(ptr); +} + +void abort_(void) { + nlr_raise(mp_obj_new_exception(mp_load_global(MP_QSTR_RuntimeError))); +} + +int native_errno; +#if defined(__linux__) +int *__errno_location (void) +#else +int *__errno (void) +#endif +{ + return &native_errno; +} + +ssize_t mp_stream_posix_write(void *stream, const void *buf, size_t len) { + mp_obj_base_t* o = stream; + const mp_stream_p_t *stream_p = o->type->protocol; + mp_uint_t out_sz = stream_p->write(MP_OBJ_FROM_PTR(stream), buf, len, &native_errno); + if (out_sz == MP_STREAM_ERROR) { + return -1; + } else { + return out_sz; + } +} + +ssize_t mp_stream_posix_read(void *stream, void *buf, size_t len) { + mp_obj_base_t* o = stream; + const mp_stream_p_t *stream_p = o->type->protocol; + mp_uint_t out_sz = stream_p->read(MP_OBJ_FROM_PTR(stream), buf, len, &native_errno); + if (out_sz == MP_STREAM_ERROR) { + return -1; + } else { + return out_sz; + } +} + +off_t mp_stream_posix_lseek(void *stream, off_t offset, int whence) { + const mp_obj_base_t* o = stream; + const mp_stream_p_t *stream_p = o->type->protocol; + struct mp_stream_seek_t seek_s; + seek_s.offset = offset; + seek_s.whence = whence; + mp_uint_t res = stream_p->ioctl(MP_OBJ_FROM_PTR(stream), MP_STREAM_SEEK, (mp_uint_t)(uintptr_t)&seek_s, &native_errno); + if (res == MP_STREAM_ERROR) { + return -1; + } + return seek_s.offset; +} + +int mp_stream_posix_fsync(void *stream) { + mp_obj_base_t* o = stream; + const mp_stream_p_t *stream_p = o->type->protocol; + mp_uint_t res = stream_p->ioctl(MP_OBJ_FROM_PTR(stream), MP_STREAM_FLUSH, 0, &native_errno); + if (res == MP_STREAM_ERROR) { + return -1; + } + return res; +} + +mp_obj_type_t btree_type; + +#include "extmod/modbtree.c" + +mp_map_elem_t btree_locals_dict_table[8]; +STATIC MP_DEFINE_CONST_DICT(btree_locals_dict, btree_locals_dict_table); + +STATIC mp_obj_t btree_open(size_t n_args, const mp_obj_t *args) { + // Make sure we got a stream object + mp_get_stream_raise(args[0], MP_STREAM_OP_READ | MP_STREAM_OP_WRITE | MP_STREAM_OP_IOCTL); + + BTREEINFO openinfo = {0}; + openinfo.flags = mp_obj_get_int(args[1]); + openinfo.cachesize = mp_obj_get_int(args[2]); + openinfo.psize = mp_obj_get_int(args[3]); + openinfo.minkeypage = mp_obj_get_int(args[4]); + DB *db = __bt_open(MP_OBJ_TO_PTR(args[0]), &btree_stream_fvtable, &openinfo, 0); + if (db == NULL) { + mp_raise_OSError(native_errno); + } + + return MP_OBJ_FROM_PTR(btree_new(db)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(btree_open_obj, 5, 5, btree_open); + +mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *args) { + MP_DYNRUNTIME_INIT_ENTRY + + btree_type.base.type = (void*)&mp_fun_table.type_type; + btree_type.name = MP_QSTR_btree; + btree_type.print = btree_print; + btree_type.getiter = btree_getiter; + btree_type.iternext = btree_iternext; + btree_type.binary_op = btree_binary_op; + btree_type.subscr = btree_subscr; + btree_locals_dict_table[0] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_close), MP_OBJ_FROM_PTR(&btree_close_obj) }; + btree_locals_dict_table[1] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_flush), MP_OBJ_FROM_PTR(&btree_flush_obj) }; + btree_locals_dict_table[2] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_get), MP_OBJ_FROM_PTR(&btree_get_obj) }; + btree_locals_dict_table[3] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_put), MP_OBJ_FROM_PTR(&btree_put_obj) }; + btree_locals_dict_table[4] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_seq), MP_OBJ_FROM_PTR(&btree_seq_obj) }; + btree_locals_dict_table[5] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_keys), MP_OBJ_FROM_PTR(&btree_keys_obj) }; + btree_locals_dict_table[6] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_values), MP_OBJ_FROM_PTR(&btree_values_obj) }; + btree_locals_dict_table[7] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_items), MP_OBJ_FROM_PTR(&btree_items_obj) }; + btree_type.locals_dict = (void*)&btree_locals_dict; + + mp_store_global(MP_QSTR__open, MP_OBJ_FROM_PTR(&btree_open_obj)); + mp_store_global(MP_QSTR_INCL, MP_OBJ_NEW_SMALL_INT(FLAG_END_KEY_INCL)); + mp_store_global(MP_QSTR_DESC, MP_OBJ_NEW_SMALL_INT(FLAG_DESC)); + + MP_DYNRUNTIME_INIT_EXIT +} diff --git a/examples/natmod/btree/btree_py.py b/examples/natmod/btree/btree_py.py new file mode 100644 index 000000000..bd53c084a --- /dev/null +++ b/examples/natmod/btree/btree_py.py @@ -0,0 +1,3 @@ +# Implemented in Python to support keyword arguments +def open(stream, *, flags=0, cachesize=0, pagesize=0, minkeypage=0): + return _open(stream, flags, cachesize, pagesize, minkeypage) diff --git a/examples/natmod/features0/Makefile b/examples/natmod/features0/Makefile new file mode 100644 index 000000000..57490df90 --- /dev/null +++ b/examples/natmod/features0/Makefile @@ -0,0 +1,14 @@ +# Location of top-level MicroPython directory +MPY_DIR = ../../.. + +# Name of module +MOD = features0 + +# Source files (.c or .py) +SRC = features0.c + +# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin) +ARCH = x64 + +# Include to get the rules for compiling and linking the module +include $(MPY_DIR)/py/dynruntime.mk diff --git a/examples/natmod/features0/features0.c b/examples/natmod/features0/features0.c new file mode 100644 index 000000000..1b1867a3b --- /dev/null +++ b/examples/natmod/features0/features0.c @@ -0,0 +1,40 @@ +/* This example demonstrates the following features in a native module: + - defining a simple function exposed to Python + - defining a local, helper C function + - getting and creating integer objects +*/ + +// Include the header file to get access to the MicroPython API +#include "py/dynruntime.h" + +// Helper function to compute factorial +STATIC mp_int_t factorial_helper(mp_int_t x) { + if (x == 0) { + return 1; + } + return x * factorial_helper(x - 1); +} + +// This is the function which will be called from Python, as factorial(x) +STATIC mp_obj_t factorial(mp_obj_t x_obj) { + // Extract the integer from the MicroPython input object + mp_int_t x = mp_obj_get_int(x_obj); + // Calculate the factorial + mp_int_t result = factorial_helper(x); + // Convert the result to a MicroPython integer object and return it + return mp_obj_new_int(result); +} +// Define a Python reference to the function above +STATIC MP_DEFINE_CONST_FUN_OBJ_1(factorial_obj, factorial); + +// This is the entry point and is called when the module is imported +mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *args) { + // This must be first, it sets up the globals dict and other things + MP_DYNRUNTIME_INIT_ENTRY + + // Make the function available in the module's namespace + mp_store_global(MP_QSTR_factorial, MP_OBJ_FROM_PTR(&factorial_obj)); + + // This must be last, it restores the globals dict + MP_DYNRUNTIME_INIT_EXIT +} diff --git a/examples/natmod/features1/Makefile b/examples/natmod/features1/Makefile new file mode 100644 index 000000000..010640daf --- /dev/null +++ b/examples/natmod/features1/Makefile @@ -0,0 +1,14 @@ +# Location of top-level MicroPython directory +MPY_DIR = ../../.. + +# Name of module +MOD = features1 + +# Source files (.c or .py) +SRC = features1.c + +# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin) +ARCH = x64 + +# Include to get the rules for compiling and linking the module +include $(MPY_DIR)/py/dynruntime.mk diff --git a/examples/natmod/features1/features1.c b/examples/natmod/features1/features1.c new file mode 100644 index 000000000..1bfbe50b9 --- /dev/null +++ b/examples/natmod/features1/features1.c @@ -0,0 +1,106 @@ +/* This example demonstrates the following features in a native module: + - defining simple functions exposed to Python + - defining local, helper C functions + - defining constant integers and strings exposed to Python + - getting and creating integer objects + - creating Python lists + - raising exceptions + - allocating memory + - BSS and constant data (rodata) + - relocated pointers in rodata +*/ + +// Include the header file to get access to the MicroPython API +#include "py/dynruntime.h" + +// BSS (zero) data +uint16_t data16[4]; + +// Constant data (rodata) +const uint8_t table8[] = { 0, 1, 1, 2, 3, 5, 8, 13 }; +const uint16_t table16[] = { 0x1000, 0x2000 }; + +// Constant data pointing to BSS/constant data +uint16_t *const table_ptr16a[] = { &data16[0], &data16[1], &data16[2], &data16[3] }; +const uint16_t *const table_ptr16b[] = { &table16[0], &table16[1] }; + +// A simple function that adds its 2 arguments (must be integers) +STATIC mp_obj_t add(mp_obj_t x_in, mp_obj_t y_in) { + mp_int_t x = mp_obj_get_int(x_in); + mp_int_t y = mp_obj_get_int(y_in); + return mp_obj_new_int(x + y); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(add_obj, add); + +// A local helper function (not exposed to Python) +STATIC mp_int_t fibonacci_helper(mp_int_t x) { + if (x < MP_ARRAY_SIZE(table8)) { + return table8[x]; + } else { + return fibonacci_helper(x - 1) + fibonacci_helper(x - 2); + } +} + +// A function which computes Fibonacci numbers +STATIC mp_obj_t fibonacci(mp_obj_t x_in) { + mp_int_t x = mp_obj_get_int(x_in); + if (x < 0) { + mp_raise_ValueError("can't compute negative Fibonacci number"); + } + return mp_obj_new_int(fibonacci_helper(x)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(fibonacci_obj, fibonacci); + +// A function that accesses the BSS data +STATIC mp_obj_t access(size_t n_args, const mp_obj_t *args) { + if (n_args == 0) { + // Create a list holding all items from data16 + mp_obj_list_t *lst = MP_OBJ_TO_PTR(mp_obj_new_list(MP_ARRAY_SIZE(data16), NULL)); + for (int i = 0; i < MP_ARRAY_SIZE(data16); ++i) { + lst->items[i] = mp_obj_new_int(data16[i]); + } + return MP_OBJ_FROM_PTR(lst); + } else if (n_args == 1) { + // Get one item from data16 + mp_int_t idx = mp_obj_get_int(args[0]) & 3; + return mp_obj_new_int(data16[idx]); + } else { + // Set one item in data16 (via table_ptr16a) + mp_int_t idx = mp_obj_get_int(args[0]) & 3; + *table_ptr16a[idx] = mp_obj_get_int(args[1]); + return mp_const_none; + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(access_obj, 0, 2, access); + +// A function that allocates memory and creates a bytearray +STATIC mp_obj_t make_array(void) { + uint16_t *ptr = m_new(uint16_t, MP_ARRAY_SIZE(table_ptr16b)); + for (int i = 0; i < MP_ARRAY_SIZE(table_ptr16b); ++i) { + ptr[i] = *table_ptr16b[i]; + } + return mp_obj_new_bytearray_by_ref(sizeof(uint16_t) * MP_ARRAY_SIZE(table_ptr16b), ptr); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(make_array_obj, make_array); + +// This is the entry point and is called when the module is imported +mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *args) { + // This must be first, it sets up the globals dict and other things + MP_DYNRUNTIME_INIT_ENTRY + + // Messages can be printed as usualy + mp_printf(&mp_plat_print, "initialising module self=%p\n", self); + + // Make the functions available in the module's namespace + mp_store_global(MP_QSTR_add, MP_OBJ_FROM_PTR(&add_obj)); + mp_store_global(MP_QSTR_fibonacci, MP_OBJ_FROM_PTR(&fibonacci_obj)); + mp_store_global(MP_QSTR_access, MP_OBJ_FROM_PTR(&access_obj)); + mp_store_global(MP_QSTR_make_array, MP_OBJ_FROM_PTR(&make_array_obj)); + + // Add some constants to the module's namespace + mp_store_global(MP_QSTR_VAL, MP_OBJ_NEW_SMALL_INT(42)); + mp_store_global(MP_QSTR_MSG, MP_OBJ_NEW_QSTR(MP_QSTR_HELLO_MICROPYTHON)); + + // This must be last, it restores the globals dict + MP_DYNRUNTIME_INIT_EXIT +} diff --git a/examples/natmod/features2/Makefile b/examples/natmod/features2/Makefile new file mode 100644 index 000000000..4fd23c687 --- /dev/null +++ b/examples/natmod/features2/Makefile @@ -0,0 +1,14 @@ +# Location of top-level MicroPython directory +MPY_DIR = ../../.. + +# Name of module +MOD = features2 + +# Source files (.c or .py) +SRC = main.c prod.c test.py + +# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin) +ARCH = x64 + +# Include to get the rules for compiling and linking the module +include $(MPY_DIR)/py/dynruntime.mk diff --git a/examples/natmod/features2/main.c b/examples/natmod/features2/main.c new file mode 100644 index 000000000..de83bf2be --- /dev/null +++ b/examples/natmod/features2/main.c @@ -0,0 +1,83 @@ +/* This example demonstrates the following features in a native module: + - using floats + - defining additional code in Python (see test.py) + - have extra C code in a separate file (see prod.c) +*/ + +// Include the header file to get access to the MicroPython API +#include "py/dynruntime.h" + +// Include the header for auxiliary C code for this module +#include "prod.h" + +// Automatically detect if this module should include double-precision code. +// If double precision is supported by the target architecture then it can +// be used in native module regardless of what float setting the target +// MicroPython runtime uses (being none, float or double). +#if defined(__i386__) || defined(__x86_64__) || (defined(__ARM_FP) && (__ARM_FP & 8)) +#define USE_DOUBLE 1 +#else +#define USE_DOUBLE 0 +#endif + +// A function that uses the default float type configured for the current target +// This default can be overridden by specifying MICROPY_FLOAT_IMPL at the make level +STATIC mp_obj_t add(mp_obj_t x, mp_obj_t y) { + return mp_obj_new_float(mp_obj_get_float(x) + mp_obj_get_float(y)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(add_obj, add); + +// A function that explicitly uses single precision floats +STATIC mp_obj_t add_f(mp_obj_t x, mp_obj_t y) { + return mp_obj_new_float_from_f(mp_obj_get_float_to_f(x) + mp_obj_get_float_to_f(y)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(add_f_obj, add_f); + +#if USE_DOUBLE +// A function that explicitly uses double precision floats +STATIC mp_obj_t add_d(mp_obj_t x, mp_obj_t y) { + return mp_obj_new_float_from_d(mp_obj_get_float_to_d(x) + mp_obj_get_float_to_d(y)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(add_d_obj, add_d); +#endif + +// A function that computes the product of floats in an array. +// This function uses the most general C argument interface, which is more difficult +// to use but has access to the globals dict of the module via self->globals. +STATIC mp_obj_t productf(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *args) { + // Check number of arguments is valid + mp_arg_check_num(n_args, n_kw, 1, 1, false); + + // Extract buffer pointer and verify typecode + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[0], &bufinfo, MP_BUFFER_RW); + if (bufinfo.typecode != 'f') { + mp_raise_ValueError("expecting float array"); + } + + // Compute product, store result back in first element of array + float *ptr = bufinfo.buf; + float prod = prod_array(bufinfo.len / sizeof(*ptr), ptr); + ptr[0] = prod; + + return mp_const_none; +} + +// This is the entry point and is called when the module is imported +mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *args) { + // This must be first, it sets up the globals dict and other things + MP_DYNRUNTIME_INIT_ENTRY + + // Make the functions available in the module's namespace + mp_store_global(MP_QSTR_add, MP_OBJ_FROM_PTR(&add_obj)); + mp_store_global(MP_QSTR_add_f, MP_OBJ_FROM_PTR(&add_f_obj)); + #if USE_DOUBLE + mp_store_global(MP_QSTR_add_d, MP_OBJ_FROM_PTR(&add_d_obj)); + #endif + + // The productf function uses the most general C argument interface + mp_store_global(MP_QSTR_productf, MP_DYNRUNTIME_MAKE_FUNCTION(productf)); + + // This must be last, it restores the globals dict + MP_DYNRUNTIME_INIT_EXIT +} diff --git a/examples/natmod/features2/prod.c b/examples/natmod/features2/prod.c new file mode 100644 index 000000000..7791dcad1 --- /dev/null +++ b/examples/natmod/features2/prod.c @@ -0,0 +1,9 @@ +#include "prod.h" + +float prod_array(int n, float *ar) { + float ans = 1; + for (int i = 0; i < n; ++i) { + ans *= ar[i]; + } + return ans; +} diff --git a/examples/natmod/features2/prod.h b/examples/natmod/features2/prod.h new file mode 100644 index 000000000..f27dd8d03 --- /dev/null +++ b/examples/natmod/features2/prod.h @@ -0,0 +1 @@ +float prod_array(int n, float *ar); diff --git a/examples/natmod/features2/test.py b/examples/natmod/features2/test.py new file mode 100644 index 000000000..2e9db00f4 --- /dev/null +++ b/examples/natmod/features2/test.py @@ -0,0 +1,26 @@ +# This Python code will be merged with the C code in main.c + +import array + +def isclose(a, b): + return abs(a - b) < 1e-3 + +def test(): + tests = [ + isclose(add(0.1, 0.2), 0.3), + isclose(add_f(0.1, 0.2), 0.3), + ] + + ar = array.array('f', [1, 2, 3.5]) + productf(ar) + tests.append(isclose(ar[0], 7)) + + if 'add_d' in globals(): + tests.append(isclose(add_d(0.1, 0.2), 0.3)) + + print(tests) + + if not all(tests): + raise SystemExit(1) + +test() diff --git a/examples/natmod/framebuf/Makefile b/examples/natmod/framebuf/Makefile new file mode 100644 index 000000000..2e2b81597 --- /dev/null +++ b/examples/natmod/framebuf/Makefile @@ -0,0 +1,13 @@ +# Location of top-level MicroPython directory +MPY_DIR = ../../.. + +# Name of module (different to built-in framebuf so it can coexist) +MOD = framebuf_$(ARCH) + +# Source files (.c or .py) +SRC = framebuf.c + +# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin) +ARCH = x64 + +include $(MPY_DIR)/py/dynruntime.mk diff --git a/examples/natmod/framebuf/framebuf.c b/examples/natmod/framebuf/framebuf.c new file mode 100644 index 000000000..8f322c204 --- /dev/null +++ b/examples/natmod/framebuf/framebuf.c @@ -0,0 +1,50 @@ +#define MICROPY_ENABLE_DYNRUNTIME (1) +#define MICROPY_PY_FRAMEBUF (1) + +#include "py/dynruntime.h" + +#if !defined(__linux__) +void *memset(void *s, int c, size_t n) { + return mp_fun_table.memset_(s, c, n); +} +#endif + +mp_obj_type_t mp_type_framebuf; + +#include "extmod/modframebuf.c" + +mp_map_elem_t framebuf_locals_dict_table[10]; +STATIC MP_DEFINE_CONST_DICT(framebuf_locals_dict, framebuf_locals_dict_table); + +mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *args) { + MP_DYNRUNTIME_INIT_ENTRY + + mp_type_framebuf.base.type = (void*)&mp_type_type; + mp_type_framebuf.name = MP_QSTR_FrameBuffer; + mp_type_framebuf.make_new = framebuf_make_new; + mp_type_framebuf.buffer_p.get_buffer = framebuf_get_buffer; + framebuf_locals_dict_table[0] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_fill), MP_OBJ_FROM_PTR(&framebuf_fill_obj) }; + framebuf_locals_dict_table[1] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_fill_rect), MP_OBJ_FROM_PTR(&framebuf_fill_rect_obj) }; + framebuf_locals_dict_table[2] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_pixel), MP_OBJ_FROM_PTR(&framebuf_pixel_obj) }; + framebuf_locals_dict_table[3] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_hline), MP_OBJ_FROM_PTR(&framebuf_hline_obj) }; + framebuf_locals_dict_table[4] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_vline), MP_OBJ_FROM_PTR(&framebuf_vline_obj) }; + framebuf_locals_dict_table[5] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_rect), MP_OBJ_FROM_PTR(&framebuf_rect_obj) }; + framebuf_locals_dict_table[6] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_line), MP_OBJ_FROM_PTR(&framebuf_line_obj) }; + framebuf_locals_dict_table[7] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_blit), MP_OBJ_FROM_PTR(&framebuf_blit_obj) }; + framebuf_locals_dict_table[8] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_scroll), MP_OBJ_FROM_PTR(&framebuf_scroll_obj) }; + framebuf_locals_dict_table[9] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_text), MP_OBJ_FROM_PTR(&framebuf_text_obj) }; + mp_type_framebuf.locals_dict = (void*)&framebuf_locals_dict; + + mp_store_global(MP_QSTR_FrameBuffer, MP_OBJ_FROM_PTR(&mp_type_framebuf)); + mp_store_global(MP_QSTR_FrameBuffer1, MP_OBJ_FROM_PTR(&legacy_framebuffer1_obj)); + mp_store_global(MP_QSTR_MVLSB, MP_OBJ_NEW_SMALL_INT(FRAMEBUF_MVLSB)); + mp_store_global(MP_QSTR_MONO_VLSB, MP_OBJ_NEW_SMALL_INT(FRAMEBUF_MVLSB)); + mp_store_global(MP_QSTR_RGB565, MP_OBJ_NEW_SMALL_INT(FRAMEBUF_RGB565)); + mp_store_global(MP_QSTR_GS2_HMSB, MP_OBJ_NEW_SMALL_INT(FRAMEBUF_GS2_HMSB)); + mp_store_global(MP_QSTR_GS4_HMSB, MP_OBJ_NEW_SMALL_INT(FRAMEBUF_GS4_HMSB)); + mp_store_global(MP_QSTR_GS8, MP_OBJ_NEW_SMALL_INT(FRAMEBUF_GS8)); + mp_store_global(MP_QSTR_MONO_HLSB, MP_OBJ_NEW_SMALL_INT(FRAMEBUF_MHLSB)); + mp_store_global(MP_QSTR_MONO_HMSB, MP_OBJ_NEW_SMALL_INT(FRAMEBUF_MHMSB)); + + MP_DYNRUNTIME_INIT_EXIT +} diff --git a/examples/natmod/uheapq/Makefile b/examples/natmod/uheapq/Makefile new file mode 100644 index 000000000..55de3cc08 --- /dev/null +++ b/examples/natmod/uheapq/Makefile @@ -0,0 +1,13 @@ +# Location of top-level MicroPython directory +MPY_DIR = ../../.. + +# Name of module (different to built-in uheapq so it can coexist) +MOD = uheapq_$(ARCH) + +# Source files (.c or .py) +SRC = uheapq.c + +# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin) +ARCH = x64 + +include $(MPY_DIR)/py/dynruntime.mk diff --git a/examples/natmod/uheapq/uheapq.c b/examples/natmod/uheapq/uheapq.c new file mode 100644 index 000000000..df880bd38 --- /dev/null +++ b/examples/natmod/uheapq/uheapq.c @@ -0,0 +1,17 @@ +#define MICROPY_ENABLE_DYNRUNTIME (1) +#define MICROPY_PY_UHEAPQ (1) + +#include "py/dynruntime.h" + +#include "extmod/moduheapq.c" + +mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *args) { + MP_DYNRUNTIME_INIT_ENTRY + + mp_store_global(MP_QSTR___name__, MP_OBJ_NEW_QSTR(MP_QSTR_uheapq)); + mp_store_global(MP_QSTR_heappush, MP_OBJ_FROM_PTR(&mod_uheapq_heappush_obj)); + mp_store_global(MP_QSTR_heappop, MP_OBJ_FROM_PTR(&mod_uheapq_heappop_obj)); + mp_store_global(MP_QSTR_heapify, MP_OBJ_FROM_PTR(&mod_uheapq_heapify_obj)); + + MP_DYNRUNTIME_INIT_EXIT +} diff --git a/examples/natmod/urandom/Makefile b/examples/natmod/urandom/Makefile new file mode 100644 index 000000000..3f018baaf --- /dev/null +++ b/examples/natmod/urandom/Makefile @@ -0,0 +1,13 @@ +# Location of top-level MicroPython directory +MPY_DIR = ../../.. + +# Name of module (different to built-in urandom so it can coexist) +MOD = urandom_$(ARCH) + +# Source files (.c or .py) +SRC = urandom.c + +# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin) +ARCH = x64 + +include $(MPY_DIR)/py/dynruntime.mk diff --git a/examples/natmod/urandom/urandom.c b/examples/natmod/urandom/urandom.c new file mode 100644 index 000000000..732e439ee --- /dev/null +++ b/examples/natmod/urandom/urandom.c @@ -0,0 +1,34 @@ +#define MICROPY_ENABLE_DYNRUNTIME (1) +#define MICROPY_PY_URANDOM (1) +#define MICROPY_PY_URANDOM_EXTRA_FUNCS (1) + +#include "py/dynruntime.h" + +// Dynamic native modules don't support a data section so these must go in the BSS +uint32_t yasmarang_pad, yasmarang_n, yasmarang_d; +uint8_t yasmarang_dat; + +#include "extmod/modurandom.c" + +mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *args) { + MP_DYNRUNTIME_INIT_ENTRY + + yasmarang_pad = 0xeda4baba; + yasmarang_n = 69; + yasmarang_d = 233; + + mp_store_global(MP_QSTR___name__, MP_OBJ_NEW_QSTR(MP_QSTR_urandom)); + mp_store_global(MP_QSTR_getrandbits, MP_OBJ_FROM_PTR(&mod_urandom_getrandbits_obj)); + mp_store_global(MP_QSTR_seed, MP_OBJ_FROM_PTR(&mod_urandom_seed_obj)); + #if MICROPY_PY_URANDOM_EXTRA_FUNCS + mp_store_global(MP_QSTR_randrange, MP_OBJ_FROM_PTR(&mod_urandom_randrange_obj)); + mp_store_global(MP_QSTR_randint, MP_OBJ_FROM_PTR(&mod_urandom_randint_obj)); + mp_store_global(MP_QSTR_choice, MP_OBJ_FROM_PTR(&mod_urandom_choice_obj)); + #if MICROPY_PY_BUILTINS_FLOAT + mp_store_global(MP_QSTR_random, MP_OBJ_FROM_PTR(&mod_urandom_random_obj)); + mp_store_global(MP_QSTR_uniform, MP_OBJ_FROM_PTR(&mod_urandom_uniform_obj)); + #endif + #endif + + MP_DYNRUNTIME_INIT_EXIT +} diff --git a/examples/natmod/ure/Makefile b/examples/natmod/ure/Makefile new file mode 100644 index 000000000..f5254298f --- /dev/null +++ b/examples/natmod/ure/Makefile @@ -0,0 +1,13 @@ +# Location of top-level MicroPython directory +MPY_DIR = ../../.. + +# Name of module (different to built-in ure so it can coexist) +MOD = ure_$(ARCH) + +# Source files (.c or .py) +SRC = ure.c + +# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin) +ARCH = x64 + +include $(MPY_DIR)/py/dynruntime.mk diff --git a/examples/natmod/ure/ure.c b/examples/natmod/ure/ure.c new file mode 100644 index 000000000..6c9e9e307 --- /dev/null +++ b/examples/natmod/ure/ure.c @@ -0,0 +1,79 @@ +#define MICROPY_ENABLE_DYNRUNTIME (1) +#define MICROPY_STACK_CHECK (1) +#define MICROPY_PY_URE (1) +#define MICROPY_PY_URE_MATCH_GROUPS (1) +#define MICROPY_PY_URE_MATCH_SPAN_START_END (1) +#define MICROPY_PY_URE_SUB (0) // requires vstr interface + +#include +#include "py/dynruntime.h" + +#define STACK_LIMIT (2048) + +const char *stack_top; + +void mp_stack_check(void) { + // Assumes descending stack on target + volatile char dummy; + if (stack_top - &dummy >= STACK_LIMIT) { + mp_raise_msg(&mp_type_RuntimeError, "maximum recursion depth exceeded"); + } +} + +#if !defined(__linux__) +void *memcpy(void *dst, const void *src, size_t n) { + return mp_fun_table.memmove_(dst, src, n); +} +void *memset(void *s, int c, size_t n) { + return mp_fun_table.memset_(s, c, n); +} +#endif + +void *memmove(void *dest, const void *src, size_t n) { + return mp_fun_table.memmove_(dest, src, n); +} + +mp_obj_type_t match_type; +mp_obj_type_t re_type; + +#include "extmod/modure.c" + +mp_map_elem_t match_locals_dict_table[5]; +STATIC MP_DEFINE_CONST_DICT(match_locals_dict, match_locals_dict_table); + +mp_map_elem_t re_locals_dict_table[3]; +STATIC MP_DEFINE_CONST_DICT(re_locals_dict, re_locals_dict_table); + +mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *args) { + MP_DYNRUNTIME_INIT_ENTRY + + char dummy; + stack_top = &dummy; + + // Because MP_QSTR_start/end/split are static, xtensa and xtensawin will make a small data section + // to copy in this key/value pair if they are specified as a struct, so assign them separately. + + match_type.base.type = (void*)&mp_fun_table.type_type; + match_type.name = MP_QSTR_match; + match_type.print = match_print; + match_locals_dict_table[0] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_group), MP_OBJ_FROM_PTR(&match_group_obj) }; + match_locals_dict_table[1] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_groups), MP_OBJ_FROM_PTR(&match_groups_obj) }; + match_locals_dict_table[2] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_span), MP_OBJ_FROM_PTR(&match_span_obj) }; + match_locals_dict_table[3] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_start), MP_OBJ_FROM_PTR(&match_start_obj) }; + match_locals_dict_table[4] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_end), MP_OBJ_FROM_PTR(&match_end_obj) }; + match_type.locals_dict = (void*)&match_locals_dict; + + re_type.base.type = (void*)&mp_fun_table.type_type; + re_type.name = MP_QSTR_ure; + re_type.print = re_print; + re_locals_dict_table[0] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_match), MP_OBJ_FROM_PTR(&re_match_obj) }; + re_locals_dict_table[1] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_search), MP_OBJ_FROM_PTR(&re_search_obj) }; + re_locals_dict_table[2] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_split), MP_OBJ_FROM_PTR(&re_split_obj) }; + re_type.locals_dict = (void*)&re_locals_dict; + + mp_store_global(MP_QSTR_compile, MP_OBJ_FROM_PTR(&mod_re_compile_obj)); + mp_store_global(MP_QSTR_match, MP_OBJ_FROM_PTR(&mod_re_match_obj)); + mp_store_global(MP_QSTR_search, MP_OBJ_FROM_PTR(&mod_re_search_obj)); + + MP_DYNRUNTIME_INIT_EXIT +} diff --git a/examples/natmod/uzlib/Makefile b/examples/natmod/uzlib/Makefile new file mode 100644 index 000000000..8761caf2d --- /dev/null +++ b/examples/natmod/uzlib/Makefile @@ -0,0 +1,13 @@ +# Location of top-level MicroPython directory +MPY_DIR = ../../.. + +# Name of module (different to built-in uzlib so it can coexist) +MOD = uzlib_$(ARCH) + +# Source files (.c or .py) +SRC = uzlib.c + +# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin) +ARCH = x64 + +include $(MPY_DIR)/py/dynruntime.mk diff --git a/examples/natmod/uzlib/uzlib.c b/examples/natmod/uzlib/uzlib.c new file mode 100644 index 000000000..4873171e5 --- /dev/null +++ b/examples/natmod/uzlib/uzlib.c @@ -0,0 +1,36 @@ +#define MICROPY_ENABLE_DYNRUNTIME (1) +#define MICROPY_PY_UZLIB (1) + +#include "py/dynruntime.h" + +#if !defined(__linux__) +void *memset(void *s, int c, size_t n) { + return mp_fun_table.memset_(s, c, n); +} +#endif + +mp_obj_type_t decompio_type; + +#include "extmod/moduzlib.c" + +mp_map_elem_t decompio_locals_dict_table[3]; +STATIC MP_DEFINE_CONST_DICT(decompio_locals_dict, decompio_locals_dict_table); + +mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *args) { + MP_DYNRUNTIME_INIT_ENTRY + + decompio_type.base.type = mp_fun_table.type_type; + decompio_type.name = MP_QSTR_DecompIO; + decompio_type.make_new = decompio_make_new; + decompio_type.protocol = &decompio_stream_p; + decompio_locals_dict_table[0] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_read), MP_OBJ_FROM_PTR(&mp_stream_read_obj) }; + decompio_locals_dict_table[1] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_readinto), MP_OBJ_FROM_PTR(&mp_stream_readinto_obj) }; + decompio_locals_dict_table[2] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_readline), MP_OBJ_FROM_PTR(&mp_stream_unbuffered_readline_obj) }; + decompio_type.locals_dict = (void*)&decompio_locals_dict; + + mp_store_global(MP_QSTR___name__, MP_OBJ_NEW_QSTR(MP_QSTR_uzlib)); + mp_store_global(MP_QSTR_decompress, MP_OBJ_FROM_PTR(&mod_uzlib_decompress_obj)); + mp_store_global(MP_QSTR_DecompIO, MP_OBJ_FROM_PTR(&decompio_type)); + + MP_DYNRUNTIME_INIT_EXIT +} diff --git a/examples/network/http_server_ssl.py b/examples/network/http_server_ssl.py index 9a69ca9d4..47d83bf24 100644 --- a/examples/network/http_server_ssl.py +++ b/examples/network/http_server_ssl.py @@ -1,3 +1,4 @@ +import ubinascii as binascii try: import usocket as socket except: @@ -5,6 +6,35 @@ except: import ussl as ssl +# This self-signed key/cert pair is randomly generated and to be used for +# testing/demonstration only. You should always generate your own key/cert. +key = binascii.unhexlify( + b'3082013b020100024100cc20643fd3d9c21a0acba4f48f61aadd675f52175a9dcf07fbef' + b'610a6a6ba14abb891745cd18a1d4c056580d8ff1a639460f867013c8391cdc9f2e573b0f' + b'872d0203010001024100bb17a54aeb3dd7ae4edec05e775ca9632cf02d29c2a089b563b0' + b'd05cdf95aeca507de674553f28b4eadaca82d5549a86058f9996b07768686a5b02cb240d' + b'd9f1022100f4a63f5549e817547dca97b5c658038e8593cb78c5aba3c4642cc4cd031d86' + b'8f022100d598d870ffe4a34df8de57047a50b97b71f4d23e323f527837c9edae88c79483' + b'02210098560c89a70385c36eb07fd7083235c4c1184e525d838aedf7128958bedfdbb102' + b'2051c0dab7057a8176ca966f3feb81123d4974a733df0f958525f547dfd1c271f9022044' + b'6c2cafad455a671a8cf398e642e1be3b18a3d3aec2e67a9478f83c964c4f1f') +cert = binascii.unhexlify( + b'308201d53082017f020203e8300d06092a864886f70d01010505003075310b3009060355' + b'0406130258583114301206035504080c0b54686550726f76696e63653110300e06035504' + b'070c075468654369747931133011060355040a0c0a436f6d70616e7958595a3113301106' + b'0355040b0c0a436f6d70616e7958595a3114301206035504030c0b546865486f73744e61' + b'6d65301e170d3139313231383033333935355a170d3239313231353033333935355a3075' + b'310b30090603550406130258583114301206035504080c0b54686550726f76696e636531' + b'10300e06035504070c075468654369747931133011060355040a0c0a436f6d70616e7958' + b'595a31133011060355040b0c0a436f6d70616e7958595a3114301206035504030c0b5468' + b'65486f73744e616d65305c300d06092a864886f70d0101010500034b003048024100cc20' + b'643fd3d9c21a0acba4f48f61aadd675f52175a9dcf07fbef610a6a6ba14abb891745cd18' + b'a1d4c056580d8ff1a639460f867013c8391cdc9f2e573b0f872d0203010001300d06092a' + b'864886f70d0101050500034100b0513fe2829e9ecbe55b6dd14c0ede7502bde5d46153c8' + b'e960ae3ebc247371b525caeb41bbcf34686015a44c50d226e66aef0a97a63874ca5944ef' + b'979b57f0b3') + + CONTENT = b"""\ HTTP/1.0 200 OK @@ -31,7 +61,8 @@ def main(use_stream=True): client_addr = res[1] print("Client address:", client_addr) print("Client socket:", client_s) - client_s = ssl.wrap_socket(client_s, server_side=True) + # CPython uses key keyfile/certfile arguments, but MicroPython uses key/cert + client_s = ssl.wrap_socket(client_s, server_side=True, key=key, cert=cert) print(client_s) print("Request:") if use_stream: diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index bac7c07cb..756d3d6d1 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -33,6 +33,7 @@ #include "py/objarray.h" #include "py/qstr.h" #include "py/runtime.h" +#include "py/mphal.h" #include "extmod/modbluetooth.h" #include @@ -42,16 +43,13 @@ #error modbluetooth requires MICROPY_ENABLE_SCHEDULER #endif -// This is used to protect the ringbuffer. -#ifndef MICROPY_PY_BLUETOOTH_ENTER -#define MICROPY_PY_BLUETOOTH_ENTER mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); -#define MICROPY_PY_BLUETOOTH_EXIT MICROPY_END_ATOMIC_SECTION(atomic_state); -#endif - #define MP_BLUETOOTH_CONNECT_DEFAULT_SCAN_DURATION_MS 2000 #define MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_TUPLE_LEN 5 -#define MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_BYTES_LEN (MICROPY_PY_BLUETOOTH_RINGBUF_SIZE / 2) + +// This formula is intended to allow queuing the data of a large characteristic +// while still leaving room for a couple of normal (small, fixed size) events. +#define MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_BYTES_LEN(ringbuf_size) (MAX((int)((ringbuf_size) / 2), (int)(ringbuf_size) - 64)) STATIC const mp_obj_type_t bluetooth_ble_type; STATIC const mp_obj_type_t bluetooth_uuid_type; @@ -60,9 +58,11 @@ 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]; - uint8_t irq_data_data_bytes[MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_BYTES_LEN]; + 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_bluetooth_uuid_t irq_data_uuid; @@ -248,11 +248,12 @@ STATIC mp_obj_t bluetooth_ble_make_new(const mp_obj_type_t *type, size_t n_args, // 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; + 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 = o->irq_data_data_bytes; + o->irq_data_data.data = m_new(uint8_t, o->irq_data_data_alloc); o->irq_data_uuid.base.type = &bluetooth_uuid_type; - // Allocate the ringbuf. TODO: Consider making the size user-specified. + // Allocate the default ringbuf. ringbuf_alloc(&o->ringbuf, MICROPY_PY_BLUETOOTH_RINGBUF_SIZE); MP_STATE_VM(bluetooth) = MP_OBJ_FROM_PTR(o); @@ -277,16 +278,77 @@ STATIC mp_obj_t bluetooth_ble_active(size_t n_args, const mp_obj_t *args) { } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_active_obj, 1, 2, bluetooth_ble_active); -STATIC mp_obj_t bluetooth_ble_config(mp_obj_t self_in, mp_obj_t param) { - if (param == MP_OBJ_NEW_QSTR(MP_QSTR_mac)) { - uint8_t addr[6]; - mp_bluetooth_get_device_addr(addr); - return mp_obj_new_bytes(addr, MP_ARRAY_SIZE(addr)); +STATIC mp_obj_t bluetooth_ble_config(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + mp_obj_bluetooth_ble_t *self = MP_OBJ_TO_PTR(args[0]); + + if (kwargs->used == 0) { + // Get config value + if (n_args != 2) { + mp_raise_TypeError("must query one param"); + } + + switch (mp_obj_str_get_qstr(args[1])) { + case MP_QSTR_mac: { + uint8_t addr[6]; + mp_bluetooth_get_device_addr(addr); + return mp_obj_new_bytes(addr, MP_ARRAY_SIZE(addr)); + } + default: + mp_raise_ValueError("unknown config param"); + } } else { - mp_raise_ValueError("unknown config param"); + // Set config value(s) + if (n_args != 1) { + mp_raise_TypeError("can't specify pos and kw args"); + } + + for (size_t i = 0; i < kwargs->alloc; ++i) { + if (MP_MAP_SLOT_IS_FILLED(kwargs, i)) { + mp_map_elem_t *e = &kwargs->table[i]; + switch (mp_obj_str_get_qstr(e->key)) { + case MP_QSTR_rxbuf: { + // Determine new buffer sizes + mp_int_t ringbuf_alloc = mp_obj_get_int(e->value); + if (ringbuf_alloc < 16 || ringbuf_alloc > 0xffff) { + mp_raise_ValueError(NULL); + } + size_t irq_data_alloc = MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_BYTES_LEN(ringbuf_alloc); + + // Allocate new buffers + uint8_t *ringbuf = m_new(uint8_t, ringbuf_alloc); + uint8_t *irq_data = m_new(uint8_t, irq_data_alloc); + + // 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; + size_t old_irq_data_alloc = self->irq_data_data_alloc; + + // Atomically update the ringbuf and irq data + MICROPY_PY_BLUETOOTH_ENTER + self->ringbuf.size = ringbuf_alloc; + self->ringbuf.buf = ringbuf; + self->ringbuf.iget = 0; + self->ringbuf.iput = 0; + self->irq_data_data_alloc = irq_data_alloc; + self->irq_data_data.data = irq_data; + MICROPY_PY_BLUETOOTH_EXIT + + // Free old buffers + m_del(uint8_t, old_ringbuf_buf, old_ringbuf_alloc); + m_del(uint8_t, old_irq_data_buf, old_irq_data_alloc); + break; + } + default: + mp_raise_ValueError("unknown config param"); + } + } + } + + return mp_const_none; } } -STATIC MP_DEFINE_CONST_FUN_OBJ_2(bluetooth_ble_config_obj, bluetooth_ble_config); +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 }; @@ -645,9 +707,13 @@ STATIC mp_obj_t bluetooth_ble_gattc_write(size_t n_args, const mp_obj_t *args) { mp_buffer_info_t bufinfo = {0}; mp_get_buffer_raise(data, &bufinfo, MP_BUFFER_READ); size_t len = bufinfo.len; - return bluetooth_handle_errno(mp_bluetooth_gattc_write(conn_handle, value_handle, bufinfo.buf, &len)); + unsigned int mode = MP_BLUETOOTH_WRITE_MODE_NO_RESPONSE; + if (n_args == 5) { + mode = mp_obj_get_int(args[4]); + } + return bluetooth_handle_errno(mp_bluetooth_gattc_write(conn_handle, value_handle, bufinfo.buf, &len, mode)); } -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gattc_write_obj, 4, 4, bluetooth_ble_gattc_write); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gattc_write_obj, 4, 5, bluetooth_ble_gattc_write); #endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE @@ -741,8 +807,8 @@ STATIC void ringbuf_extract(ringbuf_t* ringbuf, mp_obj_tuple_t *data_tuple, size data_tuple->items[j++] = MP_OBJ_FROM_PTR(uuid); } // The code that enqueues into the ringbuf should ensure that it doesn't - // put more than MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_BYTES_LEN bytes into - // the ringbuf. + // put more than bt->irq_data_data_alloc bytes into the ringbuf, because + // that's what's available here in bt->irq_data_bytes. if (bytes_data) { bytes_data->len = ringbuf_get(ringbuf); for (int i = 0; i < bytes_data->len; ++i) { @@ -757,9 +823,12 @@ STATIC void ringbuf_extract(ringbuf_t* ringbuf, mp_obj_tuple_t *data_tuple, size STATIC mp_obj_t bluetooth_ble_invoke_irq(mp_obj_t none_in) { // This is always executing in schedule context. + + mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); + o->irq_scheduled = false; + for (;;) { MICROPY_PY_BLUETOOTH_ENTER - mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); mp_int_t event = event = ringbuf_get16(&o->ringbuf); if (event < 0) { @@ -822,9 +891,7 @@ 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, bool *sched) { - *sched = false; - +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) { return false; } @@ -849,11 +916,6 @@ STATIC bool enqueue_irq(mp_obj_bluetooth_ble_t *o, size_t len, uint16_t event, b for (int i = 0; i < n; ++i) { ringbuf_get(&o->ringbuf); } - - // No need to schedule the handler, as the ringbuffer was non-empty. - } else { - // Schedule the handler only if this is the first thing in the ringbuffer. - *sched = ringbuf_avail(&o->ringbuf) == 0; } // Append this event, the caller will then append the arguments. @@ -861,8 +923,10 @@ STATIC bool enqueue_irq(mp_obj_bluetooth_ble_t *o, size_t len, uint16_t event, b return true; } -STATIC void schedule_ringbuf(bool sched) { - if (sched) { +STATIC void schedule_ringbuf(void) { + mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); + if (!o->irq_scheduled) { + o->irq_scheduled = true; mp_sched_schedule(MP_OBJ_FROM_PTR(MP_ROM_PTR(&bluetooth_ble_invoke_irq_obj)), mp_const_none); } } @@ -870,47 +934,43 @@ STATIC void schedule_ringbuf(bool sched) { void mp_bluetooth_gap_on_connected_disconnected(uint16_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)); - bool sched; - if (enqueue_irq(o, 2 + 1 + 6, event, &sched)) { + if (enqueue_irq(o, 2 + 1 + 6, event)) { ringbuf_put16(&o->ringbuf, conn_handle); ringbuf_put(&o->ringbuf, addr_type); for (int i = 0; i < 6; ++i) { ringbuf_put(&o->ringbuf, addr[i]); } } + schedule_ringbuf(); MICROPY_PY_BLUETOOTH_EXIT - schedule_ringbuf(sched); } void mp_bluetooth_gatts_on_write(uint16_t conn_handle, uint16_t value_handle) { MICROPY_PY_BLUETOOTH_ENTER mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - bool sched; - if (enqueue_irq(o, 2 + 2, MP_BLUETOOTH_IRQ_GATTS_WRITE, &sched)) { + if (enqueue_irq(o, 2 + 2, MP_BLUETOOTH_IRQ_GATTS_WRITE)) { ringbuf_put16(&o->ringbuf, conn_handle); ringbuf_put16(&o->ringbuf, value_handle); } + schedule_ringbuf(); MICROPY_PY_BLUETOOTH_EXIT - schedule_ringbuf(sched); } #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)); - bool sched; - if (enqueue_irq(o, 0, MP_BLUETOOTH_IRQ_SCAN_COMPLETE, &sched)) { + if (enqueue_irq(o, 0, MP_BLUETOOTH_IRQ_SCAN_COMPLETE)) { } + schedule_ringbuf(); MICROPY_PY_BLUETOOTH_EXIT - schedule_ringbuf(sched); } void mp_bluetooth_gap_on_scan_result(uint8_t addr_type, const uint8_t *addr, bool connectable, const int8_t rssi, const uint8_t *data, size_t data_len) { MICROPY_PY_BLUETOOTH_ENTER mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - bool sched; - data_len = MIN(MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_BYTES_LEN, data_len); - if (enqueue_irq(o, 1 + 6 + 1 + 1 + 1 + data_len, MP_BLUETOOTH_IRQ_SCAN_RESULT, &sched)) { + 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)) { ringbuf_put(&o->ringbuf, addr_type); for (int i = 0; i < 6; ++i) { ringbuf_put(&o->ringbuf, addr[i]); @@ -923,80 +983,83 @@ void mp_bluetooth_gap_on_scan_result(uint8_t addr_type, const uint8_t *addr, boo ringbuf_put(&o->ringbuf, data[i]); } } + schedule_ringbuf(); MICROPY_PY_BLUETOOTH_EXIT - schedule_ringbuf(sched); } void mp_bluetooth_gattc_on_primary_service_result(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle, mp_obj_bluetooth_uuid_t *service_uuid) { MICROPY_PY_BLUETOOTH_ENTER mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - bool sched; - if (enqueue_irq(o, 2 + 2 + 2 + 1 + service_uuid->type, MP_BLUETOOTH_IRQ_GATTC_SERVICE_RESULT, &sched)) { + if (enqueue_irq(o, 2 + 2 + 2 + 1 + service_uuid->type, MP_BLUETOOTH_IRQ_GATTC_SERVICE_RESULT)) { ringbuf_put16(&o->ringbuf, conn_handle); ringbuf_put16(&o->ringbuf, start_handle); ringbuf_put16(&o->ringbuf, end_handle); ringbuf_put_uuid(&o->ringbuf, service_uuid); } + schedule_ringbuf(); MICROPY_PY_BLUETOOTH_EXIT - schedule_ringbuf(sched); } void mp_bluetooth_gattc_on_characteristic_result(uint16_t conn_handle, uint16_t def_handle, uint16_t value_handle, uint8_t properties, mp_obj_bluetooth_uuid_t *characteristic_uuid) { MICROPY_PY_BLUETOOTH_ENTER mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - bool sched; - if (enqueue_irq(o, 2 + 2 + 2 + 1 + characteristic_uuid->type, MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_RESULT, &sched)) { + if (enqueue_irq(o, 2 + 2 + 2 + 1 + characteristic_uuid->type, MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_RESULT)) { ringbuf_put16(&o->ringbuf, conn_handle); ringbuf_put16(&o->ringbuf, def_handle); ringbuf_put16(&o->ringbuf, value_handle); ringbuf_put(&o->ringbuf, properties); ringbuf_put_uuid(&o->ringbuf, characteristic_uuid); } + schedule_ringbuf(); MICROPY_PY_BLUETOOTH_EXIT - schedule_ringbuf(sched); } void mp_bluetooth_gattc_on_descriptor_result(uint16_t conn_handle, uint16_t handle, mp_obj_bluetooth_uuid_t *descriptor_uuid) { MICROPY_PY_BLUETOOTH_ENTER mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - bool sched; - if (enqueue_irq(o, 2 + 2 + 1 + descriptor_uuid->type, MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_RESULT, &sched)) { + if (enqueue_irq(o, 2 + 2 + 1 + descriptor_uuid->type, MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_RESULT)) { ringbuf_put16(&o->ringbuf, conn_handle); ringbuf_put16(&o->ringbuf, handle); ringbuf_put_uuid(&o->ringbuf, descriptor_uuid); } + schedule_ringbuf(); MICROPY_PY_BLUETOOTH_EXIT - schedule_ringbuf(sched); } -void mp_bluetooth_gattc_on_data_available(uint16_t event, uint16_t conn_handle, uint16_t value_handle, const uint8_t *data, size_t data_len) { - MICROPY_PY_BLUETOOTH_ENTER +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_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - bool sched; - data_len = MIN(MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_BYTES_LEN, data_len); - if (enqueue_irq(o, 2 + 2 + 1 + data_len, event, &sched)) { + data_len = MIN(o->irq_data_data_alloc, data_len); + if (enqueue_irq(o, 2 + 2 + 1 + data_len, event)) { ringbuf_put16(&o->ringbuf, conn_handle); ringbuf_put16(&o->ringbuf, value_handle); ringbuf_put(&o->ringbuf, data_len); - for (int i = 0; i < data_len; ++i) { - ringbuf_put(&o->ringbuf, data[i]); - } + return data_len; + } else { + return 0; } - MICROPY_PY_BLUETOOTH_EXIT - schedule_ringbuf(sched); +} + +void mp_bluetooth_gattc_on_data_available_chunk(const uint8_t *data, size_t data_len) { + mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); + for (int i = 0; i < data_len; ++i) { + ringbuf_put(&o->ringbuf, data[i]); + } +} + +void mp_bluetooth_gattc_on_data_available_end(void) { + schedule_ringbuf(); } void mp_bluetooth_gattc_on_write_status(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)); - bool sched; - if (enqueue_irq(o, 2 + 2 + 2, MP_BLUETOOTH_IRQ_GATTC_WRITE_STATUS, &sched)) { + if (enqueue_irq(o, 2 + 2 + 2, MP_BLUETOOTH_IRQ_GATTC_WRITE_STATUS)) { ringbuf_put16(&o->ringbuf, conn_handle); ringbuf_put16(&o->ringbuf, value_handle); ringbuf_put16(&o->ringbuf, status); } + schedule_ringbuf(); MICROPY_PY_BLUETOOTH_EXIT - schedule_ringbuf(sched); } #endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index bce28a6d1..fbae67bc4 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -47,6 +47,12 @@ #define MICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK (0) #endif +// This is used to protect the ringbuffer. +#ifndef MICROPY_PY_BLUETOOTH_ENTER +#define MICROPY_PY_BLUETOOTH_ENTER mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); +#define MICROPY_PY_BLUETOOTH_EXIT MICROPY_END_ATOMIC_SECTION(atomic_state); +#endif + // Common constants. #ifndef MP_BLUETOOTH_MAX_ATTR_SIZE #define MP_BLUETOOTH_MAX_ATTR_SIZE (20) @@ -59,6 +65,10 @@ #define MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE (1 << 3) #define MP_BLUETOOTH_CHARACTERISTIC_FLAG_NOTIFY (1 << 4) +// For mp_bluetooth_gattc_write, the mode parameter +#define MP_BLUETOOTH_WRITE_MODE_NO_RESPONSE (0) +#define MP_BLUETOOTH_WRITE_MODE_WITH_RESPONSE (1) + // Type value also doubles as length. #define MP_BLUETOOTH_UUID_TYPE_16 (2) #define MP_BLUETOOTH_UUID_TYPE_32 (4) @@ -213,7 +223,7 @@ int mp_bluetooth_gattc_discover_descriptors(uint16_t conn_handle, uint16_t start 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); +int mp_bluetooth_gattc_write(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t *value_len, unsigned int mode); #endif ///////////////////////////////////////////////////////////////////////////// @@ -247,7 +257,11 @@ void mp_bluetooth_gattc_on_characteristic_result(uint16_t conn_handle, uint16_t void mp_bluetooth_gattc_on_descriptor_result(uint16_t conn_handle, uint16_t handle, mp_obj_bluetooth_uuid_t *descriptor_uuid); // Notify modbluetooth that a read has completed with data (or notify/indicate data available, use `event` to disambiguate). -void mp_bluetooth_gattc_on_data_available(uint16_t event, uint16_t conn_handle, uint16_t value_handle, const uint8_t *data, size_t data_len); +// 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); +void mp_bluetooth_gattc_on_data_available_chunk(const uint8_t *data, size_t data_len); +void mp_bluetooth_gattc_on_data_available_end(void); // 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); diff --git a/extmod/modbluetooth_nimble.c b/extmod/modbluetooth_nimble.c index f86ab70bf..930dd06d1 100644 --- a/extmod/modbluetooth_nimble.c +++ b/extmod/modbluetooth_nimble.c @@ -616,6 +616,20 @@ int mp_bluetooth_gatts_set_buffer(uint16_t value_handle, size_t len, bool append #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) { + MICROPY_PY_BLUETOOTH_ENTER + size_t len = OS_MBUF_PKTLEN(om); + len = mp_bluetooth_gattc_on_data_available_start(event, conn_handle, value_handle, len); + while (len > 0 && om != NULL) { + size_t n = MIN(om->om_len, len); + mp_bluetooth_gattc_on_data_available_chunk(OS_MBUF_DATA(om, const uint8_t*), n); + len -= n; + om = SLIST_NEXT(om, om_next); + } + mp_bluetooth_gattc_on_data_available_end(); + MICROPY_PY_BLUETOOTH_EXIT +} + 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); @@ -674,8 +688,6 @@ int mp_bluetooth_gap_scan_stop(void) { 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); struct ble_gap_conn_desc desc; - uint8_t buf[MP_BLUETOOTH_MAX_ATTR_SIZE]; - size_t len; uint8_t addr[6] = {0}; switch (event->type) { @@ -698,15 +710,11 @@ STATIC int peripheral_gap_event_cb(struct ble_gap_event *event, void *arg) { break; - case BLE_GAP_EVENT_NOTIFY_RX: - len = MIN(MP_BLUETOOTH_MAX_ATTR_SIZE, OS_MBUF_PKTLEN(event->notify_rx.om)); - os_mbuf_copydata(event->notify_rx.om, 0, len, buf); - if (event->notify_rx.indication == 0) { - mp_bluetooth_gattc_on_data_available(MP_BLUETOOTH_IRQ_GATTC_NOTIFY, event->notify_rx.conn_handle, event->notify_rx.attr_handle, buf, len); - } else { - mp_bluetooth_gattc_on_data_available(MP_BLUETOOTH_IRQ_GATTC_INDICATE, event->notify_rx.conn_handle, event->notify_rx.attr_handle, buf, len); - } + case BLE_GAP_EVENT_NOTIFY_RX: { + uint16_t ev = event->notify_rx.indication == 0 ? MP_BLUETOOTH_IRQ_GATTC_NOTIFY : MP_BLUETOOTH_IRQ_GATTC_INDICATE; + gattc_on_data_available(ev, event->notify_rx.conn_handle, event->notify_rx.attr_handle, event->notify_rx.om); break; + } case BLE_GAP_EVENT_CONN_UPDATE: // TODO @@ -790,10 +798,7 @@ STATIC int ble_gatt_attr_read_cb(uint16_t conn_handle, const struct ble_gatt_err DEBUG_EVENT_printf("ble_gatt_attr_read_cb: conn_handle=%d status=%d handle=%d\n", conn_handle, error->status, attr ? attr->handle : -1); // TODO: Maybe send NULL if error->status non-zero. if (error->status == 0) { - uint8_t buf[MP_BLUETOOTH_MAX_ATTR_SIZE]; - size_t len = MIN(MP_BLUETOOTH_MAX_ATTR_SIZE, OS_MBUF_PKTLEN(attr->om)); - os_mbuf_copydata(attr->om, 0, len, buf); - mp_bluetooth_gattc_on_data_available(MP_BLUETOOTH_IRQ_GATTC_READ_RESULT, conn_handle, attr->handle, buf, len); + gattc_on_data_available(MP_BLUETOOTH_IRQ_GATTC_READ_RESULT, conn_handle, attr->handle, attr->om); } return 0; } @@ -811,8 +816,15 @@ STATIC int ble_gatt_attr_write_cb(uint16_t conn_handle, const struct ble_gatt_er } // 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) { - int err = ble_gattc_write_flat(conn_handle, value_handle, value, *value_len, &ble_gatt_attr_write_cb, NULL); +int mp_bluetooth_gattc_write(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t *value_len, unsigned int mode) { + int err; + if (mode == MP_BLUETOOTH_WRITE_MODE_NO_RESPONSE) { + err = ble_gattc_write_no_rsp_flat(conn_handle, value_handle, value, *value_len); + } else if (mode == MP_BLUETOOTH_WRITE_MODE_WITH_RESPONSE) { + err = ble_gattc_write_flat(conn_handle, value_handle, value, *value_len, &ble_gatt_attr_write_cb, NULL); + } else { + err = BLE_HS_EINVAL; + } return ble_hs_err_to_errno(err); } diff --git a/extmod/modbtree.c b/extmod/modbtree.c index 2a08a9cab..a1d576fda 100644 --- a/extmod/modbtree.c +++ b/extmod/modbtree.c @@ -52,7 +52,9 @@ typedef struct _mp_obj_btree_t { byte next_flags; } mp_obj_btree_t; +#if !MICROPY_ENABLE_DYNRUNTIME STATIC const mp_obj_type_t btree_type; +#endif #define CHECK_ERROR(res) \ if (res == RET_ERROR) { \ @@ -60,7 +62,7 @@ STATIC const mp_obj_type_t btree_type; } void __dbpanic(DB *db) { - printf("__dbpanic(%p)\n", db); + mp_printf(&mp_plat_print, "__dbpanic(%p)\n", db); } STATIC mp_obj_btree_t *btree_new(DB *db) { @@ -295,6 +297,7 @@ STATIC mp_obj_t btree_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs } } +#if !MICROPY_ENABLE_DYNRUNTIME STATIC const mp_rom_map_elem_t btree_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&btree_close_obj) }, { MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&btree_flush_obj) }, @@ -319,6 +322,7 @@ STATIC const mp_obj_type_t btree_type = { .subscr = btree_subscr, .locals_dict = (void*)&btree_locals_dict, }; +#endif STATIC const FILEVTABLE btree_stream_fvtable = { mp_stream_posix_read, @@ -327,6 +331,7 @@ STATIC const FILEVTABLE btree_stream_fvtable = { mp_stream_posix_fsync }; +#if !MICROPY_ENABLE_DYNRUNTIME STATIC mp_obj_t mod_btree_open(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { static const mp_arg_t allowed_args[] = { { MP_QSTR_flags, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, @@ -373,5 +378,6 @@ const mp_obj_module_t mp_module_btree = { .base = { &mp_type_module }, .globals = (mp_obj_dict_t*)&mp_module_btree_globals, }; +#endif #endif // MICROPY_PY_BTREE diff --git a/extmod/modframebuf.c b/extmod/modframebuf.c index a7f6ba905..416431cd6 100644 --- a/extmod/modframebuf.c +++ b/extmod/modframebuf.c @@ -580,6 +580,7 @@ STATIC mp_obj_t framebuf_text(size_t n_args, const mp_obj_t *args) { } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(framebuf_text_obj, 4, 5, framebuf_text); +#if !MICROPY_ENABLE_DYNRUNTIME STATIC const mp_rom_map_elem_t framebuf_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_fill), MP_ROM_PTR(&framebuf_fill_obj) }, { MP_ROM_QSTR(MP_QSTR_fill_rect), MP_ROM_PTR(&framebuf_fill_rect_obj) }, @@ -601,6 +602,7 @@ STATIC const mp_obj_type_t mp_type_framebuf = { .buffer_p = { .get_buffer = framebuf_get_buffer }, .locals_dict = (mp_obj_dict_t*)&framebuf_locals_dict, }; +#endif // this factory function is provided for backwards compatibility with old FrameBuffer1 class STATIC mp_obj_t legacy_framebuffer1(size_t n_args, const mp_obj_t *args) { @@ -624,6 +626,7 @@ STATIC mp_obj_t legacy_framebuffer1(size_t n_args, const mp_obj_t *args) { } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(legacy_framebuffer1_obj, 3, 4, legacy_framebuffer1); +#if !MICROPY_ENABLE_DYNRUNTIME STATIC const mp_rom_map_elem_t framebuf_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_framebuf) }, { MP_ROM_QSTR(MP_QSTR_FrameBuffer), MP_ROM_PTR(&mp_type_framebuf) }, @@ -644,5 +647,6 @@ const mp_obj_module_t mp_module_framebuf = { .base = { &mp_type_module }, .globals = (mp_obj_dict_t*)&framebuf_module_globals, }; +#endif #endif // MICROPY_PY_FRAMEBUF diff --git a/extmod/moduheapq.c b/extmod/moduheapq.c index f63305210..2e8010143 100644 --- a/extmod/moduheapq.c +++ b/extmod/moduheapq.c @@ -103,6 +103,7 @@ STATIC mp_obj_t mod_uheapq_heapify(mp_obj_t heap_in) { } STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_uheapq_heapify_obj, mod_uheapq_heapify); +#if !MICROPY_ENABLE_DYNRUNTIME STATIC const mp_rom_map_elem_t mp_module_uheapq_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_uheapq) }, { MP_ROM_QSTR(MP_QSTR_heappush), MP_ROM_PTR(&mod_uheapq_heappush_obj) }, @@ -116,5 +117,6 @@ const mp_obj_module_t mp_module_uheapq = { .base = { &mp_type_module }, .globals = (mp_obj_dict_t*)&mp_module_uheapq_globals, }; +#endif #endif //MICROPY_PY_UHEAPQ diff --git a/extmod/modurandom.c b/extmod/modurandom.c index 2e667570d..e3c7fc7f5 100644 --- a/extmod/modurandom.c +++ b/extmod/modurandom.c @@ -36,8 +36,10 @@ // http://www.literatecode.com/yasmarang // Public Domain +#if !MICROPY_ENABLE_DYNRUNTIME STATIC uint32_t yasmarang_pad = 0xeda4baba, yasmarang_n = 69, yasmarang_d = 233; STATIC uint8_t yasmarang_dat = 0; +#endif STATIC uint32_t yasmarang(void) { @@ -208,6 +210,7 @@ STATIC mp_obj_t mod_urandom___init__() { STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_urandom___init___obj, mod_urandom___init__); #endif +#if !MICROPY_ENABLE_DYNRUNTIME STATIC const mp_rom_map_elem_t mp_module_urandom_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_urandom) }, #ifdef MICROPY_PY_URANDOM_SEED_INIT_FUNC @@ -232,5 +235,6 @@ const mp_obj_module_t mp_module_urandom = { .base = { &mp_type_module }, .globals = (mp_obj_dict_t*)&mp_module_urandom_globals, }; +#endif #endif //MICROPY_PY_URANDOM diff --git a/extmod/modure.c b/extmod/modure.c index 8a6020705..f3a9d88f7 100644 --- a/extmod/modure.c +++ b/extmod/modure.c @@ -144,6 +144,7 @@ MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(match_end_obj, 1, 2, match_end); #endif +#if !MICROPY_ENABLE_DYNRUNTIME STATIC const mp_rom_map_elem_t match_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_group), MP_ROM_PTR(&match_group_obj) }, #if MICROPY_PY_URE_MATCH_GROUPS @@ -164,6 +165,7 @@ STATIC const mp_obj_type_t match_type = { .print = match_print, .locals_dict = (void*)&match_locals_dict, }; +#endif STATIC void re_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { (void)kind; @@ -363,6 +365,7 @@ MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(re_sub_obj, 3, 5, re_sub); #endif +#if !MICROPY_ENABLE_DYNRUNTIME STATIC const mp_rom_map_elem_t re_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_match), MP_ROM_PTR(&re_match_obj) }, { MP_ROM_QSTR(MP_QSTR_search), MP_ROM_PTR(&re_search_obj) }, @@ -380,6 +383,7 @@ STATIC const mp_obj_type_t re_type = { .print = re_print, .locals_dict = (void*)&re_locals_dict, }; +#endif STATIC mp_obj_t mod_re_compile(size_t n_args, const mp_obj_t *args) { (void)n_args; @@ -437,6 +441,7 @@ STATIC mp_obj_t mod_re_sub(size_t n_args, const mp_obj_t *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) }, @@ -456,6 +461,7 @@ const mp_obj_module_t mp_module_ure = { .base = { &mp_type_module }, .globals = (mp_obj_dict_t*)&mp_module_re_globals, }; +#endif // Source files #include'd here to make sure they're compiled in // only if module is enabled by config setting. diff --git a/extmod/moduzlib.c b/extmod/moduzlib.c index f8452c72b..64327f1f7 100644 --- a/extmod/moduzlib.c +++ b/extmod/moduzlib.c @@ -122,6 +122,7 @@ STATIC mp_uint_t decompio_read(mp_obj_t o_in, void *buf, mp_uint_t size, int *er return o->decomp.dest - (byte*)buf; } +#if !MICROPY_ENABLE_DYNRUNTIME STATIC const mp_rom_map_elem_t decompio_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) }, { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, @@ -129,11 +130,13 @@ STATIC const mp_rom_map_elem_t decompio_locals_dict_table[] = { }; STATIC MP_DEFINE_CONST_DICT(decompio_locals_dict, decompio_locals_dict_table); +#endif STATIC const mp_stream_p_t decompio_stream_p = { .read = decompio_read, }; +#if !MICROPY_ENABLE_DYNRUNTIME STATIC const mp_obj_type_t decompio_type = { { &mp_type_type }, .name = MP_QSTR_DecompIO, @@ -141,6 +144,7 @@ STATIC const mp_obj_type_t decompio_type = { .protocol = &decompio_stream_p, .locals_dict = (void*)&decompio_locals_dict, }; +#endif STATIC mp_obj_t mod_uzlib_decompress(size_t n_args, const mp_obj_t *args) { mp_obj_t data = args[0]; @@ -201,6 +205,7 @@ error: } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_uzlib_decompress_obj, 1, 3, mod_uzlib_decompress); +#if !MICROPY_ENABLE_DYNRUNTIME STATIC const mp_rom_map_elem_t mp_module_uzlib_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_uzlib) }, { MP_ROM_QSTR(MP_QSTR_decompress), MP_ROM_PTR(&mod_uzlib_decompress_obj) }, @@ -213,6 +218,7 @@ const mp_obj_module_t mp_module_uzlib = { .base = { &mp_type_module }, .globals = (mp_obj_dict_t*)&mp_module_uzlib_globals, }; +#endif // Source files #include'd here to make sure they're compiled in // only if module is enabled by config setting. diff --git a/extmod/webrepl/manifest.py b/extmod/webrepl/manifest.py new file mode 100644 index 000000000..0f2b44005 --- /dev/null +++ b/extmod/webrepl/manifest.py @@ -0,0 +1 @@ +freeze('.', ('webrepl.py', 'webrepl_setup.py', 'websocket_helper.py',)) diff --git a/ports/esp8266/modules/webrepl.py b/extmod/webrepl/webrepl.py similarity index 92% rename from ports/esp8266/modules/webrepl.py rename to extmod/webrepl/webrepl.py index bbf8bdb32..24c63299d 100644 --- a/ports/esp8266/modules/webrepl.py +++ b/extmod/webrepl/webrepl.py @@ -43,8 +43,9 @@ def accept_conn(listen_sock): ws = uwebsocket.websocket(cl, True) ws = _webrepl._webrepl(ws) cl.setblocking(False) - # notify REPL on socket incoming data - cl.setsockopt(socket.SOL_SOCKET, 20, uos.dupterm_notify) + # notify REPL on socket incoming data (ESP32/ESP8266-only) + if hasattr(uos, 'dupterm_notify'): + cl.setsockopt(socket.SOL_SOCKET, 20, uos.dupterm_notify) uos.dupterm(ws) diff --git a/ports/esp8266/modules/webrepl_setup.py b/extmod/webrepl/webrepl_setup.py similarity index 100% rename from ports/esp8266/modules/webrepl_setup.py rename to extmod/webrepl/webrepl_setup.py diff --git a/ports/esp8266/modules/websocket_helper.py b/extmod/webrepl/websocket_helper.py similarity index 100% rename from ports/esp8266/modules/websocket_helper.py rename to extmod/webrepl/websocket_helper.py diff --git a/lib/lv_bindings b/lib/lv_bindings index 29ef44705..33a4cf026 160000 --- a/lib/lv_bindings +++ b/lib/lv_bindings @@ -1 +1 @@ -Subproject commit 29ef44705de7ac2ba64fc278e0aaa341b4a65554 +Subproject commit 33a4cf02601e6959255465a3f34450e6788058e8 diff --git a/lib/tinytest/tinytest.c b/lib/tinytest/tinytest.c index 01772f3f8..1ef957d31 100644 --- a/lib/tinytest/tinytest.c +++ b/lib/tinytest/tinytest.c @@ -234,9 +234,8 @@ testcase_run_one(const struct testgroup_t *group, return SKIP; } - printf("# starting %s%s\n", group->prefix, testcase->name); if (opt_verbosity>0 && !opt_forked) { - //printf("%s%s: ", group->prefix, testcase->name); + printf("%s%s: ", group->prefix, testcase->name); } else { if (opt_verbosity==0) printf("."); cur_test_prefix = group->prefix; @@ -253,7 +252,6 @@ testcase_run_one(const struct testgroup_t *group, outcome = testcase_run_bare_(testcase); } - printf("%s%s: ", group->prefix, testcase->name); if (outcome == OK) { ++n_ok; if (opt_verbosity>0 && !opt_forked) @@ -265,8 +263,7 @@ testcase_run_one(const struct testgroup_t *group, } else { ++n_bad; if (!opt_forked) - //printf("\n [%s FAILED]\n", testcase->name); - puts("FAILED"); + printf("\n [%s FAILED]\n", testcase->name); } if (opt_forked) { diff --git a/mpy-cross/README.md b/mpy-cross/README.md index e35b28b69..bf743a903 100644 --- a/mpy-cross/README.md +++ b/mpy-cross/README.md @@ -22,4 +22,10 @@ the unix port of MicroPython requires the following: $ ./mpy-cross -mcache-lookup-bc foo.py +If the Python code contains `@native` or `@viper` annotations, then you must +specify `-march` to match the target architecture. + Run `./mpy-cross -h` to get a full list of options. + +The optimisation level is 0 by default. Optimisation levels are detailed in +https://docs.micropython.org/en/latest/library/micropython.html#micropython.opt_level diff --git a/mpy-cross/main.c b/mpy-cross/main.c index 4a4fccb3b..e82277220 100644 --- a/mpy-cross/main.c +++ b/mpy-cross/main.c @@ -109,7 +109,7 @@ STATIC int usage(char **argv) { "-msmall-int-bits=number : set the maximum bits used to encode a small-int\n" "-mno-unicode : don't support unicode in compiled strings\n" "-mcache-lookup-bc : cache map lookups in the bytecode\n" -"-march= : set architecture for native emitter; x86, x64, armv6, armv7m, xtensa, xtensawin\n" +"-march= : set architecture for native emitter; x86, x64, armv6, armv7m, armv7em, armv7emsp, armv7emdp, xtensa, xtensawin\n" "\n" "Implementation specific options:\n", argv[0] ); @@ -285,6 +285,15 @@ MP_NOINLINE int main_(int argc, char **argv) { } else if (strcmp(arch, "armv7m") == 0) { mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_ARMV7M; mp_dynamic_compiler.nlr_buf_num_regs = MICROPY_NLR_NUM_REGS_ARM_THUMB_FP; + } else if (strcmp(arch, "armv7em") == 0) { + mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_ARMV7EM; + mp_dynamic_compiler.nlr_buf_num_regs = MICROPY_NLR_NUM_REGS_ARM_THUMB_FP; + } else if (strcmp(arch, "armv7emsp") == 0) { + mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_ARMV7EMSP; + mp_dynamic_compiler.nlr_buf_num_regs = MICROPY_NLR_NUM_REGS_ARM_THUMB_FP; + } else if (strcmp(arch, "armv7emdp") == 0) { + mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_ARMV7EMDP; + mp_dynamic_compiler.nlr_buf_num_regs = MICROPY_NLR_NUM_REGS_ARM_THUMB_FP; } else if (strcmp(arch, "xtensa") == 0) { mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_XTENSA; mp_dynamic_compiler.nlr_buf_num_regs = MICROPY_NLR_NUM_REGS_XTENSA; diff --git a/ports/esp32/Makefile b/ports/esp32/Makefile index 8a00f14c0..7588f309c 100644 --- a/ports/esp32/Makefile +++ b/ports/esp32/Makefile @@ -323,6 +323,7 @@ SRC_C = \ modsocket.c \ modesp.c \ esp32_partition.c \ + esp32_rmt.c \ esp32_ulp.c \ modesp32.c \ espneopixel.c \ diff --git a/ports/esp32/boards/TINYPICO/manifest.py b/ports/esp32/boards/TINYPICO/manifest.py new file mode 100644 index 000000000..81fff1d7c --- /dev/null +++ b/ports/esp32/boards/TINYPICO/manifest.py @@ -0,0 +1,2 @@ +include('$(PORT_DIR)/boards/manifest.py') +freeze("modules") diff --git a/ports/esp32/boards/TINYPICO/modules/dotstar.py b/ports/esp32/boards/TINYPICO/modules/dotstar.py new file mode 100644 index 000000000..a848e8e17 --- /dev/null +++ b/ports/esp32/boards/TINYPICO/modules/dotstar.py @@ -0,0 +1,228 @@ +# DotStar strip driver for MicroPython +# +# The MIT License (MIT) +# +# Copyright (c) 2016 Damien P. George (original Neopixel object) +# Copyright (c) 2017 Ladyada +# Copyright (c) 2017 Scott Shawcroft for Adafruit Industries +# Copyright (c) 2019 Matt Trentini (porting back to MicroPython) +# +# 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. + +START_HEADER_SIZE = 4 +LED_START = 0b11100000 # Three "1" bits, followed by 5 brightness bits + +# Pixel color order constants +RGB = (0, 1, 2) +RBG = (0, 2, 1) +GRB = (1, 0, 2) +GBR = (1, 2, 0) +BRG = (2, 0, 1) +BGR = (2, 1, 0) + + +class DotStar: + """ + A sequence of dotstars. + + :param SPI spi: The SPI object to write output to. + :param int n: The number of dotstars in the chain + :param float brightness: Brightness of the pixels between 0.0 and 1.0 + :param bool auto_write: True if the dotstars should immediately change when + set. If False, `show` must be called explicitly. + :param tuple pixel_order: Set the pixel order on the strip - different + strips implement this differently. If you send red, and it looks blue + or green on the strip, modify this! It should be one of the values above + + + Example for TinyPICO: + + .. code-block:: python + + from dotstar import DotStar + from machine import Pin, SPI + + spi = SPI(sck=Pin(12), mosi=Pin(13), miso=Pin(18)) # Configure SPI - note: miso is unused + dotstar = DotStar(spi, 1) + dotstar[0] = (128, 0, 0) # Red + """ + + def __init__(self, spi, n, *, brightness=1.0, auto_write=True, + pixel_order=BGR): + self._spi = spi + self._n = n + # Supply one extra clock cycle for each two pixels in the strip. + self.end_header_size = n // 16 + if n % 16 != 0: + self.end_header_size += 1 + self._buf = bytearray(n * 4 + START_HEADER_SIZE + self.end_header_size) + self.end_header_index = len(self._buf) - self.end_header_size + self.pixel_order = pixel_order + # Four empty bytes to start. + for i in range(START_HEADER_SIZE): + self._buf[i] = 0x00 + # Mark the beginnings of each pixel. + for i in range(START_HEADER_SIZE, self.end_header_index, 4): + self._buf[i] = 0xff + # 0xff bytes at the end. + for i in range(self.end_header_index, len(self._buf)): + self._buf[i] = 0xff + self._brightness = 1.0 + # Set auto_write to False temporarily so brightness setter does _not_ + # call show() while in __init__. + self.auto_write = False + self.brightness = brightness + self.auto_write = auto_write + + def deinit(self): + """Blank out the DotStars and release the resources.""" + self.auto_write = False + for i in range(START_HEADER_SIZE, self.end_header_index): + if i % 4 != 0: + self._buf[i] = 0 + self.show() + if self._spi: + self._spi.deinit() + + def __enter__(self): + return self + + def __exit__(self, exception_type, exception_value, traceback): + self.deinit() + + def __repr__(self): + return "[" + ", ".join([str(x) for x in self]) + "]" + + def _set_item(self, index, value): + """ + value can be one of three things: + a (r,g,b) list/tuple + a (r,g,b, brightness) list/tuple + a single, longer int that contains RGB values, like 0xFFFFFF + brightness, if specified should be a float 0-1 + + Set a pixel value. You can set per-pixel brightness here, if it's not passed it + will use the max value for pixel brightness value, which is a good default. + + Important notes about the per-pixel brightness - it's accomplished by + PWMing the entire output of the LED, and that PWM is at a much + slower clock than the rest of the LEDs. This can cause problems in + Persistence of Vision Applications + """ + + offset = index * 4 + START_HEADER_SIZE + rgb = value + if isinstance(value, int): + rgb = (value >> 16, (value >> 8) & 0xff, value & 0xff) + + if len(rgb) == 4: + brightness = value[3] + # Ignore value[3] below. + else: + brightness = 1 + + # LED startframe is three "1" bits, followed by 5 brightness bits + # then 8 bits for each of R, G, and B. The order of those 3 are configurable and + # vary based on hardware + # same as math.ceil(brightness * 31) & 0b00011111 + # Idea from https://www.codeproject.com/Tips/700780/Fast-floor-ceiling-functions + brightness_byte = 32 - int(32 - brightness * 31) & 0b00011111 + self._buf[offset] = brightness_byte | LED_START + self._buf[offset + 1] = rgb[self.pixel_order[0]] + self._buf[offset + 2] = rgb[self.pixel_order[1]] + self._buf[offset + 3] = rgb[self.pixel_order[2]] + + def __setitem__(self, index, val): + if isinstance(index, slice): + start, stop, step = index.indices(self._n) + length = stop - start + if step != 0: + # same as math.ceil(length / step) + # Idea from https://fizzbuzzer.com/implement-a-ceil-function/ + length = (length + step - 1) // step + if len(val) != length: + raise ValueError("Slice and input sequence size do not match.") + for val_i, in_i in enumerate(range(start, stop, step)): + self._set_item(in_i, val[val_i]) + else: + self._set_item(index, val) + + if self.auto_write: + self.show() + + def __getitem__(self, index): + if isinstance(index, slice): + out = [] + for in_i in range(*index.indices(self._n)): + out.append( + tuple(self._buf[in_i * 4 + (3 - i) + START_HEADER_SIZE] for i in range(3))) + return out + if index < 0: + index += len(self) + if index >= self._n or index < 0: + raise IndexError + offset = index * 4 + return tuple(self._buf[offset + (3 - i) + START_HEADER_SIZE] + for i in range(3)) + + def __len__(self): + return self._n + + @property + def brightness(self): + """Overall brightness of the pixel""" + return self._brightness + + @brightness.setter + def brightness(self, brightness): + self._brightness = min(max(brightness, 0.0), 1.0) + if self.auto_write: + self.show() + + def fill(self, color): + """Colors all pixels the given ***color***.""" + auto_write = self.auto_write + self.auto_write = False + for i in range(self._n): + self[i] = color + if auto_write: + self.show() + self.auto_write = auto_write + + def show(self): + """Shows the new colors on the pixels themselves if they haven't already + been autowritten. + + The colors may or may not be showing after this function returns because + it may be done asynchronously.""" + # Create a second output buffer if we need to compute brightness + buf = self._buf + if self.brightness < 1.0: + buf = bytearray(self._buf) + # Four empty bytes to start. + for i in range(START_HEADER_SIZE): + buf[i] = 0x00 + for i in range(START_HEADER_SIZE, self.end_header_index): + buf[i] = self._buf[i] if i % 4 == 0 else int(self._buf[i] * self._brightness) + # Four 0xff bytes at the end. + for i in range(self.end_header_index, len(buf)): + buf[i] = 0xff + + if self._spi: + self._spi.write(buf) diff --git a/ports/esp32/boards/TINYPICO/modules/tinypico.py b/ports/esp32/boards/TINYPICO/modules/tinypico.py new file mode 100644 index 000000000..2fc379ccd --- /dev/null +++ b/ports/esp32/boards/TINYPICO/modules/tinypico.py @@ -0,0 +1,113 @@ +# TinyPICO MicroPython Helper Library +# 2019 Seon Rozenblum, Matt Trentini +# +# Project home: +# https://github.com/TinyPICO +# +# 2019-Mar-12 - v0.1 - Initial implementation +# 2019-May-20 - v1.0 - Initial Release +# 2019-Oct-23 - v1.1 - Removed temp sensor code, prep for frozen modules + +# Import required libraries +from micropython import const +from machine import Pin, SPI, ADC +import machine, time, esp32 + +# TinyPICO Hardware Pin Assignments + +# Battery +BAT_VOLTAGE = const(35) +BAT_CHARGE = const(34) + +# APA102 Dotstar pins for production boards +DOTSTAR_CLK = const(12) +DOTSTAR_DATA = const(2) +DOTSTAR_PWR = const(13) + +# SPI +SPI_MOSI = const(23) +SPI_CLK = const(18) +SPI_MISO = const(19) + +#I2C +I2C_SDA = const(21) +I2C_SCL = const(22) + +#DAC +DAC1 = const(25) +DAC2 = const(26) + +# Helper functions + +# Get a *rough* estimate of the current battery voltage +# If the battery is not present, the charge IC will still report it's trying to charge at X voltage +# so it will still show a voltage. +def get_battery_voltage(): + """ + Returns the current battery voltage. If no battery is connected, returns 3.7V + This is an approximation only, but useful to detect of the charge state of the battery is getting low. + """ + adc = ADC(Pin(BAT_VOLTAGE)) # Assign the ADC pin to read + measuredvbat = adc.read() # Read the value + measuredvbat /= 4095 # divide by 4095 as we are using the default ADC voltage range of 0-1V + measuredvbat *= 3.7 # Multiply by 3.7V, our reference voltage + return measuredvbat + +# Return the current charge state of the battery - we need to read the value multiple times +# to eliminate false negatives due to the charge IC not knowing the difference between no battery +# and a full battery not charging - This is why the charge LED flashes +def get_battery_charging(): + """ + Returns the current battery charging state. + This can trigger false positives as the charge IC can't tell the difference between a full battery or no battery connected. + """ + measuredVal = 0 # start our reading at 0 + io = Pin(BAT_CHARGE, Pin.IN) # Assign the pin to read + + for y in range(0, 10): # loop through 10 times adding the read values together to ensure no false positives + measuredVal += io.value() + + return measuredVal == 0 # return True if the value is 0 + + +# Power to the on-board Dotstar is controlled by a PNP transistor, so low is ON and high is OFF +# We also need to set the Dotstar clock and data pins to be inputs to prevent power leakage when power is off +# This might be improved at a future date +# The reason we have power control for the Dotstar is that it has a quiescent current of around 1mA, so we +# need to be able to cut power to it to minimise power consumption during deep sleep or with general battery powered use +# to minimise unneeded battery drain +def set_dotstar_power(state): + """Set the power for the on-board Dostar to allow no current draw when not needed.""" + # Set the power pin to the inverse of state + if state: + Pin(DOTSTAR_PWR, Pin.OUT, None) # Break the PULL_HOLD on the pin + Pin(DOTSTAR_PWR).value(False) # Set the pin to LOW to enable the Transistor + else: + Pin(13, Pin.IN, Pin.PULL_HOLD) # Set PULL_HOLD on the pin to allow the 3V3 pull-up to work + + Pin(DOTSTAR_CLK, Pin.OUT if state else Pin.IN) # If power is on, set CLK to be output, otherwise input + Pin(DOTSTAR_DATA, Pin.OUT if state else Pin.IN) # If power is on, set DATA to be output, otherwise input + + # A small delay to let the IO change state + time.sleep(.035) + +# Dotstar rainbow colour wheel +def dotstar_color_wheel(wheel_pos): + """Color wheel to allow for cycling through the rainbow of RGB colors.""" + wheel_pos = wheel_pos % 255 + + if wheel_pos < 85: + return 255 - wheel_pos * 3, 0, wheel_pos * 3 + elif wheel_pos < 170: + wheel_pos -= 85 + return 0, wheel_pos * 3, 255 - wheel_pos * 3 + else: + wheel_pos -= 170 + return wheel_pos * 3, 255 - wheel_pos * 3, 0 + +# Go into deep sleep but shut down the APA first to save power +# Use this if you want lowest deep sleep current +def go_deepsleep(t): + """Deep sleep helper that also powers down the on-board Dotstar.""" + set_dotstar_power(False) + machine.deepsleep(t) diff --git a/ports/esp32/boards/TINYPICO/mpconfigboard.mk b/ports/esp32/boards/TINYPICO/mpconfigboard.mk index 485b3f165..5c96ec45a 100644 --- a/ports/esp32/boards/TINYPICO/mpconfigboard.mk +++ b/ports/esp32/boards/TINYPICO/mpconfigboard.mk @@ -4,3 +4,5 @@ SDKCONFIG += boards/sdkconfig.base SDKCONFIG += boards/sdkconfig.240mhz SDKCONFIG += boards/sdkconfig.spiram SDKCONFIG += boards/TINYPICO/sdkconfig.board + +FROZEN_MANIFEST ?= $(BOARD_DIR)/manifest.py diff --git a/ports/esp32/boards/manifest.py b/ports/esp32/boards/manifest.py index 2b07639ee..bab2b614b 100644 --- a/ports/esp32/boards/manifest.py +++ b/ports/esp32/boards/manifest.py @@ -1,6 +1,6 @@ freeze('$(PORT_DIR)/modules') freeze('$(MPY_DIR)/tools', ('upip.py', 'upip_utarfile.py')) freeze('$(MPY_DIR)/ports/esp8266/modules', 'ntptime.py') -freeze('$(MPY_DIR)/ports/esp8266/modules', ('webrepl.py', 'webrepl_setup.py', 'websocket_helper.py',)) freeze('$(MPY_DIR)/drivers/dht', 'dht.py') freeze('$(MPY_DIR)/drivers/onewire') +include('$(MPY_DIR)/extmod/webrepl/manifest.py') diff --git a/ports/esp32/boards/sdkconfig.base b/ports/esp32/boards/sdkconfig.base index d60d30969..c441b780a 100644 --- a/ports/esp32/boards/sdkconfig.base +++ b/ports/esp32/boards/sdkconfig.base @@ -34,6 +34,9 @@ CONFIG_LWIP_PPP_CHAP_SUPPORT=y # Use 4kiB output buffer instead of default 16kiB (because IDF heap is fragmented in 4.0) CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN=y +# ULP coprocessor support +CONFIG_ESP32_ULP_COPROC_ENABLED=y + # v3.3-only (renamed in 4.0) CONFIG_LOG_BOOTLOADER_LEVEL_WARN=y CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0=n @@ -43,6 +46,7 @@ CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK=y CONFIG_PPP_SUPPORT=y CONFIG_PPP_PAP_SUPPORT=y CONFIG_PPP_CHAP_SUPPORT=y +CONFIG_ULP_COPROC_ENABLED=y # Logs CONFIG_LOG_DEFAULT_LEVEL_WARN=y diff --git a/ports/esp32/esp32_rmt.c b/ports/esp32/esp32_rmt.c new file mode 100644 index 000000000..1356456bc --- /dev/null +++ b/ports/esp32/esp32_rmt.c @@ -0,0 +1,237 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 "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. + */ + +#include "py/runtime.h" +#include "modmachine.h" +#include "mphalport.h" +#include "driver/rmt.h" + +// This exposes the ESP32's RMT module to MicroPython. RMT is provided by the Espressif ESP-IDF: +// +// https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/rmt.html +// +// With some examples provided: +// +// https://github.com/espressif/arduino-esp32/tree/master/libraries/ESP32/examples/RMT +// +// RMT allows accurate (down to 12.5ns resolution) transmit - and receive - of pulse signals. +// Originally designed to generate infrared remote control signals, the module is very +// flexible and quite easy-to-use. +// +// This current MicroPython implementation lacks some major features, notably receive pulses +// and carrier output. + +// Forward declaration +extern const mp_obj_type_t esp32_rmt_type; + +typedef struct _esp32_rmt_obj_t { + mp_obj_base_t base; + uint8_t channel_id; + gpio_num_t pin; + uint8_t clock_div; + mp_uint_t num_items; + rmt_item32_t* items; +} esp32_rmt_obj_t; + +// Defined in machine_time.c; simply added the error message +// Fixme: Should use this updated error hadline more widely in the ESP32 port. +// At least update the method in machine_time.c. +STATIC esp_err_t check_esp_err(esp_err_t code) { + if (code) { + mp_raise_msg(&mp_type_OSError, esp_err_to_name(code)); + } + + return code; +} + +STATIC mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_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_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + mp_uint_t channel_id = args[0].u_int; + gpio_num_t pin_id = machine_pin_get_id(args[1].u_obj); + mp_uint_t clock_div = args[2].u_int; + + if (clock_div < 1 || clock_div > 255) { + mp_raise_ValueError("clock_div must be between 1 and 255"); + } + + esp32_rmt_obj_t *self = m_new_obj_with_finaliser(esp32_rmt_obj_t); + self->base.type = &esp32_rmt_type; + self->channel_id = channel_id; + self->pin = pin_id; + self->clock_div = clock_div; + + rmt_config_t config; + config.rmt_mode = RMT_MODE_TX; + config.channel = (rmt_channel_t) self->channel_id; + config.gpio_num = self->pin; + config.mem_block_num = 1; + config.tx_config.loop_en = 0; + + config.tx_config.carrier_en = 0; + 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_level = 1; + + config.clk_div = self->clock_div; + + check_esp_err(rmt_config(&config)); + check_esp_err(rmt_driver_install(config.channel, 0, 0)); + + return MP_OBJ_FROM_PTR(self); +} + +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)", + self->channel_id, self->pin, APB_CLK_FREQ, self->clock_div); + } else { + mp_printf(print, "RMT()"); + } +} + +STATIC mp_obj_t esp32_rmt_deinit(mp_obj_t self_in) { + // fixme: check for valid channel. Return exception if error occurs. + esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (self->pin != -1) { // Check if channel has already been deinitialised. + rmt_driver_uninstall(self->channel_id); + self->pin = -1; // -1 to indicate RMT is unused + m_free(self->items); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_deinit_obj, esp32_rmt_deinit); + +// Return the source frequency. +// Currently only the APB clock (80MHz) can be used but it is possible other +// clock sources will added in the future. +STATIC mp_obj_t esp32_rmt_source_freq(mp_obj_t self_in) { + return mp_obj_new_int(APB_CLK_FREQ); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_source_freq_obj, esp32_rmt_source_freq); + +// Return the clock divider. +STATIC mp_obj_t esp32_rmt_clock_div(mp_obj_t self_in) { + esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(self->clock_div); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_clock_div_obj, esp32_rmt_clock_div); + +// Query whether the channel has finished sending pulses. Takes an optional +// timeout (in ticks of the 80MHz clock), returning true if the pulse stream has +// completed or false if they are still transmitting (or timeout is reached). +STATIC mp_obj_t esp32_rmt_wait_done(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_self, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(args[0].u_obj); + + esp_err_t err = rmt_wait_tx_done(self->channel_id, args[1].u_int); + return err == ESP_OK ? mp_const_true : mp_const_false; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(esp32_rmt_wait_done_obj, 1, esp32_rmt_wait_done); + +STATIC mp_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))); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(esp32_rmt_loop_obj, esp32_rmt_loop); + +STATIC mp_obj_t esp32_rmt_write_pulses(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_self, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_pulses, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_start, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(args[0].u_obj); + mp_obj_t pulses = args[1].u_obj; + mp_uint_t start = args[2].u_int; + + if (start < 0 || start > 1) { + mp_raise_ValueError("start must be 0 or 1"); + } + + size_t pulses_length = 0; + mp_obj_t* pulses_ptr = NULL; + mp_obj_get_array(pulses, &pulses_length, &pulses_ptr); + + mp_uint_t num_items = (pulses_length / 2) + (pulses_length % 2); + if (num_items > self->num_items) { + self->items = (rmt_item32_t*)m_realloc(self->items, num_items * sizeof(rmt_item32_t *)); + self->num_items = num_items; + } + + for (mp_uint_t item_index = 0; item_index < num_items; item_index++) { + mp_uint_t pulse_index = item_index * 2; + self->items[item_index].duration0 = mp_obj_get_int(pulses_ptr[pulse_index++]); + self->items[item_index].level0 = start++; // Note that start _could_ wrap. + if (pulse_index < pulses_length) { + self->items[item_index].duration1 = mp_obj_get_int(pulses_ptr[pulse_index]); + self->items[item_index].level1 = start++; + } + } + check_esp_err(rmt_write_items(self->channel_id, self->items, num_items, false /* non-blocking */)); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(esp32_rmt_write_pulses_obj, 2, esp32_rmt_write_pulses); + +STATIC const mp_rom_map_elem_t esp32_rmt_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&esp32_rmt_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&esp32_rmt_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_source_freq), MP_ROM_PTR(&esp32_rmt_source_freq_obj) }, + { MP_ROM_QSTR(MP_QSTR_clock_div), MP_ROM_PTR(&esp32_rmt_clock_div_obj) }, + { MP_ROM_QSTR(MP_QSTR_wait_done), MP_ROM_PTR(&esp32_rmt_wait_done_obj) }, + { MP_ROM_QSTR(MP_QSTR_loop), MP_ROM_PTR(&esp32_rmt_loop_obj) }, + { MP_ROM_QSTR(MP_QSTR_write_pulses), MP_ROM_PTR(&esp32_rmt_write_pulses_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(esp32_rmt_locals_dict, esp32_rmt_locals_dict_table); + +const mp_obj_type_t esp32_rmt_type = { + { &mp_type_type }, + .name = MP_QSTR_RMT, + .print = esp32_rmt_print, + .make_new = esp32_rmt_make_new, + .locals_dict = (mp_obj_dict_t*)&esp32_rmt_locals_dict, +}; diff --git a/ports/esp32/main.c b/ports/esp32/main.c index ec48faf32..80cf8509f 100644 --- a/ports/esp32/main.c +++ b/ports/esp32/main.c @@ -47,6 +47,7 @@ #include "py/nlr.h" #include "py/compile.h" #include "py/runtime.h" +#include "py/persistentcode.h" #include "py/repl.h" #include "py/gc.h" #include "py/mphal.h" @@ -173,12 +174,15 @@ void mbedtls_debug_set_threshold(int threshold) { (void)threshold; } -void *esp_native_code_commit(void *buf, size_t len) { +void *esp_native_code_commit(void *buf, size_t len, void *reloc) { len = (len + 3) & ~3; uint32_t *p = heap_caps_malloc(len, MALLOC_CAP_EXEC); if (p == NULL) { m_malloc_fail(len); } + if (reloc) { + mp_native_relocate(reloc, buf, (uintptr_t)p); + } memcpy(p, buf, len); return p; } diff --git a/ports/esp32/modesp32.c b/ports/esp32/modesp32.c index ddc030e3f..77617113f 100644 --- a/ports/esp32/modesp32.c +++ b/ports/esp32/modesp32.c @@ -155,6 +155,7 @@ STATIC const mp_rom_map_elem_t esp32_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_hall_sensor), MP_ROM_PTR(&esp32_hall_sensor_obj) }, { MP_ROM_QSTR(MP_QSTR_Partition), MP_ROM_PTR(&esp32_partition_type) }, + { MP_ROM_QSTR(MP_QSTR_RMT), MP_ROM_PTR(&esp32_rmt_type) }, { MP_ROM_QSTR(MP_QSTR_ULP), MP_ROM_PTR(&esp32_ulp_type) }, { MP_ROM_QSTR(MP_QSTR_WAKEUP_ALL_LOW), MP_ROM_PTR(&mp_const_false_obj) }, diff --git a/ports/esp32/modesp32.h b/ports/esp32/modesp32.h index 26eec8ae6..f04bdba67 100644 --- a/ports/esp32/modesp32.h +++ b/ports/esp32/modesp32.h @@ -27,6 +27,7 @@ #define RTC_IS_VALID_EXT_PIN(pin_id) ((1ll << (pin_id)) & RTC_VALID_EXT_PINS) extern const mp_obj_type_t esp32_partition_type; +extern const mp_obj_type_t esp32_rmt_type; extern const mp_obj_type_t esp32_ulp_type; #endif // MICROPY_INCLUDED_ESP32_MODESP32_H diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index c3cf6d2da..f62fdfe25 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -21,8 +21,6 @@ // emitters #define MICROPY_PERSISTENT_CODE_LOAD (1) #define MICROPY_EMIT_XTENSAWIN (1) -void *esp_native_code_commit(void*, size_t); -#define MP_PLAT_COMMIT_EXEC(buf, len) esp_native_code_commit(buf, len) // compiler configuration #define MICROPY_COMP_MODULE_CONST (1) @@ -284,6 +282,8 @@ struct mp_bluetooth_nimble_root_pointers_t; #define BYTES_PER_WORD (4) #define MICROPY_MAKE_POINTER_CALLABLE(p) ((void*)((mp_uint_t)(p))) #define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn_cooked(str, len) +void *esp_native_code_commit(void*, size_t, void*); +#define MP_PLAT_COMMIT_EXEC(buf, len, reloc) esp_native_code_commit(buf, len, reloc) #define MP_SSIZE_MAX (0x7fffffff) // Note: these "critical nested" macros do not ensure cross-CPU exclusion, diff --git a/ports/esp8266/boards/manifest.py b/ports/esp8266/boards/manifest.py index 779e84088..b6df53fc6 100644 --- a/ports/esp8266/boards/manifest.py +++ b/ports/esp8266/boards/manifest.py @@ -2,3 +2,4 @@ freeze('$(PORT_DIR)/modules') freeze('$(MPY_DIR)/tools', ('upip.py', 'upip_utarfile.py')) freeze('$(MPY_DIR)/drivers/dht', 'dht.py') freeze('$(MPY_DIR)/drivers/onewire') +include('$(MPY_DIR)/extmod/webrepl/manifest.py') diff --git a/ports/esp8266/modesp.c b/ports/esp8266/modesp.c index 2aeb3d690..a3cc2eca7 100644 --- a/ports/esp8266/modesp.c +++ b/ports/esp8266/modesp.c @@ -28,6 +28,7 @@ #include "py/gc.h" #include "py/runtime.h" +#include "py/persistentcode.h" #include "py/mperrno.h" #include "py/mphal.h" #include "drivers/dht/dht.h" @@ -282,7 +283,7 @@ void esp_native_code_init(void) { esp_native_code_erased = 0; } -void *esp_native_code_commit(void *buf, size_t len) { +void *esp_native_code_commit(void *buf, size_t len, void *reloc) { //printf("COMMIT(buf=%p, len=%u, start=%08x, cur=%08x, end=%08x, erased=%08x)\n", buf, len, esp_native_code_start, esp_native_code_cur, esp_native_code_end, esp_native_code_erased); len = (len + 3) & ~3; @@ -294,6 +295,14 @@ void *esp_native_code_commit(void *buf, size_t len) { void *dest; if (esp_native_code_location == ESP_NATIVE_CODE_IRAM1) { dest = (void*)esp_native_code_cur; + } else { + dest = (void*)(FLASH_START + esp_native_code_cur); + } + if (reloc) { + mp_native_relocate(reloc, buf, (uintptr_t)dest); + } + + if (esp_native_code_location == ESP_NATIVE_CODE_IRAM1) { memcpy(dest, buf, len); } else { SpiFlashOpResult res; @@ -313,7 +322,6 @@ void *esp_native_code_commit(void *buf, size_t len) { if (res != SPI_FLASH_RESULT_OK) { mp_raise_OSError(res == SPI_FLASH_RESULT_TIMEOUT ? MP_ETIMEDOUT : MP_EIO); } - dest = (void*)(FLASH_START + esp_native_code_cur); } esp_native_code_cur += len; diff --git a/ports/esp8266/mpconfigport.h b/ports/esp8266/mpconfigport.h index 22ca99b2b..81544bb2a 100644 --- a/ports/esp8266/mpconfigport.h +++ b/ports/esp8266/mpconfigport.h @@ -131,8 +131,8 @@ typedef uint32_t sys_prot_t; // for modlwip #include #define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn_cooked(str, len) -void *esp_native_code_commit(void*, size_t); -#define MP_PLAT_COMMIT_EXEC(buf, len) esp_native_code_commit(buf, len) +void *esp_native_code_commit(void*, size_t, void*); +#define MP_PLAT_COMMIT_EXEC(buf, len, reloc) esp_native_code_commit(buf, len, reloc) // printer for debugging output, goes to UART only extern const struct _mp_print_t mp_debug_print; diff --git a/ports/nrf/boards/particle_xenon/mpconfigboard.h b/ports/nrf/boards/particle_xenon/mpconfigboard.h index c2aabce48..4d8e8337a 100644 --- a/ports/nrf/boards/particle_xenon/mpconfigboard.h +++ b/ports/nrf/boards/particle_xenon/mpconfigboard.h @@ -38,6 +38,8 @@ #define MICROPY_PY_MACHINE_TEMP (1) #define MICROPY_PY_RANDOM_HW_RNG (1) +#define MICROPY_HW_USB_CDC (1) + #define MICROPY_HW_HAS_LED (1) #define MICROPY_HW_LED_TRICOLOR (1) #define MICROPY_HW_LED_PULLUP (1) diff --git a/ports/nrf/main.c b/ports/nrf/main.c index f7d42060e..9ffe7a285 100644 --- a/ports/nrf/main.c +++ b/ports/nrf/main.c @@ -230,7 +230,7 @@ pin_init0(); led_state(1, 0); -#if MICROPY_VFS || MICROPY_MBFS +#if MICROPY_VFS || MICROPY_MBFS || MICROPY_MODULE_FROZEN // run boot.py and main.py if they exist. pyexec_file_if_exists("boot.py"); pyexec_file_if_exists("main.py"); diff --git a/ports/qemu-arm/Makefile b/ports/qemu-arm/Makefile index e06e5dd5e..cb7dcb021 100644 --- a/ports/qemu-arm/Makefile +++ b/ports/qemu-arm/Makefile @@ -31,14 +31,11 @@ LDSCRIPT = mps2.ld SRC_BOARD_O = lib/utils/gchelper_m3.o endif -CROSS_COMPILE = arm-none-eabi- - -TINYTEST = $(TOP)/lib/tinytest +CROSS_COMPILE ?= arm-none-eabi- INC += -I. INC += -I$(TOP) INC += -I$(BUILD) -INC += -I$(TINYTEST) CFLAGS += $(INC) -Wall -Wpointer-arith -Werror -std=gnu99 $(COPT) \ -ffunction-sections -fdata-sections @@ -71,6 +68,7 @@ SRC_RUN_C = \ SRC_TEST_C = \ test_main.c \ + lib/tinytest/tinytest.c \ LIB_SRC_C += $(addprefix lib/,\ libc/string0.c \ @@ -108,7 +106,6 @@ ALL_OBJ_RUN = $(OBJ_COMMON) $(OBJ_RUN) OBJ_TEST = OBJ_TEST += $(addprefix $(BUILD)/, $(SRC_TEST_C:.c=.o)) -OBJ_TEST += $(BUILD)/tinytest.o ALL_OBJ_TEST = $(OBJ_COMMON) $(OBJ_TEST) diff --git a/ports/qemu-arm/Makefile.test b/ports/qemu-arm/Makefile.test index df0ba9939..340e1c3b9 100644 --- a/ports/qemu-arm/Makefile.test +++ b/ports/qemu-arm/Makefile.test @@ -13,8 +13,7 @@ $(BUILD)/genhdr/tests.h: (cd $(TOP)/tests; ./run-tests --target=qemu-arm --write-exp) $(Q)echo "Generating $@";(cd $(TOP)/tests; ../tools/tinytest-codegen.py) > $@ -$(BUILD)/tinytest.o: - $(Q)$(CC) $(CFLAGS) -DNO_FORKING -o $@ -c $(TINYTEST)/tinytest.c +$(BUILD)/lib/tinytest/tinytest.o: CFLAGS += -DNO_FORKING $(BUILD)/firmware-test.elf: $(LDSCRIPT) $(ALL_OBJ_TEST) $(Q)$(LD) $(LDFLAGS) -o $@ $(ALL_OBJ_TEST) $(LIBS) diff --git a/ports/qemu-arm/test_main.c b/ports/qemu-arm/test_main.c index 3a85d65f3..3fd2c0148 100644 --- a/ports/qemu-arm/test_main.c +++ b/ports/qemu-arm/test_main.c @@ -12,9 +12,8 @@ #include "py/gc.h" #include "py/mperrno.h" #include "lib/utils/gchelper.h" - -#include "tinytest.h" -#include "tinytest_macros.h" +#include "lib/tinytest/tinytest.h" +#include "lib/tinytest/tinytest_macros.h" #define HEAP_SIZE (100 * 1024) diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index 61ce06ca8..c9fdc7b0d 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -408,6 +408,10 @@ ifneq ($(MICROPY_PY_WIZNET5K),0) WIZNET5K_DIR=drivers/wiznet5k INC += -I$(TOP)/$(WIZNET5K_DIR) CFLAGS_MOD += -DMICROPY_PY_WIZNET5K=$(MICROPY_PY_WIZNET5K) -D_WIZCHIP_=$(MICROPY_PY_WIZNET5K) +ifeq ($(MICROPY_PY_LWIP),1) +# When using MACRAW mode (with lwIP), maximum buffer space must be used for the raw socket +CFLAGS_MOD += -DWIZCHIP_USE_MAX_BUFFER +endif SRC_MOD += network_wiznet5k.c modnwwiznet5k.c SRC_MOD += $(addprefix $(WIZNET5K_DIR)/,\ ethernet/w$(MICROPY_PY_WIZNET5K)/w$(MICROPY_PY_WIZNET5K).c \ diff --git a/ports/stm32/boards/NUCLEO_F767ZI/mpconfigboard.h b/ports/stm32/boards/NUCLEO_F767ZI/mpconfigboard.h index 1ee73c303..b659223b4 100644 --- a/ports/stm32/boards/NUCLEO_F767ZI/mpconfigboard.h +++ b/ports/stm32/boards/NUCLEO_F767ZI/mpconfigboard.h @@ -1,7 +1,5 @@ -// This board is only confirmed to operate using DFU mode and openocd. -// DFU mode can be accessed by setting BOOT0 (see schematics) -// To use openocd run "OPENOCD_CONFIG=boards/openocd_stm32f7.cfg" in -// the make command. +// Note: if the board shows odd behaviour check the option bits and make sure nDBANK is +// set to make the 2MByte space continuous instead of divided into two 1MByte segments. #define MICROPY_HW_BOARD_NAME "NUCLEO-F767ZI" #define MICROPY_HW_MCU_NAME "STM32F767" @@ -12,6 +10,7 @@ #define MICROPY_HW_ENABLE_RTC (1) #define MICROPY_HW_ENABLE_DAC (1) #define MICROPY_HW_ENABLE_USB (1) +#define MICROPY_HW_ENABLE_SDCARD (1) #define MICROPY_BOARD_EARLY_INIT NUCLEO_F767ZI_board_early_init void NUCLEO_F767ZI_board_early_init(void); @@ -30,16 +29,25 @@ void NUCLEO_F767ZI_board_early_init(void); #define MICROPY_HW_UART2_CTS (pin_D3) #define MICROPY_HW_UART3_TX (pin_D8) #define MICROPY_HW_UART3_RX (pin_D9) -#define MICROPY_HW_UART6_TX (pin_G14) -#define MICROPY_HW_UART6_RX (pin_G9) +#define MICROPY_HW_UART6_TX (pin_C6) +#define MICROPY_HW_UART6_RX (pin_C7) +#define MICROPY_HW_UART5_TX (pin_B6) +#define MICROPY_HW_UART5_RX (pin_B12) +#define MICROPY_HW_UART7_TX (pin_F7) +#define MICROPY_HW_UART7_RX (pin_F6) +#define MICROPY_HW_UART8_TX (pin_E1) +#define MICROPY_HW_UART8_RX (pin_E0) + #define MICROPY_HW_UART_REPL PYB_UART_3 #define MICROPY_HW_UART_REPL_BAUD 115200 // I2C busses #define MICROPY_HW_I2C1_SCL (pin_B8) #define MICROPY_HW_I2C1_SDA (pin_B9) -#define MICROPY_HW_I2C3_SCL (pin_H7) -#define MICROPY_HW_I2C3_SDA (pin_H8) +#define MICROPY_HW_I2C2_SCL (pin_F1) +#define MICROPY_HW_I2C2_SDA (pin_F0) +#define MICROPY_HW_I2C4_SCL (pin_F14) +#define MICROPY_HW_I2C4_SDA (pin_F15) // SPI #define MICROPY_HW_SPI3_NSS (pin_A4) @@ -48,10 +56,8 @@ void NUCLEO_F767ZI_board_early_init(void); #define MICROPY_HW_SPI3_MOSI (pin_B5) // CAN busses -#define MICROPY_HW_CAN1_TX (pin_B9) -#define MICROPY_HW_CAN1_RX (pin_B8) -#define MICROPY_HW_CAN2_TX (pin_B13) -#define MICROPY_HW_CAN2_RX (pin_B12) +#define MICROPY_HW_CAN1_TX (pin_D1) +#define MICROPY_HW_CAN1_RX (pin_D0) // USRSW is pulled low. Pressing the button makes the input go high. #define MICROPY_HW_USRSW_PIN (pin_C13) @@ -71,6 +77,11 @@ void NUCLEO_F767ZI_board_early_init(void); #define MICROPY_HW_USB_VBUS_DETECT_PIN (pin_A9) #define MICROPY_HW_USB_OTG_ID_PIN (pin_A10) +// SD card detect switch (actual pin may need to be changed for a particular use) +#define MICROPY_HW_SDCARD_DETECT_PIN (pin_G2) +#define MICROPY_HW_SDCARD_DETECT_PULL (GPIO_PULLUP) +#define MICROPY_HW_SDCARD_DETECT_PRESENT (GPIO_PIN_RESET) + // Ethernet via RMII #define MICROPY_HW_ETH_MDC (pin_C1) #define MICROPY_HW_ETH_MDIO (pin_A2) diff --git a/ports/stm32/boards/NUCLEO_F767ZI/pins.csv b/ports/stm32/boards/NUCLEO_F767ZI/pins.csv index d84f8e9d1..e447b76b0 100644 --- a/ports/stm32/boards/NUCLEO_F767ZI/pins.csv +++ b/ports/stm32/boards/NUCLEO_F767ZI/pins.csv @@ -33,43 +33,97 @@ D22,PB5 D23,PB3 D24,PA4 D25,PB4 +D26,PB6 +D27,PB2 +D28,PD13 +D29,PD12 +D30,PD11 +D31,PE2 +D32,PA0 +D33,PB0 +D34,PE0 +D35,PB11 +D36,PB10 +D37,PE15 +D38,PE14 +D39,PE12 +D40,PE10 +D41,PE7 +D42,PE8 +D43,PC8 +D44,PC9 +D45,PC10 +D46,PC11 +D47,PC12 +D48,PD2 +D49,PG2 +D50,PG3 +D51,PD7 +D52,PD6 +D53,PD5 +D54,PD4 +D55,PD3 +D56,PE2 +D57,PE4 +D58,PE5 +D59,PE6 +D60,PE3 +D61,PF8 +D62,PF7 +D63,PF9 +D64,PG1 +D65,PG0 +D66,PD1 +D67,PD0 +D68,PF0 +D69,PF1 +D70,PF2 +D71,PA7 +DAC1,PA4 +DAC2,PA5 LED1,PB0 LED2,PB7 LED3,PB14 SW,PC13 -TP1,PH2 -TP2,PI8 -TP3,PH15 -AUDIO_INT,PD6 -AUDIO_SDA,PH8 -AUDIO_SCL,PH7 -EXT_SDA,PB9 -EXT_SCL,PB8 -EXT_RST,PG3 -SD_SW,PC13 -LCD_BL_CTRL,PK3 -LCD_INT,PI13 -LCD_SDA,PH8 -LCD_SCL,PH7 -OTG_FS_POWER,PD5 -OTG_FS_OVER_CURRENT,PD4 -OTG_HS_OVER_CURRENT,PE3 +SD_D0,PC8 +SD_D1,PC9 +SD_D2,PC10 +SD_D3,PC11 +SD_CMD,PD2 +SD_CK,PC12 +SD_SW,PG2 +OTG_FS_POWER,PG6 +OTG_FS_OVER_CURRENT,PG7 USB_VBUS,PA9 USB_ID,PA10 USB_DM,PA11 USB_DP,PA12 -USB_POWER,PG6 -VCP_TX,PD8 -VCP_RX,PD9 UART2_TX,PD5 UART2_RX,PD6 UART2_RTS,PD4 UART2_CTS,PD3 -UART6_TX,PG14 -UART6_RX,PG9 -SPI_B_NSS,PA4 -SPI_B_SCK,PB3 -SPI_B_MOSI,PB5 +VCP_TX,PD8 +VCP_RX,PD9 +UART3_TX,PD8 +UART3_RX,PD9 +UART5_TX,PB6 +UART5_RX,PB12 +UART6_TX,PC6 +UART6_RX,PC7 +UART7_TX,PF7 +UART7_RX,PF6 +UART8_TX,PE1 +UART8_RX,PE0 +SPI3_NSS,PA4 +SPI3_SCK,PB3 +SPI3_MISO,PB4 +SPI3_MOSI,PB5 +I2C1_SDA,PB9 +I2C1_SCL,PB8 +I2C2_SDA,PF0 +I2C2_SCL,PF1 +I2C4_SCL,PF14 +I2C4_SDA,PF15 ETH_MDC,PC1 ETH_MDIO,PA2 ETH_RMII_REF_CLK,PA1 @@ -79,3 +133,5 @@ ETH_RMII_RXD1,PC5 ETH_RMII_TX_EN,PG11 ETH_RMII_TXD0,PG13 ETH_RMII_TXD1,PB13 +SWDIO,PA13 +SWDCLK,PA14 diff --git a/ports/stm32/boards/NUCLEO_H743ZI/mpconfigboard.h b/ports/stm32/boards/NUCLEO_H743ZI/mpconfigboard.h index 40ce889ab..4e6b16226 100644 --- a/ports/stm32/boards/NUCLEO_H743ZI/mpconfigboard.h +++ b/ports/stm32/boards/NUCLEO_H743ZI/mpconfigboard.h @@ -31,8 +31,21 @@ void NUCLEO_H743ZI_board_early_init(void); #define MICROPY_HW_FLASH_LATENCY FLASH_LATENCY_4 // UART config +#define MICROPY_HW_UART2_TX (pin_D5) +#define MICROPY_HW_UART2_RX (pin_D6) +#define MICROPY_HW_UART2_RTS (pin_D4) +#define MICROPY_HW_UART2_CTS (pin_D3) #define MICROPY_HW_UART3_TX (pin_D8) #define MICROPY_HW_UART3_RX (pin_D9) +#define MICROPY_HW_UART5_TX (pin_B6) +#define MICROPY_HW_UART5_RX (pin_B12) +#define MICROPY_HW_UART6_TX (pin_C6) +#define MICROPY_HW_UART6_RX (pin_C7) +#define MICROPY_HW_UART7_TX (pin_F7) +#define MICROPY_HW_UART7_RX (pin_F6) +#define MICROPY_HW_UART8_TX (pin_E1) +#define MICROPY_HW_UART8_RX (pin_E0) + #define MICROPY_HW_UART_REPL PYB_UART_3 #define MICROPY_HW_UART_REPL_BAUD 115200 @@ -41,6 +54,8 @@ void NUCLEO_H743ZI_board_early_init(void); #define MICROPY_HW_I2C1_SDA (pin_B9) #define MICROPY_HW_I2C2_SCL (pin_F1) #define MICROPY_HW_I2C2_SDA (pin_F0) +#define MICROPY_HW_I2C4_SCL (pin_F14) +#define MICROPY_HW_I2C4_SDA (pin_F15) // SPI #define MICROPY_HW_SPI3_NSS (pin_A4) @@ -75,3 +90,14 @@ void NUCLEO_H743ZI_board_early_init(void); #define MICROPY_HW_SDCARD_DETECT_PIN (pin_G2) #define MICROPY_HW_SDCARD_DETECT_PULL (GPIO_PULLUP) #define MICROPY_HW_SDCARD_DETECT_PRESENT (GPIO_PIN_RESET) + +// Ethernet via RMII (MDC define disabled for now until eth.c supports H7) +//#define MICROPY_HW_ETH_MDC (pin_C1) +#define MICROPY_HW_ETH_MDIO (pin_A2) +#define MICROPY_HW_ETH_RMII_REF_CLK (pin_A1) +#define MICROPY_HW_ETH_RMII_CRS_DV (pin_A7) +#define MICROPY_HW_ETH_RMII_RXD0 (pin_C4) +#define MICROPY_HW_ETH_RMII_RXD1 (pin_C5) +#define MICROPY_HW_ETH_RMII_TX_EN (pin_G11) +#define MICROPY_HW_ETH_RMII_TXD0 (pin_G13) +#define MICROPY_HW_ETH_RMII_TXD1 (pin_B13) diff --git a/ports/stm32/boards/NUCLEO_H743ZI/mpconfigboard.mk b/ports/stm32/boards/NUCLEO_H743ZI/mpconfigboard.mk index 1d232e080..ce8f83e57 100644 --- a/ports/stm32/boards/NUCLEO_H743ZI/mpconfigboard.mk +++ b/ports/stm32/boards/NUCLEO_H743ZI/mpconfigboard.mk @@ -16,3 +16,8 @@ LD_FILES = boards/stm32h743.ld boards/common_ifs.ld TEXT0_ADDR = 0x08000000 TEXT1_ADDR = 0x08040000 endif + +# MicroPython settings +MICROPY_PY_LWIP = 1 +MICROPY_PY_USSL = 1 +MICROPY_SSL_MBEDTLS = 1 diff --git a/ports/stm32/boards/NUCLEO_H743ZI/pins.csv b/ports/stm32/boards/NUCLEO_H743ZI/pins.csv index daa36691b..d3647ca42 100644 --- a/ports/stm32/boards/NUCLEO_H743ZI/pins.csv +++ b/ports/stm32/boards/NUCLEO_H743ZI/pins.csv @@ -1,46 +1,97 @@ -A0,PA0 -A1,PF10 -A2,PF9 -A3,PF8 -A4,PF7 -A5,PF6 -D0,PC7 -D1,PC6 -D2,PG6 -D3,PB4 -D4,PG7 -D5,PA8 -D6,PH6 -D7,PI3 -D8,PI2 -D9,PA15 -D10,PI0 -D11,PB15 -D12,PB14 -D13,PI1 +A0,PA3 +A1,PC0 +A2,PC3 +A3,PB1 +A4,PC2 +A5,PF10 +A6,PF4 +A7,PF5 +A8,PF6 +D0,PB7 +D1,PB6 +D2,PG14 +D3,PE13 +D4,PE14 +D5,PE11 +D6,PE9 +D7,PG12 +D8,PF3 +D9,PD15 +D10,PD14 +D11,PB5 +D12,PA6 +D13,PA7 D14,PB9 D15,PB8 +D16,PC6 +D17,PB15 +D18,PB13 +D19,PB12 +D20,PA15 +D21,PC7 D22,PB5 D23,PB3 -D67,PD0 +D24,PA4 +D25,PB4 +D26,PG6 +D27,PB2 +D28,PD13 +D29,PD12 +D30,PD11 +D31,PE2 +D32,PA0 +D33,PB0 +D34,PE0 +D35,PB11 +D36,PB10 +D37,PE15 +D38,PE6 +D39,PE12 +D40,PE10 +D41,PE7 +D42,PE8 +D43,PC8 +D44,PC9 +D45,PC10 +D46,PC11 +D47,PC12 +D48,PD2 +D49,PG2 +D50,PG3 +D51,PD7 +D52,PD6 +D53,PD5 +D54,PD4 +D55,PD3 +D56,PE2 +D57,PE4 +D58,PE5 +D59,PE6 +D60,PE3 +D61,PF8 +D62,PF7 +D63,PF9 +D64,PG1 +D65,PG0 D66,PD1 +D67,PD0 +D68,PF0 +D69,PF1 +D70,PF2 +D71,PE9 +D72,PB2 DAC1,PA4 DAC2,PA5 LED1,PB0 LED2,PB7 LED3,PB14 SW,PC13 -TP1,PH2 -TP2,PI8 -TP3,PH15 -AUDIO_INT,PD6 -AUDIO_SDA,PH8 -AUDIO_SCL,PH7 I2C1_SDA,PB9 I2C1_SCL,PB8 I2C2_SDA,PF0 I2C2_SCL,PF1 -EXT_RST,PG3 +I2C4_SCL,PF14 +I2C4_SDA,PF15 SD_D0,PC8 SD_D1,PC9 SD_D2,PC10 @@ -48,20 +99,32 @@ SD_D3,PC11 SD_CMD,PD2 SD_CK,PC12 SD_SW,PG2 -LCD_BL_CTRL,PK3 -LCD_INT,PI13 -LCD_SDA,PH8 -LCD_SCL,PH7 -OTG_FS_POWER,PD5 -OTG_FS_OVER_CURRENT,PD4 -OTG_HS_OVER_CURRENT,PE3 -USB_VBUS,PJ12 -USB_ID,PA8 +OTG_FS_POWER,PG6 +OTG_FS_OVER_CURRENT,PG7 +USB_VBUS,PA9 +USB_ID,PA10 USB_DM,PA11 USB_DP,PA12 -UART1_TX,PA9 -UART1_RX,PA10 -UART5_TX,PC12 -UART5_RX,PD2 +UART2_TX,PD5 +UART2_RX,PD6 +UART2_RTS,PD4 +UART2_CTS,PD3 UART3_TX,PD8 UART3_RX,PD9 +UART5_TX,PB6 +UART5_RX,PB12 +UART6_TX,PC6 +UART6_RX,PC7 +UART7_TX,PF7 +UART7_RX,PF6 +UART8_TX,PE1 +UART8_RX,PE0 +ETH_MDC,PC1 +ETH_MDIO,PA2 +ETH_RMII_REF_CLK,PA1 +ETH_RMII_CRS_DV,PA7 +ETH_RMII_RXD0,PC4 +ETH_RMII_RXD1,PC5 +ETH_RMII_TX_EN,PG11 +ETH_RMII_TXD0,PG13 +ETH_RMII_TXD1,PB13 diff --git a/ports/stm32/boards/NUCLEO_WB55/mpconfigboard.h b/ports/stm32/boards/NUCLEO_WB55/mpconfigboard.h index 54747cb04..2992ccce7 100644 --- a/ports/stm32/boards/NUCLEO_WB55/mpconfigboard.h +++ b/ports/stm32/boards/NUCLEO_WB55/mpconfigboard.h @@ -7,6 +7,7 @@ #define MICROPY_PY_PYB_LEGACY (0) +#define MICROPY_HW_HAS_FLASH (1) #define MICROPY_HW_ENABLE_RTC (1) #define MICROPY_HW_ENABLE_RNG (1) #define MICROPY_HW_ENABLE_ADC (0) diff --git a/ports/stm32/boards/NUCLEO_WB55/mpconfigboard.mk b/ports/stm32/boards/NUCLEO_WB55/mpconfigboard.mk index cd9f104ef..416364df9 100644 --- a/ports/stm32/boards/NUCLEO_WB55/mpconfigboard.mk +++ b/ports/stm32/boards/NUCLEO_WB55/mpconfigboard.mk @@ -7,3 +7,4 @@ STARTUP_FILE = lib/stm32lib/CMSIS/STM32WBxx/Source/Templates/gcc/startup_stm32wb # MicroPython settings MICROPY_PY_BLUETOOTH = 1 MICROPY_BLUETOOTH_NIMBLE = 1 +MICROPY_VFS_LFS2 = 1 diff --git a/ports/stm32/boards/PYBD_SF2/manifest.py b/ports/stm32/boards/PYBD_SF2/manifest.py new file mode 100644 index 000000000..48cc2ce93 --- /dev/null +++ b/ports/stm32/boards/PYBD_SF2/manifest.py @@ -0,0 +1,2 @@ +include('$(PORT_DIR)/boards/manifest.py') +include('$(MPY_DIR)/extmod/webrepl/manifest.py') diff --git a/ports/stm32/boards/PYBD_SF2/mpconfigboard.h b/ports/stm32/boards/PYBD_SF2/mpconfigboard.h index 8e116bd02..4ce17abd8 100644 --- a/ports/stm32/boards/PYBD_SF2/mpconfigboard.h +++ b/ports/stm32/boards/PYBD_SF2/mpconfigboard.h @@ -154,6 +154,7 @@ extern struct _spi_bdev_t spi_bdev2; #define MICROPY_HW_USRSW_PRESSED (0) // LEDs +#define MICROPY_HW_LED_INVERTED (1) // LEDs are on when pin is driven low #define MICROPY_HW_LED1 (pyb_pin_LED_RED) #define MICROPY_HW_LED2 (pyb_pin_LED_GREEN) #define MICROPY_HW_LED3 (pyb_pin_LED_BLUE) diff --git a/ports/stm32/boards/PYBD_SF2/mpconfigboard.mk b/ports/stm32/boards/PYBD_SF2/mpconfigboard.mk index 9c0121f31..69407345b 100644 --- a/ports/stm32/boards/PYBD_SF2/mpconfigboard.mk +++ b/ports/stm32/boards/PYBD_SF2/mpconfigboard.mk @@ -17,3 +17,6 @@ MICROPY_PY_NETWORK_CYW43 = 1 MICROPY_PY_USSL = 1 MICROPY_SSL_MBEDTLS = 1 MICROPY_VFS_LFS2 = 1 + +# PYBD-specific frozen modules +FROZEN_MANIFEST = $(BOARD_DIR)/manifest.py diff --git a/ports/stm32/boards/PYBD_SF3/mpconfigboard.mk b/ports/stm32/boards/PYBD_SF3/mpconfigboard.mk index 7bef5651d..f6d6e955b 100644 --- a/ports/stm32/boards/PYBD_SF3/mpconfigboard.mk +++ b/ports/stm32/boards/PYBD_SF3/mpconfigboard.mk @@ -16,3 +16,6 @@ MICROPY_PY_LWIP = 1 MICROPY_PY_NETWORK_CYW43 = 1 MICROPY_PY_USSL = 1 MICROPY_SSL_MBEDTLS = 1 + +# PYBD-specific frozen modules +FROZEN_MANIFEST = boards/PYBD_SF2/manifest.py diff --git a/ports/stm32/boards/PYBD_SF6/mpconfigboard.mk b/ports/stm32/boards/PYBD_SF6/mpconfigboard.mk index f85d9c997..e33d62d86 100644 --- a/ports/stm32/boards/PYBD_SF6/mpconfigboard.mk +++ b/ports/stm32/boards/PYBD_SF6/mpconfigboard.mk @@ -13,3 +13,6 @@ MICROPY_PY_LWIP = 1 MICROPY_PY_NETWORK_CYW43 = 1 MICROPY_PY_USSL = 1 MICROPY_SSL_MBEDTLS = 1 + +# PYBD-specific frozen modules +FROZEN_MANIFEST = boards/PYBD_SF2/manifest.py diff --git a/ports/stm32/boards/USBDONGLE_WB55/mpconfigboard.h b/ports/stm32/boards/USBDONGLE_WB55/mpconfigboard.h index eeedfb084..0ff751d61 100644 --- a/ports/stm32/boards/USBDONGLE_WB55/mpconfigboard.h +++ b/ports/stm32/boards/USBDONGLE_WB55/mpconfigboard.h @@ -7,6 +7,7 @@ #define MICROPY_PY_PYB_LEGACY (0) +#define MICROPY_HW_HAS_FLASH (1) #define MICROPY_HW_ENABLE_RTC (1) #define MICROPY_HW_ENABLE_RNG (1) #define MICROPY_HW_ENABLE_ADC (0) diff --git a/ports/stm32/boards/USBDONGLE_WB55/mpconfigboard.mk b/ports/stm32/boards/USBDONGLE_WB55/mpconfigboard.mk index cd9f104ef..416364df9 100644 --- a/ports/stm32/boards/USBDONGLE_WB55/mpconfigboard.mk +++ b/ports/stm32/boards/USBDONGLE_WB55/mpconfigboard.mk @@ -7,3 +7,4 @@ STARTUP_FILE = lib/stm32lib/CMSIS/STM32WBxx/Source/Templates/gcc/startup_stm32wb # MicroPython settings MICROPY_PY_BLUETOOTH = 1 MICROPY_BLUETOOTH_NIMBLE = 1 +MICROPY_VFS_LFS2 = 1 diff --git a/ports/stm32/boards/stm32f767.ld b/ports/stm32/boards/stm32f767.ld index 9410b9fa6..d07f2ecbe 100644 --- a/ports/stm32/boards/stm32f767.ld +++ b/ports/stm32/boards/stm32f767.ld @@ -5,7 +5,7 @@ /* Specify the memory areas */ MEMORY { - FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K + FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K FLASH_ISR (rx) : ORIGIN = 0x08000000, LENGTH = 32K /* sector 0, 32K */ FLASH_APP (rx) : ORIGIN = 0x08008000, LENGTH = 2016K /* sectors 1-11 3x32K 1*128K 7*256K */ FLASH_FS (r) : ORIGIN = 0x08008000, LENGTH = 96K /* sectors 1, 2, 3 (32K each) */ diff --git a/ports/stm32/main.c b/ports/stm32/main.c index 8cb6ed1e3..fd9cdd6f6 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -644,7 +644,7 @@ soft_reset: // if an SD card is present then mount it on /sd/ if (sdcard_is_present()) { // if there is a file in the flash called "SKIPSD", then we don't mount the SD card - if (!mounted_flash || mp_vfs_import_stat("SKIPSD") == MP_IMPORT_STAT_FILE) { + if (!mounted_flash || mp_vfs_import_stat("SKIPSD") == MP_IMPORT_STAT_NO_EXIST) { mounted_sdcard = init_sdcard_fs(); } } diff --git a/ports/stm32/modusocket.c b/ports/stm32/modusocket.c index 46d7240ca..9af6e371f 100644 --- a/ports/stm32/modusocket.c +++ b/ports/stm32/modusocket.c @@ -369,6 +369,13 @@ mp_uint_t socket_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int * } return 0; } + if (self->nic == MP_OBJ_NULL) { + if (request == MP_STREAM_POLL) { + return MP_STREAM_POLL_NVAL; + } + *errcode = MP_EINVAL; + return MP_STREAM_ERROR; + } return self->nic_type->ioctl(self, request, arg, errcode); } diff --git a/ports/stm32/mpconfigboard_common.h b/ports/stm32/mpconfigboard_common.h index e0c77719e..ce5c715ac 100644 --- a/ports/stm32/mpconfigboard_common.h +++ b/ports/stm32/mpconfigboard_common.h @@ -196,7 +196,7 @@ #define PYB_EXTI_NUM_VECTORS (30) // TODO (22 configurable, 7 direct) #define MICROPY_HW_MAX_I2C (3) #define MICROPY_HW_MAX_TIMER (22) -#define MICROPY_HW_MAX_UART (4) +#define MICROPY_HW_MAX_UART (5) // Configuration for STM32L4 series #elif defined(STM32L4) diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index fc54026f6..becde2b91 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -366,6 +366,10 @@ static inline mp_uint_t disable_irq(void) { #define MICROPY_PY_LWIP_REENTER irq_state = raise_irq_pri(IRQ_PRI_PENDSV); #define MICROPY_PY_LWIP_EXIT restore_irq_pri(irq_state); +// 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 + // We need an implementation of the log2 function which is not a macro #define MP_NEED_LOG2 (1) diff --git a/ports/stm32/stm32_it.c b/ports/stm32/stm32_it.c index 3a4709d9f..e77642b8e 100644 --- a/ports/stm32/stm32_it.c +++ b/ports/stm32/stm32_it.c @@ -609,6 +609,14 @@ void TIM1_UP_TIM16_IRQHandler(void) { } #endif +#if defined(STM32H7) +void TIM1_UP_IRQHandler(void) { + IRQ_ENTER(TIM1_UP_IRQn); + timer_irq_handler(1); + IRQ_EXIT(TIM1_UP_IRQn); +} +#endif + void TIM1_TRG_COM_TIM11_IRQHandler(void) { IRQ_ENTER(TIM1_TRG_COM_TIM11_IRQn); timer_irq_handler(11); @@ -705,6 +713,26 @@ void TIM8_TRG_COM_TIM14_IRQHandler(void) { } #endif +#if defined(STM32H7) +void TIM15_IRQHandler(void) { + IRQ_ENTER(TIM15_IRQn); + timer_irq_handler(15); + IRQ_EXIT(TIM15_IRQn); +} + +void TIM16_IRQHandler(void) { + IRQ_ENTER(TIM16_IRQn); + timer_irq_handler(16); + IRQ_EXIT(TIM16_IRQn); +} + +void TIM17_IRQHandler(void) { + IRQ_ENTER(TIM17_IRQn); + timer_irq_handler(17); + IRQ_EXIT(TIM17_IRQn); +} +#endif + // UART/USART IRQ handlers void USART1_IRQHandler(void) { IRQ_ENTER(USART1_IRQn); @@ -731,6 +759,15 @@ void USART3_8_IRQHandler(void) { IRQ_EXIT(USART3_8_IRQn); } +#elif defined(STM32L0) + +void USART4_5_IRQHandler(void) { + IRQ_ENTER(USART4_5_IRQn); + uart_irq_handler(4); + uart_irq_handler(5); + IRQ_EXIT(USART4_5_IRQn); +} + #else void USART3_IRQHandler(void) { diff --git a/ports/stm32/timer.c b/ports/stm32/timer.c index 834ebd9c8..2e8f3e05b 100644 --- a/ports/stm32/timer.c +++ b/ports/stm32/timer.c @@ -759,6 +759,8 @@ STATIC const uint32_t tim_instance_table[MICROPY_HW_MAX_TIMER] = { TIM_ENTRY(1, TIM1_BRK_UP_TRG_COM_IRQn), #elif defined(STM32F4) || defined(STM32F7) TIM_ENTRY(1, TIM1_UP_TIM10_IRQn), + #elif defined(STM32H7) + TIM_ENTRY(1, TIM1_UP_IRQn), #elif defined(STM32L4) TIM_ENTRY(1, TIM1_UP_TIM16_IRQn), #endif @@ -780,7 +782,7 @@ STATIC const uint32_t tim_instance_table[MICROPY_HW_MAX_TIMER] = { TIM_ENTRY(7, TIM7_IRQn), #endif #if defined(TIM8) - #if defined(STM32F4) || defined(STM32F7) + #if defined(STM32F4) || defined(STM32F7) || defined(STM32H7) TIM_ENTRY(8, TIM8_UP_TIM13_IRQn), #elif defined(STM32L4) TIM_ENTRY(8, TIM8_UP_IRQn), diff --git a/ports/stm32/uart.c b/ports/stm32/uart.c index 72bb53f80..d2d234a2c 100644 --- a/ports/stm32/uart.c +++ b/ports/stm32/uart.c @@ -247,6 +247,10 @@ bool uart_init(pyb_uart_obj_t *uart_obj, UARTx = USART4; irqn = USART3_8_IRQn; __HAL_RCC_USART4_CLK_ENABLE(); + #elif defined(STM32L0) + UARTx = USART4; + irqn = USART4_5_IRQn; + __HAL_RCC_USART4_CLK_ENABLE(); #else UARTx = UART4; irqn = UART4_IRQn; @@ -274,6 +278,10 @@ bool uart_init(pyb_uart_obj_t *uart_obj, UARTx = USART5; irqn = USART3_8_IRQn; __HAL_RCC_USART5_CLK_ENABLE(); + #elif defined(STM32L0) + UARTx = USART5; + irqn = USART4_5_IRQn; + __HAL_RCC_USART5_CLK_ENABLE(); #else UARTx = UART5; irqn = UART5_IRQn; diff --git a/py/asmbase.h b/py/asmbase.h index d2b403893..b5e259358 100644 --- a/py/asmbase.h +++ b/py/asmbase.h @@ -60,7 +60,7 @@ static inline size_t mp_asm_base_get_code_size(mp_asm_base_t *as) { static inline void *mp_asm_base_get_code(mp_asm_base_t *as) { #if defined(MP_PLAT_COMMIT_EXEC) - return MP_PLAT_COMMIT_EXEC(as->code_base, as->code_size); + return MP_PLAT_COMMIT_EXEC(as->code_base, as->code_size, NULL); #else return as->code_base; #endif diff --git a/py/bc.h b/py/bc.h index fd52571fd..a96d17a0d 100644 --- a/py/bc.h +++ b/py/bc.h @@ -74,7 +74,7 @@ #define MP_BC_PRELUDE_SIG_ENCODE(S, E, scope, out_byte, out_env) \ do { \ /*// Get values to store in prelude */ \ - size_t F = scope->scope_flags & 0x0f; /* only need to store lower 4 flag bits */ \ + size_t F = scope->scope_flags & MP_SCOPE_FLAG_ALL_SIG; \ size_t A = scope->num_pos_args; \ size_t K = scope->num_kwonly_args; \ size_t D = scope->num_def_pos_args; \ diff --git a/py/dynruntime.h b/py/dynruntime.h new file mode 100644 index 000000000..7092a5dd8 --- /dev/null +++ b/py/dynruntime.h @@ -0,0 +1,218 @@ +/* + * 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. + */ +#ifndef MICROPY_INCLUDED_PY_DYNRUNTIME_H +#define MICROPY_INCLUDED_PY_DYNRUNTIME_H + +// This header file contains definitions to dynamically implement the static +// MicroPython runtime API defined in py/obj.h and py/runtime.h. + +#include "py/nativeglue.h" +#include "py/objstr.h" + +#undef MP_ROM_QSTR +#undef MP_OBJ_QSTR_VALUE +#undef MP_OBJ_NEW_QSTR +#undef mp_const_none +#undef mp_const_false +#undef mp_const_true +#undef mp_const_empty_tuple +#undef nlr_raise + +/******************************************************************************/ +// Memory allocation + +#define m_malloc(n) (m_malloc_dyn((n))) +#define m_free(ptr) (m_free_dyn((ptr))) +#define m_realloc(ptr, new_num_bytes) (m_realloc_dyn((ptr), (new_num_bytes))) + +static inline void *m_malloc_dyn(size_t n) { + // TODO won't raise on OOM + return mp_fun_table.realloc_(NULL, n, false); +} + +static inline void m_free_dyn(void *ptr) { + mp_fun_table.realloc_(ptr, 0, false); +} + +static inline void *m_realloc_dyn(void *ptr, size_t new_num_bytes) { + // TODO won't raise on OOM + return mp_fun_table.realloc_(ptr, new_num_bytes, true); +} + +/******************************************************************************/ +// Printing + +#define mp_plat_print (*mp_fun_table.plat_print) +#define mp_printf(p, ...) (mp_fun_table.printf_((p), __VA_ARGS__)) +#define mp_vprintf(p, fmt, args) (mp_fun_table.vprintf_((p), (fmt), (args))) + +/******************************************************************************/ +// Types and objects + +#define MP_OBJ_NEW_QSTR(x) MP_OBJ_NEW_QSTR_ ## x + +#define mp_type_type (*mp_fun_table.type_type) +#define mp_type_str (*mp_fun_table.type_str) +#define mp_type_list (*mp_fun_table.type_list) +#define mp_type_EOFError (*(mp_obj_type_t*)(mp_load_global(MP_QSTR_EOFError))) +#define mp_type_IndexError (*(mp_obj_type_t*)(mp_load_global(MP_QSTR_IndexError))) +#define mp_type_KeyError (*(mp_obj_type_t*)(mp_load_global(MP_QSTR_KeyError))) +#define mp_type_NotImplementedError (*(mp_obj_type_t*)(mp_load_global(MP_QSTR_NotImplementedError))) +#define mp_type_RuntimeError (*(mp_obj_type_t*)(mp_load_global(MP_QSTR_RuntimeError))) +#define mp_type_TypeError (*(mp_obj_type_t*)(mp_load_global(MP_QSTR_TypeError))) +#define mp_type_ValueError (*(mp_obj_type_t*)(mp_load_global(MP_QSTR_ValueError))) + +#define mp_stream_read_obj (*mp_fun_table.stream_read_obj) +#define mp_stream_readinto_obj (*mp_fun_table.stream_readinto_obj) +#define mp_stream_unbuffered_readline_obj (*mp_fun_table.stream_unbuffered_readline_obj) +#define mp_stream_write_obj (*mp_fun_table.stream_write_obj) + +#define mp_const_none ((mp_obj_t)mp_fun_table.const_none) +#define mp_const_false ((mp_obj_t)mp_fun_table.const_false) +#define mp_const_true ((mp_obj_t)mp_fun_table.const_true) +#define mp_const_empty_tuple (mp_fun_table.new_tuple(0, NULL)) + +#define mp_obj_new_bool(b) ((b) ? (mp_obj_t)mp_fun_table.const_true : (mp_obj_t)mp_fun_table.const_false) +#define mp_obj_new_int(i) (mp_fun_table.native_to_obj(i, MP_NATIVE_TYPE_INT)) +#define mp_obj_new_int_from_uint(i) (mp_fun_table.native_to_obj(i, MP_NATIVE_TYPE_UINT)) +#define mp_obj_new_str(data, len) (mp_fun_table.obj_new_str((data), (len))) +#define mp_obj_new_str_of_type(t, d, l) (mp_obj_new_str_of_type_dyn((t), (d), (l))) +#define mp_obj_new_bytes(data, len) (mp_fun_table.obj_new_bytes((data), (len))) +#define mp_obj_new_bytearray_by_ref(n, i) (mp_fun_table.obj_new_bytearray_by_ref((n), (i))) +#define mp_obj_new_tuple(n, items) (mp_fun_table.new_tuple((n), (items))) +#define mp_obj_new_list(n, items) (mp_fun_table.new_list((n), (items))) + +#define mp_obj_get_type(o) (mp_fun_table.obj_get_type((o))) +#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_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))) + +#define mp_obj_len(o) (mp_obj_len_dyn(o)) +#define mp_obj_subscr(base, index, val) (mp_fun_table.obj_subscr((base), (index), (val))) +#define mp_obj_list_append(list, item) (mp_fun_table.list_append((list), (item))) + +static inline mp_obj_t mp_obj_new_str_of_type_dyn(const mp_obj_type_t *type, const byte* data, size_t len) { + if (type == &mp_type_str) { + return mp_obj_new_str((const char*)data, len); + } else { + return mp_obj_new_bytes(data, len); + } +} + +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; + return bufinfo.buf; +} + +static inline mp_obj_t mp_obj_len_dyn(mp_obj_t o) { + // If bytes implemented MP_UNARY_OP_LEN could use: mp_unary_op(MP_UNARY_OP_LEN, o) + return mp_fun_table.call_function_n_kw(mp_fun_table.load_name(MP_QSTR_len), 1, &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_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))) + +#define mp_make_function_from_raw_code(rc, def_args, def_kw_args) \ + (mp_fun_table.make_function_from_raw_code((rc), (def_args), (def_kw_args))) + +#define mp_call_function_n_kw(fun, n_args, n_kw, args) \ + (mp_fun_table.call_function_n_kw((fun), (n_args) | ((n_kw) << 8), args)) + +#define mp_arg_check_num(n_args, n_kw, n_args_min, n_args_max, takes_kw) \ + (mp_fun_table.arg_check_num_sig((n_args), (n_kw), MP_OBJ_FUN_MAKE_SIG((n_args_min), (n_args_max), (takes_kw)))) + +#define MP_DYNRUNTIME_INIT_ENTRY \ + mp_obj_t old_globals = mp_fun_table.swap_globals(self->globals); \ + mp_raw_code_t rc; \ + rc.kind = MP_CODE_NATIVE_VIPER; \ + rc.scope_flags = 0; \ + rc.const_table = (void*)self->const_table; \ + (void)rc; + +#define MP_DYNRUNTIME_INIT_EXIT \ + mp_fun_table.swap_globals(old_globals); \ + return mp_const_none; + +#define MP_DYNRUNTIME_MAKE_FUNCTION(f) \ + (mp_make_function_from_raw_code((rc.fun_data = (f), &rc), MP_OBJ_NULL, MP_OBJ_NULL)) + +/******************************************************************************/ +// Exceptions + +#define mp_obj_new_exception(o) ((mp_obj_t)(o)) // Assumes returned object will be raised, will create instance then +#define mp_obj_new_exception_arg1(e_type, arg) (mp_obj_new_exception_arg1_dyn((e_type), (arg))) + +#define nlr_raise(o) (mp_raise_dyn(o)) +#define mp_raise_msg(type, msg) (mp_fun_table.raise_msg((type), (msg))) +#define mp_raise_OSError(er) (mp_raise_OSError_dyn(er)) +#define mp_raise_NotImplementedError(msg) (mp_raise_msg(&mp_type_NotImplementedError, (msg))) +#define mp_raise_TypeError(msg) (mp_raise_msg(&mp_type_TypeError, (msg))) +#define mp_raise_ValueError(msg) (mp_raise_msg(&mp_type_ValueError, (msg))) + +static inline mp_obj_t mp_obj_new_exception_arg1_dyn(const mp_obj_type_t *exc_type, mp_obj_t arg) { + mp_obj_t args[1] = { arg }; + return mp_call_function_n_kw(MP_OBJ_FROM_PTR(exc_type), 1, 0, &args[0]); +} + +static NORETURN inline void mp_raise_dyn(mp_obj_t o) { + mp_fun_table.raise(o); + for (;;) { + } +} + +static inline void mp_raise_OSError_dyn(int er) { + mp_obj_t args[1] = { MP_OBJ_NEW_SMALL_INT(er) }; + nlr_raise(mp_call_function_n_kw(mp_load_global(MP_QSTR_OSError), 1, 0, &args[0])); +} + +/******************************************************************************/ +// Floating point + +#define mp_obj_new_float_from_f(f) (mp_fun_table.obj_new_float_from_f((f))) +#define mp_obj_new_float_from_d(d) (mp_fun_table.obj_new_float_from_d((d))) +#define mp_obj_get_float_to_f(o) (mp_fun_table.obj_get_float_to_f((o))) +#define mp_obj_get_float_to_d(o) (mp_fun_table.obj_get_float_to_d((o))) + +#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT +#define mp_obj_new_float(f) (mp_obj_new_float_from_f((f))) +#define mp_obj_get_float(o) (mp_obj_get_float_to_f((o))) +#elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE +#define mp_obj_new_float(f) (mp_obj_new_float_from_d((f))) +#define mp_obj_get_float(o) (mp_obj_get_float_to_d((o))) +#endif + +#endif // MICROPY_INCLUDED_PY_DYNRUNTIME_H diff --git a/py/dynruntime.mk b/py/dynruntime.mk new file mode 100644 index 000000000..8b65745af --- /dev/null +++ b/py/dynruntime.mk @@ -0,0 +1,144 @@ +# Makefile fragment for generating native .mpy files from C source +# MPY_DIR must be set to the top of the MicroPython source tree + +BUILD ?= build + +ECHO = @echo +RM = /bin/rm +MKDIR = /bin/mkdir +PYTHON = python3 +MPY_CROSS = $(MPY_DIR)/mpy-cross/mpy-cross +MPY_TOOL = $(PYTHON) $(MPY_DIR)/tools/mpy-tool.py +MPY_LD = $(PYTHON) $(MPY_DIR)/tools/mpy_ld.py + +Q = @ +ifeq ("$(origin V)", "command line") +ifeq ($(V),1) +Q = +MPY_LD += '-vvv' +endif +endif + +ARCH_UPPER = $(shell echo $(ARCH) | tr '[:lower:]' '[:upper:]') +CONFIG_H = $(BUILD)/$(MOD).config.h + +CFLAGS += -I. -I$(MPY_DIR) +CFLAGS += -std=c99 +CFLAGS += -Os +CFLAGS += -Wall -Werror -DNDEBUG +CFLAGS += -DNO_QSTR +CFLAGS += -DMP_CONFIGFILE='<$(CONFIG_H)>' +CFLAGS += -fpic -fno-common +CFLAGS += -U _FORTIFY_SOURCE # prevent use of __*_chk libc functions +#CFLAGS += -fdata-sections -ffunction-sections + +MPY_CROSS_FLAGS += -march=$(ARCH) + +SRC_O += $(addprefix $(BUILD)/, $(patsubst %.c,%.o,$(filter %.c,$(SRC)))) +SRC_MPY += $(addprefix $(BUILD)/, $(patsubst %.py,%.mpy,$(filter %.py,$(SRC)))) + +################################################################################ +# Architecture configuration + +ifeq ($(ARCH),x86) + +# x86 +CROSS = +CFLAGS += -m32 -fno-stack-protector +MPY_CROSS_FLAGS += -mcache-lookup-bc +MICROPY_FLOAT_IMPL ?= double + +else ifeq ($(ARCH),x64) + +# x64 +CROSS = +CFLAGS += -fno-stack-protector +MPY_CROSS_FLAGS += -mcache-lookup-bc +MICROPY_FLOAT_IMPL ?= double + +else ifeq ($(ARCH),armv7m) + +# thumb +CROSS = arm-none-eabi- +CFLAGS += -mthumb -mcpu=cortex-m3 +MICROPY_FLOAT_IMPL ?= none + +else ifeq ($(ARCH),armv7emsp) + +# thumb +CROSS = arm-none-eabi- +CFLAGS += -mthumb -mcpu=cortex-m4 +CFLAGS += -mfpu=fpv4-sp-d16 -mfloat-abi=hard +MICROPY_FLOAT_IMPL ?= float + +else ifeq ($(ARCH),armv7emdp) + +# thumb +CROSS = arm-none-eabi- +CFLAGS += -mthumb -mcpu=cortex-m7 +CFLAGS += -mfpu=fpv5-d16 -mfloat-abi=hard +MICROPY_FLOAT_IMPL ?= double + +else ifeq ($(ARCH),xtensa) + +# xtensa +CROSS = xtensa-lx106-elf- +CFLAGS += -mforce-l32 +MICROPY_FLOAT_IMPL ?= none + +else ifeq ($(ARCH),xtensawin) + +# xtensawin +CROSS = xtensa-esp32-elf- +CFLAGS += +MICROPY_FLOAT_IMPL ?= float + +else +$(error architecture '$(ARCH)' not supported) +endif + +MICROPY_FLOAT_IMPL_UPPER = $(shell echo $(MICROPY_FLOAT_IMPL) | tr '[:lower:]' '[:upper:]') +CFLAGS += -DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_$(MICROPY_FLOAT_IMPL_UPPER) + +CFLAGS += $(CFLAGS_EXTRA) + +################################################################################ +# Build rules + +.PHONY: all clean + +all: $(MOD).mpy + +clean: + $(RM) -rf $(BUILD) $(CLEAN_EXTRA) + +# Create build destination directories first +BUILD_DIRS = $(sort $(dir $(CONFIG_H) $(SRC_O) $(SRC_MPY))) +$(CONFIG_H) $(SRC_O) $(SRC_MPY): | $(BUILD_DIRS) +$(BUILD_DIRS): + $(Q)$(MKDIR) -p $@ + +# Preprocess all source files to generate $(CONFIG_H) +$(CONFIG_H): $(SRC) + $(ECHO) "GEN $@" + $(Q)$(MPY_LD) --arch $(ARCH) --preprocess -o $@ $^ + +# Build .o from .c source files +$(BUILD)/%.o: %.c $(CONFIG_H) Makefile + $(ECHO) "CC $<" + $(Q)$(CROSS)gcc $(CFLAGS) -o $@ -c $< + +# Build .mpy from .py source files +$(BUILD)/%.mpy: %.py + $(ECHO) "MPY $<" + $(Q)$(MPY_CROSS) $(MPY_CROSS_FLAGS) -o $@ $< + +# Build native .mpy from object files +$(BUILD)/$(MOD).native.mpy: $(SRC_O) + $(ECHO) "LINK $<" + $(Q)$(MPY_LD) --arch $(ARCH) --qstrs $(CONFIG_H) -o $@ $^ + +# Build final .mpy from all intermediate .mpy files +$(MOD).mpy: $(BUILD)/$(MOD).native.mpy $(SRC_MPY) + $(ECHO) "GEN $@" + $(Q)$(MPY_TOOL) --merge -o $@ $^ diff --git a/py/emitnative.c b/py/emitnative.c index fbf665914..07b984b78 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -47,7 +47,7 @@ #include #include "py/emit.h" -#include "py/bc.h" +#include "py/nativeglue.h" #include "py/objstr.h" #if MICROPY_DEBUG_VERBOSE // print debugging info @@ -687,7 +687,7 @@ STATIC void emit_native_end_pass(emit_t *emit) { #if !MICROPY_DYNAMIC_COMPILER // Store mp_fun_table pointer just after qstrs // (but in dynamic-compiler mode eliminate dependency on mp_fun_table) - emit->const_table[nqstr] = (mp_uint_t)(uintptr_t)mp_fun_table; + emit->const_table[nqstr] = (mp_uint_t)(uintptr_t)&mp_fun_table; #endif #if MICROPY_PERSISTENT_CODE_SAVE diff --git a/py/emitnx86.c b/py/emitnx86.c index 790cae04c..f0553f068 100644 --- a/py/emitnx86.c +++ b/py/emitnx86.c @@ -1,7 +1,7 @@ // x86 specific stuff #include "py/mpconfig.h" -#include "py/runtime0.h" +#include "py/nativeglue.h" #if MICROPY_EMIT_X86 diff --git a/py/mpconfig.h b/py/mpconfig.h index e46da3e83..1e786f753 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 11 +#define MICROPY_VERSION_MINOR 12 #define MICROPY_VERSION_MICRO 0 // Combined version as a 32-bit number for convenience diff --git a/py/nativeglue.c b/py/nativeglue.c index 08fbd3c30..1a7f92f94 100644 --- a/py/nativeglue.c +++ b/py/nativeglue.c @@ -24,14 +24,15 @@ * THE SOFTWARE. */ +#include #include #include #include #include "py/runtime.h" #include "py/smallint.h" -#include "py/emitglue.h" -#include "py/bc.h" +#include "py/nativeglue.h" +#include "py/gc.h" #if MICROPY_DEBUG_VERBOSE // print debugging info #define DEBUG_printf DEBUG_printf @@ -210,8 +211,50 @@ STATIC bool mp_native_yield_from(mp_obj_t gen, mp_obj_t send_value, mp_obj_t *re return false; } +#if MICROPY_PY_BUILTINS_FLOAT + +STATIC mp_obj_t mp_obj_new_float_from_f(float f) { + return mp_obj_new_float((mp_float_t)f); +} + +STATIC mp_obj_t mp_obj_new_float_from_d(double d) { + return mp_obj_new_float((mp_float_t)d); +} + +STATIC float mp_obj_get_float_to_f(mp_obj_t o) { + return (float)mp_obj_get_float(o); +} + +STATIC double mp_obj_get_float_to_d(mp_obj_t o) { + return (double)mp_obj_get_float(o); +} + +#else + +STATIC mp_obj_t mp_obj_new_float_from_f(float f) { + (void)f; + mp_raise_msg(&mp_type_RuntimeError, "float unsupported"); +} + +STATIC mp_obj_t mp_obj_new_float_from_d(double d) { + (void)d; + mp_raise_msg(&mp_type_RuntimeError, "float unsupported"); +} + +STATIC float mp_obj_get_float_to_f(mp_obj_t o) { + (void)o; + mp_raise_msg(&mp_type_RuntimeError, "float unsupported"); +} + +STATIC double mp_obj_get_float_to_d(mp_obj_t o) { + (void)o; + mp_raise_msg(&mp_type_RuntimeError, "float unsupported"); +} + +#endif + // these must correspond to the respective enum in runtime0.h -const void *const mp_fun_table[MP_F_NUMBER_OF] = { +const mp_fun_table_t mp_fun_table = { &mp_const_none_obj, &mp_const_false_obj, &mp_const_true_obj, @@ -270,6 +313,37 @@ const void *const mp_fun_table[MP_F_NUMBER_OF] = { #else NULL, #endif + // Additional entries for dynamic runtime, starts at index 50 + memset, + memmove, + gc_realloc, + mp_printf, + mp_vprintf, + mp_raise_msg, + mp_obj_get_type, + mp_obj_new_str, + mp_obj_new_bytes, + mp_obj_new_bytearray_by_ref, + mp_obj_new_float_from_f, + mp_obj_new_float_from_d, + mp_obj_get_float_to_f, + mp_obj_get_float_to_d, + mp_get_buffer_raise, + mp_get_stream_raise, + &mp_plat_print, + &mp_type_type, + &mp_type_str, + &mp_type_list, + &mp_type_dict, + &mp_type_fun_builtin_0, + &mp_type_fun_builtin_1, + &mp_type_fun_builtin_2, + &mp_type_fun_builtin_3, + &mp_type_fun_builtin_var, + &mp_stream_read_obj, + &mp_stream_readinto_obj, + &mp_stream_unbuffered_readline_obj, + &mp_stream_write_obj, }; #endif // MICROPY_EMIT_NATIVE diff --git a/py/nativeglue.h b/py/nativeglue.h new file mode 100644 index 000000000..021e7a8ec --- /dev/null +++ b/py/nativeglue.h @@ -0,0 +1,177 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013-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_PY_NATIVEGLUE_H +#define MICROPY_INCLUDED_PY_NATIVEGLUE_H + +#include +#include "py/obj.h" +#include "py/persistentcode.h" +#include "py/stream.h" + +typedef enum { + MP_F_CONST_NONE_OBJ = 0, + MP_F_CONST_FALSE_OBJ, + MP_F_CONST_TRUE_OBJ, + MP_F_CONVERT_OBJ_TO_NATIVE, + MP_F_CONVERT_NATIVE_TO_OBJ, + MP_F_NATIVE_SWAP_GLOBALS, + MP_F_LOAD_NAME, + MP_F_LOAD_GLOBAL, + MP_F_LOAD_BUILD_CLASS, + MP_F_LOAD_ATTR, + MP_F_LOAD_METHOD, + MP_F_LOAD_SUPER_METHOD, + MP_F_STORE_NAME, + MP_F_STORE_GLOBAL, + MP_F_STORE_ATTR, + MP_F_OBJ_SUBSCR, + MP_F_OBJ_IS_TRUE, + MP_F_UNARY_OP, + MP_F_BINARY_OP, + MP_F_BUILD_TUPLE, + MP_F_BUILD_LIST, + MP_F_BUILD_MAP, + MP_F_BUILD_SET, + MP_F_STORE_SET, + MP_F_LIST_APPEND, + MP_F_STORE_MAP, + MP_F_MAKE_FUNCTION_FROM_RAW_CODE, + MP_F_NATIVE_CALL_FUNCTION_N_KW, + MP_F_CALL_METHOD_N_KW, + MP_F_CALL_METHOD_N_KW_VAR, + MP_F_NATIVE_GETITER, + MP_F_NATIVE_ITERNEXT, + MP_F_NLR_PUSH, + MP_F_NLR_POP, + MP_F_NATIVE_RAISE, + MP_F_IMPORT_NAME, + MP_F_IMPORT_FROM, + MP_F_IMPORT_ALL, + MP_F_NEW_SLICE, + MP_F_UNPACK_SEQUENCE, + MP_F_UNPACK_EX, + MP_F_DELETE_NAME, + MP_F_DELETE_GLOBAL, + MP_F_MAKE_CLOSURE_FROM_RAW_CODE, + MP_F_ARG_CHECK_NUM_SIG, + MP_F_SETUP_CODE_STATE, + MP_F_SMALL_INT_FLOOR_DIVIDE, + MP_F_SMALL_INT_MODULO, + MP_F_NATIVE_YIELD_FROM, + MP_F_SETJMP, + MP_F_NUMBER_OF, +} mp_fun_kind_t; + +typedef struct _mp_fun_table_t { + mp_const_obj_t const_none; + mp_const_obj_t const_false; + mp_const_obj_t const_true; + mp_uint_t (*native_from_obj)(mp_obj_t obj, mp_uint_t type); + mp_obj_t (*native_to_obj)(mp_uint_t val, mp_uint_t type); + mp_obj_dict_t *(*swap_globals)(mp_obj_dict_t *new_globals); + mp_obj_t (*load_name)(qstr qst); + mp_obj_t (*load_global)(qstr qst); + mp_obj_t (*load_build_class)(void); + mp_obj_t (*load_attr)(mp_obj_t base, qstr attr); + void (*load_method)(mp_obj_t base, qstr attr, mp_obj_t *dest); + void (*load_super_method)(qstr attr, mp_obj_t *dest); + void (*store_name)(qstr qst, mp_obj_t obj); + void (*store_global)(qstr qst, mp_obj_t obj); + void (*store_attr)(mp_obj_t base, qstr attr, mp_obj_t val); + mp_obj_t (*obj_subscr)(mp_obj_t base, mp_obj_t index, mp_obj_t val); + bool (*obj_is_true)(mp_obj_t arg); + mp_obj_t (*unary_op)(mp_unary_op_t op, mp_obj_t arg); + mp_obj_t (*binary_op)(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs); + mp_obj_t (*new_tuple)(size_t n, const mp_obj_t *items); + mp_obj_t (*new_list)(size_t n, mp_obj_t *items); + mp_obj_t (*new_dict)(size_t n_args); + mp_obj_t (*new_set)(size_t n_args, mp_obj_t *items); + void (*set_store)(mp_obj_t self_in, mp_obj_t item); + mp_obj_t (*list_append)(mp_obj_t self_in, mp_obj_t arg); + mp_obj_t (*dict_store)(mp_obj_t self_in, mp_obj_t key, mp_obj_t value); + mp_obj_t (*make_function_from_raw_code)(const mp_raw_code_t *rc, mp_obj_t def_args, mp_obj_t def_kw_args); + mp_obj_t (*call_function_n_kw)(mp_obj_t fun_in, size_t n_args_kw, const mp_obj_t *args); + mp_obj_t (*call_method_n_kw)(size_t n_args, size_t n_kw, const mp_obj_t *args); + mp_obj_t (*call_method_n_kw_var)(bool have_self, size_t n_args_n_kw, const mp_obj_t *args); + mp_obj_t (*getiter)(mp_obj_t obj, mp_obj_iter_buf_t *iter); + mp_obj_t (*iternext)(mp_obj_iter_buf_t *iter); + unsigned int (*nlr_push)(nlr_buf_t *); + void (*nlr_pop)(void); + void (*raise)(mp_obj_t o); + mp_obj_t (*import_name)(qstr name, mp_obj_t fromlist, mp_obj_t level); + mp_obj_t (*import_from)(mp_obj_t module, qstr name); + void (*import_all)(mp_obj_t module); + mp_obj_t (*new_slice)(mp_obj_t start, mp_obj_t stop, mp_obj_t step); + void (*unpack_sequence)(mp_obj_t seq, size_t num, mp_obj_t *items); + void (*unpack_ex)(mp_obj_t seq, size_t num, mp_obj_t *items); + void (*delete_name)(qstr qst); + void (*delete_global)(qstr qst); + mp_obj_t (*make_closure_from_raw_code)(const mp_raw_code_t *rc, mp_uint_t n_closed_over, const mp_obj_t *args); + void (*arg_check_num_sig)(size_t n_args, size_t n_kw, uint32_t sig); + void (*setup_code_state)(mp_code_state_t *code_state, size_t n_args, size_t n_kw, const mp_obj_t *args); + mp_int_t (*small_int_floor_divide)(mp_int_t num, mp_int_t denom); + mp_int_t (*small_int_modulo)(mp_int_t dividend, mp_int_t divisor); + bool (*yield_from)(mp_obj_t gen, mp_obj_t send_value, mp_obj_t *ret_value); + void *setjmp; + // Additional entries for dynamic runtime, starts at index 50 + void *(*memset_)(void *s, int c, size_t n); + void *(*memmove_)(void *dest, const void *src, size_t n); + void *(*realloc_)(void *ptr, size_t n_bytes, bool allow_move); + int (*printf_)(const mp_print_t *print, const char *fmt, ...); + int (*vprintf_)(const mp_print_t *print, const char *fmt, va_list args); + #if defined(__GNUC__) + NORETURN // Only certain compilers support no-return attributes in function pointer declarations + #endif + void (*raise_msg)(const mp_obj_type_t *exc_type, const char *msg); + mp_obj_type_t *(*obj_get_type)(mp_const_obj_t o_in); + mp_obj_t (*obj_new_str)(const char* data, size_t len); + mp_obj_t (*obj_new_bytes)(const byte* data, size_t len); + mp_obj_t (*obj_new_bytearray_by_ref)(size_t n, void *items); + mp_obj_t (*obj_new_float_from_f)(float f); + mp_obj_t (*obj_new_float_from_d)(double d); + float (*obj_get_float_to_f)(mp_obj_t o); + double (*obj_get_float_to_d)(mp_obj_t o); + void (*get_buffer_raise)(mp_obj_t obj, mp_buffer_info_t *bufinfo, mp_uint_t flags); + const mp_stream_p_t *(*get_stream_raise)(mp_obj_t self_in, int flags); + const mp_print_t *plat_print; + const mp_obj_type_t *type_type; + const mp_obj_type_t *type_str; + const mp_obj_type_t *type_list; + const mp_obj_type_t *type_dict; + const mp_obj_type_t *type_fun_builtin_0; + const mp_obj_type_t *type_fun_builtin_1; + const mp_obj_type_t *type_fun_builtin_2; + const mp_obj_type_t *type_fun_builtin_3; + const mp_obj_type_t *type_fun_builtin_var; + const mp_obj_fun_builtin_var_t *stream_read_obj; + const mp_obj_fun_builtin_var_t *stream_readinto_obj; + const mp_obj_fun_builtin_var_t *stream_unbuffered_readline_obj; + const mp_obj_fun_builtin_var_t *stream_write_obj; +} mp_fun_table_t; + +extern const mp_fun_table_t mp_fun_table; + +#endif // MICROPY_INCLUDED_PY_NATIVEGLUE_H diff --git a/py/objenumerate.c b/py/objenumerate.c index 493e45c2a..243c9f83a 100644 --- a/py/objenumerate.c +++ b/py/objenumerate.c @@ -59,7 +59,7 @@ STATIC mp_obj_t enumerate_make_new(const mp_obj_type_t *type, size_t n_args, siz o->iter = mp_getiter(arg_vals.iterable.u_obj, NULL); o->cur = arg_vals.start.u_int; #else - (void)n_kw; + mp_arg_check_num(n_args, n_kw, 1, 2, false); mp_obj_enumerate_t *o = m_new_obj(mp_obj_enumerate_t); o->base.type = type; o->iter = mp_getiter(args[0], NULL); diff --git a/py/persistentcode.c b/py/persistentcode.c index 84385c171..7a8a94b5a 100644 --- a/py/persistentcode.c +++ b/py/persistentcode.c @@ -30,9 +30,10 @@ #include #include "py/reader.h" -#include "py/emitglue.h" +#include "py/nativeglue.h" #include "py/persistentcode.h" #include "py/bc0.h" +#include "py/objstr.h" #if MICROPY_PERSISTENT_CODE_LOAD || MICROPY_PERSISTENT_CODE_SAVE @@ -145,8 +146,16 @@ STATIC byte *extract_prelude(const byte **ip, bytecode_prelude_t *prelude) { #include "py/parsenum.h" +STATIC int read_byte(mp_reader_t *reader); +STATIC size_t read_uint(mp_reader_t *reader, byte **out); + #if MICROPY_EMIT_MACHINE_CODE +typedef struct _reloc_info_t { + mp_reader_t *reader; + mp_uint_t *const_table; +} reloc_info_t; + #if MICROPY_EMIT_THUMB STATIC void asm_thumb_rewrite_mov(uint8_t *pc, uint16_t val) { // high part @@ -179,6 +188,52 @@ STATIC void arch_link_qstr(uint8_t *pc, bool is_obj, qstr qst) { #endif } +void mp_native_relocate(void *ri_in, uint8_t *text, uintptr_t reloc_text) { + // Relocate native code + reloc_info_t *ri = ri_in; + uint8_t op; + uintptr_t *addr_to_adjust = NULL; + while ((op = read_byte(ri->reader)) != 0xff) { + if (op & 1) { + // Point to new location to make adjustments + size_t addr = read_uint(ri->reader, NULL); + if ((addr & 1) == 0) { + // Point to somewhere in text + addr_to_adjust = &((uintptr_t*)text)[addr >> 1]; + } else { + // Point to somewhere in rodata + addr_to_adjust = &((uintptr_t*)ri->const_table[1])[addr >> 1]; + } + } + op >>= 1; + uintptr_t dest; + size_t n = 1; + if (op <= 5) { + if (op & 1) { + // Read in number of adjustments to make + n = read_uint(ri->reader, NULL); + } + op >>= 1; + if (op == 0) { + // Destination is text + dest = reloc_text; + } else { + // Destination is rodata (op=1) or bss (op=1 if no rodata, else op=2) + dest = ri->const_table[op]; + } + } else if (op == 6) { + // Destination is mp_fun_table itself + dest = (uintptr_t)&mp_fun_table; + } else { + // Destination is an entry in mp_fun_table + dest = ((uintptr_t*)&mp_fun_table)[op - 7]; + } + while (n--) { + *addr_to_adjust++ += dest; + } + } +} + #endif STATIC int read_byte(mp_reader_t *reader) { @@ -340,6 +395,9 @@ STATIC mp_raw_code_t *load_raw_code(mp_reader_t *reader, qstr_window_t *qw) { // Generic 16-bit link dest[0] = qst & 0xff; dest[1] = (qst >> 8) & 0xff; + } else if ((off & 3) == 3) { + // Generic, aligned qstr-object link + *(mp_obj_t*)dest = MP_OBJ_NEW_QSTR(qst); } else { // Architecture-specific link arch_link_qstr(dest, (off & 3) == 2, qst); @@ -380,9 +438,18 @@ STATIC mp_raw_code_t *load_raw_code(mp_reader_t *reader, qstr_window_t *qw) { // Allocate constant table size_t n_alloc = prelude.n_pos_args + prelude.n_kwonly_args + n_obj + n_raw_code; + #if MICROPY_EMIT_MACHINE_CODE if (kind != MP_CODE_BYTECODE) { ++n_alloc; // additional entry for mp_fun_table + if (prelude.scope_flags & MP_SCOPE_FLAG_VIPERRODATA) { + ++n_alloc; // additional entry for rodata + } + if (prelude.scope_flags & MP_SCOPE_FLAG_VIPERBSS) { + ++n_alloc; // additional entry for BSS + } } + #endif + const_table = m_new(mp_uint_t, n_alloc); mp_uint_t *ct = const_table; @@ -395,7 +462,22 @@ STATIC mp_raw_code_t *load_raw_code(mp_reader_t *reader, qstr_window_t *qw) { #if MICROPY_EMIT_MACHINE_CODE if (kind != MP_CODE_BYTECODE) { // Populate mp_fun_table entry - *ct++ = (mp_uint_t)(uintptr_t)mp_fun_table; + *ct++ = (mp_uint_t)(uintptr_t)&mp_fun_table; + + // Allocate and load rodata if needed + if (prelude.scope_flags & MP_SCOPE_FLAG_VIPERRODATA) { + size_t size = read_uint(reader, NULL); + uint8_t *rodata = m_new(uint8_t, size); + read_bytes(reader, rodata, size); + *ct++ = (uintptr_t)rodata; + } + + // Allocate BSS if needed + if (prelude.scope_flags & MP_SCOPE_FLAG_VIPERBSS) { + size_t size = read_uint(reader, NULL); + uint8_t *bss = m_new0(uint8_t, size); + *ct++ = (uintptr_t)bss; + } } #endif @@ -411,6 +493,7 @@ STATIC mp_raw_code_t *load_raw_code(mp_reader_t *reader, qstr_window_t *qw) { // Create raw_code and return it mp_raw_code_t *rc = mp_emit_glue_new_raw_code(); if (kind == MP_CODE_BYTECODE) { + // Assign bytecode to raw code object mp_emit_glue_assign_bytecode(rc, fun_data, #if MICROPY_PERSISTENT_CODE_SAVE || MICROPY_DEBUG_PRINTERS fun_data_len, @@ -423,10 +506,18 @@ STATIC mp_raw_code_t *load_raw_code(mp_reader_t *reader, qstr_window_t *qw) { #if MICROPY_EMIT_MACHINE_CODE } else { + // Relocate and commit code to executable address space + reloc_info_t ri = {reader, const_table}; #if defined(MP_PLAT_COMMIT_EXEC) - fun_data = MP_PLAT_COMMIT_EXEC(fun_data, fun_data_len); + void *opt_ri = (prelude.scope_flags & MP_SCOPE_FLAG_VIPERRELOC) ? &ri : NULL; + fun_data = MP_PLAT_COMMIT_EXEC(fun_data, fun_data_len, opt_ri); + #else + if (prelude.scope_flags & MP_SCOPE_FLAG_VIPERRELOC) { + mp_native_relocate(&ri, fun_data, (uintptr_t)fun_data); + } #endif + // Assign native code to raw code object mp_emit_glue_assign_native(rc, kind, fun_data, fun_data_len, const_table, #if MICROPY_PERSISTENT_CODE_SAVE @@ -450,9 +541,11 @@ mp_raw_code_t *mp_raw_code_load(mp_reader_t *reader) { || read_uint(reader, NULL) > QSTR_WINDOW_SIZE) { mp_raise_ValueError("incompatible .mpy file"); } - if (MPY_FEATURE_DECODE_ARCH(header[2]) != MP_NATIVE_ARCH_NONE - && MPY_FEATURE_DECODE_ARCH(header[2]) != MPY_FEATURE_ARCH) { - mp_raise_ValueError("incompatible .mpy arch"); + if (MPY_FEATURE_DECODE_ARCH(header[2]) != MP_NATIVE_ARCH_NONE) { + byte arch = MPY_FEATURE_DECODE_ARCH(header[2]); + if (!MPY_FEATURE_ARCH_TEST(arch)) { + mp_raise_ValueError("incompatible .mpy arch"); + } } qstr_window_t qw; qw.idx = 0; @@ -624,7 +717,7 @@ STATIC void save_raw_code(mp_print_t *print, mp_raw_code_t *rc, qstr_window_t *q save_prelude_qstrs(print, qstr_window, ip_info); } else { // Save basic scope info for viper and asm - mp_print_uint(print, rc->scope_flags); + mp_print_uint(print, rc->scope_flags & MP_SCOPE_FLAG_ALL_SIG); prelude.n_pos_args = 0; prelude.n_kwonly_args = 0; if (rc->kind == MP_CODE_NATIVE_ASM) { diff --git a/py/persistentcode.h b/py/persistentcode.h index 07e018f8a..8769ef584 100644 --- a/py/persistentcode.h +++ b/py/persistentcode.h @@ -55,19 +55,34 @@ // Define the host architecture #if MICROPY_EMIT_X86 -#define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_X86) + #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_X86) #elif MICROPY_EMIT_X64 -#define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_X64) + #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_X64) #elif MICROPY_EMIT_THUMB -#define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_ARMV7M) + #if defined(__thumb2__) + #if defined(__ARM_FP) && (__ARM_FP & 8) == 8 + #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_ARMV7EMDP) + #elif defined(__ARM_FP) && (__ARM_FP & 4) == 4 + #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_ARMV7EMSP) + #else + #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_ARMV7EM) + #endif + #else + #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_ARMV7M) + #endif + #define MPY_FEATURE_ARCH_TEST(x) (MP_NATIVE_ARCH_ARMV6M <= (x) && (x) <= MPY_FEATURE_ARCH) #elif MICROPY_EMIT_ARM -#define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_ARMV6) + #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_ARMV6) #elif MICROPY_EMIT_XTENSA -#define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_XTENSA) + #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_XTENSA) #elif MICROPY_EMIT_XTENSAWIN -#define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_XTENSAWIN) + #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_XTENSAWIN) #else -#define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_NONE) + #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_NONE) +#endif + +#ifndef MPY_FEATURE_ARCH_TEST +#define MPY_FEATURE_ARCH_TEST(x) ((x) == MPY_FEATURE_ARCH) #endif // 16-bit little-endian integer with the second and third bytes of supported .mpy files @@ -95,4 +110,6 @@ mp_raw_code_t *mp_raw_code_load_file(const char *filename); void mp_raw_code_save(mp_raw_code_t *rc, mp_print_t *print); void mp_raw_code_save_file(mp_raw_code_t *rc, const char *filename); +void mp_native_relocate(void *reloc, uint8_t *text, uintptr_t reloc_text); + #endif // MICROPY_INCLUDED_PY_PERSISTENTCODE_H diff --git a/py/profile.c b/py/profile.c index f16d4d701..72726cdf5 100644 --- a/py/profile.c +++ b/py/profile.c @@ -875,10 +875,16 @@ STATIC const byte *mp_prof_opcode_decode(const byte *ip, const mp_uint_t *const_ instruction->qstr_opname = MP_QSTR_RETURN_VALUE; break; - case MP_BC_RAISE_VARARGS: - unum = *ip++; - instruction->qstr_opname = MP_QSTR_RAISE_VARARGS; - instruction->arg = unum; + case MP_BC_RAISE_LAST: + instruction->qstr_opname = MP_QSTR_RAISE_LAST; + break; + + case MP_BC_RAISE_OBJ: + instruction->qstr_opname = MP_QSTR_RAISE_OBJ; + break; + + case MP_BC_RAISE_FROM: + instruction->qstr_opname = MP_QSTR_RAISE_FROM; break; case MP_BC_YIELD_VALUE: diff --git a/py/runtime0.h b/py/runtime0.h index f588110c0..e6eeff97d 100644 --- a/py/runtime0.h +++ b/py/runtime0.h @@ -28,13 +28,17 @@ // The first four must fit in 8 bits, see emitbc.c // The remaining must fit in 16 bits, see scope.h +#define MP_SCOPE_FLAG_ALL_SIG (0x0f) #define MP_SCOPE_FLAG_GENERATOR (0x01) #define MP_SCOPE_FLAG_VARKEYWORDS (0x02) #define MP_SCOPE_FLAG_VARARGS (0x04) #define MP_SCOPE_FLAG_DEFKWARGS (0x08) #define MP_SCOPE_FLAG_REFGLOBALS (0x10) // used only if native emitter enabled #define MP_SCOPE_FLAG_HASCONSTS (0x20) // used only if native emitter enabled -#define MP_SCOPE_FLAG_VIPERRET_POS (6) // 3 bits used for viper return type +#define MP_SCOPE_FLAG_VIPERRET_POS (6) // 3 bits used for viper return type, to pass from compiler to native emitter +#define MP_SCOPE_FLAG_VIPERRELOC (0x10) // used only when loading viper from .mpy +#define MP_SCOPE_FLAG_VIPERRODATA (0x20) // used only when loading viper from .mpy +#define MP_SCOPE_FLAG_VIPERBSS (0x40) // used only when loading viper from .mpy // types for native (viper) function signature #define MP_NATIVE_TYPE_OBJ (0x00) @@ -152,60 +156,4 @@ typedef enum { MP_BINARY_OP_IS_NOT, } mp_binary_op_t; -typedef enum { - MP_F_CONST_NONE_OBJ = 0, - MP_F_CONST_FALSE_OBJ, - MP_F_CONST_TRUE_OBJ, - MP_F_CONVERT_OBJ_TO_NATIVE, - MP_F_CONVERT_NATIVE_TO_OBJ, - MP_F_NATIVE_SWAP_GLOBALS, - MP_F_LOAD_NAME, - MP_F_LOAD_GLOBAL, - MP_F_LOAD_BUILD_CLASS, - MP_F_LOAD_ATTR, - MP_F_LOAD_METHOD, - MP_F_LOAD_SUPER_METHOD, - MP_F_STORE_NAME, - MP_F_STORE_GLOBAL, - MP_F_STORE_ATTR, - MP_F_OBJ_SUBSCR, - MP_F_OBJ_IS_TRUE, - MP_F_UNARY_OP, - MP_F_BINARY_OP, - MP_F_BUILD_TUPLE, - MP_F_BUILD_LIST, - MP_F_BUILD_MAP, - MP_F_BUILD_SET, - MP_F_STORE_SET, - MP_F_LIST_APPEND, - MP_F_STORE_MAP, - MP_F_MAKE_FUNCTION_FROM_RAW_CODE, - MP_F_NATIVE_CALL_FUNCTION_N_KW, - MP_F_CALL_METHOD_N_KW, - MP_F_CALL_METHOD_N_KW_VAR, - MP_F_NATIVE_GETITER, - MP_F_NATIVE_ITERNEXT, - MP_F_NLR_PUSH, - MP_F_NLR_POP, - MP_F_NATIVE_RAISE, - MP_F_IMPORT_NAME, - MP_F_IMPORT_FROM, - MP_F_IMPORT_ALL, - MP_F_NEW_SLICE, - MP_F_UNPACK_SEQUENCE, - MP_F_UNPACK_EX, - MP_F_DELETE_NAME, - MP_F_DELETE_GLOBAL, - MP_F_MAKE_CLOSURE_FROM_RAW_CODE, - MP_F_ARG_CHECK_NUM_SIG, - MP_F_SETUP_CODE_STATE, - MP_F_SMALL_INT_FLOOR_DIVIDE, - MP_F_SMALL_INT_MODULO, - MP_F_NATIVE_YIELD_FROM, - MP_F_SETJMP, - MP_F_NUMBER_OF, -} mp_fun_kind_t; - -extern const void *const mp_fun_table[MP_F_NUMBER_OF]; - #endif // MICROPY_INCLUDED_PY_RUNTIME0_H diff --git a/py/vm.c b/py/vm.c index 7c702f386..11dbffaff 100644 --- a/py/vm.c +++ b/py/vm.c @@ -269,7 +269,7 @@ outer_dispatch_loop: MICROPY_VM_HOOK_INIT // If we have exception to inject, now that we finish setting up - // execution context, raise it. This works as if RAISE_VARARGS + // execution context, raise it. This works as if MP_BC_RAISE_OBJ // bytecode was executed. // Injecting exc into yield from generator is a special case, // handled by MP_BC_YIELD_FROM itself diff --git a/tests/basics/memoryview_itemsize.py b/tests/basics/memoryview_itemsize.py index cd7a87c23..64a8822b8 100644 --- a/tests/basics/memoryview_itemsize.py +++ b/tests/basics/memoryview_itemsize.py @@ -12,9 +12,12 @@ except ImportError: print("SKIP") raise SystemExit -for code in ['b', 'h', 'i', 'l', 'q', 'f', 'd']: +for code in ['b', 'h', 'i', 'q', 'f', 'd']: print(memoryview(array(code)).itemsize) +# 'l' varies depending on word size of the machine +print(memoryview(array('l')).itemsize in (4, 8)) + # shouldn't be able to store to the itemsize attribute try: memoryview(b'a').itemsize = 1 diff --git a/tests/basics/parser.py.exp b/tests/basics/parser.py.exp new file mode 100644 index 000000000..4d9886a09 --- /dev/null +++ b/tests/basics/parser.py.exp @@ -0,0 +1,3 @@ +SyntaxError +SyntaxError +SyntaxError diff --git a/tests/extmod/vfs_lfs_error.py b/tests/extmod/vfs_lfs_error.py index b97fe6ec1..793fae59e 100644 --- a/tests/extmod/vfs_lfs_error.py +++ b/tests/extmod/vfs_lfs_error.py @@ -106,8 +106,9 @@ def test(bdev, vfs_class): # error during seek with vfs.open('testfile', 'r') as f: + f.seek(1 << 30) # SEEK_SET try: - f.seek(1 << 31) + f.seek(1 << 30, 1) # SEEK_CUR except OSError: print('seek OSError') diff --git a/tests/import/import_override.py.exp b/tests/import/import_override.py.exp new file mode 100644 index 000000000..365248da6 --- /dev/null +++ b/tests/import/import_override.py.exp @@ -0,0 +1,2 @@ +import import1b None 0 +456 diff --git a/tests/import/mpy_native.py b/tests/import/mpy_native.py index c33b3350c..4ee537f4a 100644 --- a/tests/import/mpy_native.py +++ b/tests/import/mpy_native.py @@ -73,6 +73,27 @@ user_files = { b'\x00\x00\x00\x00\x00\x00\x00\x00' # dummy machine code b'\x00\x00\x00' # scope_flags, n_pos_args, type_sig ), + + # test loading viper with additional scope flags and relocation + '/mod2.mpy': ( + b'M\x05\x0b\x1f\x20' # header + + b'\x20' # n bytes, bytecode + b'\x00\x08\x02m\x02m' # prelude + b'\x51' # LOAD_CONST_NONE + b'\x63' # RETURN_VALUE + + b'\x00\x01' # n_obj, n_raw_code + + b'\x12' # n bytes(=4), viper code + b'\x00\x00\x00\x00' # dummy machine code + b'\x00' # n_qstr + b'\x70' # scope_flags: VIPERBSS | VIPERRODATA | VIPERRELOC + b'\x00\x00' # n_obj, n_raw_code + b'\x06rodata' # rodata, 6 bytes + b'\x04' # bss, 4 bytes + b'\x03\x01\x00' # dummy relocation of rodata + ), } # create and mount a user filesystem diff --git a/tests/import/mpy_native.py.exp b/tests/import/mpy_native.py.exp index b73812094..320cac09d 100644 --- a/tests/import/mpy_native.py.exp +++ b/tests/import/mpy_native.py.exp @@ -1,2 +1,3 @@ mod0 ValueError incompatible .mpy arch mod1 OK +mod2 OK diff --git a/tests/pyb/accel.py b/tests/pyb/accel.py index e5a1a2ed7..9aa60c185 100644 --- a/tests/pyb/accel.py +++ b/tests/pyb/accel.py @@ -1,5 +1,9 @@ import pyb +if not hasattr(pyb, 'Accel'): + print('SKIP') + raise SystemExit + accel = pyb.Accel() print(accel) accel.x() diff --git a/tests/pyb/adc.py b/tests/pyb/adc.py index 0bd9b9d53..ef4206538 100644 --- a/tests/pyb/adc.py +++ b/tests/pyb/adc.py @@ -1,7 +1,7 @@ from pyb import ADC, Timer adct = ADC(16) # Temperature 930 -> 20C -print(adct) +print(str(adct)[:19]) adcv = ADC(17) # Voltage 1500 -> 3.3V print(adcv) diff --git a/tests/pyb/adc.py.exp b/tests/pyb/adc.py.exp index 1aae16fb0..381329cdd 100644 --- a/tests/pyb/adc.py.exp +++ b/tests/pyb/adc.py.exp @@ -1,4 +1,4 @@ - + 50 25 diff --git a/tests/pyb/board_pybv1x.py b/tests/pyb/board_pybv1x.py new file mode 100644 index 000000000..f4aeeb99e --- /dev/null +++ b/tests/pyb/board_pybv1x.py @@ -0,0 +1,39 @@ +# Test board-specific items on PYBv1.x + +import os, pyb + +if not 'PYBv1.' in os.uname().machine: + print('SKIP') + raise SystemExit + +# test creating UART by id/name +for bus in (1, 2, 3, 4, 5, 6, 7, "XA", "XB", "YA", "YB", "Z"): + try: + pyb.UART(bus, 9600) + print("UART", bus) + except ValueError: + print("ValueError", bus) + +# test creating SPI by id/name +for bus in (1, 2, 3, "X", "Y", "Z"): + try: + pyb.SPI(bus) + print("SPI", bus) + except ValueError: + print("ValueError", bus) + +# test creating I2C by id/name +for bus in (2, 3, "X", "Y", "Z"): + try: + pyb.I2C(bus) + print("I2C", bus) + except ValueError: + print("ValueError", bus) + +# test creating CAN by id/name +for bus in (1, 2, 3, "YA", "YB", "YC"): + try: + pyb.CAN(bus, pyb.CAN.LOOPBACK) + print("CAN", bus) + except ValueError: + print("ValueError", bus) diff --git a/tests/pyb/board_pybv1x.py.exp b/tests/pyb/board_pybv1x.py.exp new file mode 100644 index 000000000..6d4a6f63c --- /dev/null +++ b/tests/pyb/board_pybv1x.py.exp @@ -0,0 +1,29 @@ +UART 1 +UART 2 +UART 3 +UART 4 +ValueError 5 +UART 6 +ValueError 7 +UART XA +UART XB +UART YA +UART YB +ValueError Z +SPI 1 +SPI 2 +ValueError 3 +SPI X +SPI Y +ValueError Z +I2C 2 +ValueError 3 +I2C X +I2C Y +ValueError Z +CAN 1 +CAN 2 +ValueError 3 +CAN YA +CAN YB +ValueError YC diff --git a/tests/pyb/can.py b/tests/pyb/can.py index 8a08ea9a6..71de724c7 100644 --- a/tests/pyb/can.py +++ b/tests/pyb/can.py @@ -8,8 +8,8 @@ from array import array import micropython import pyb -# test we can correctly create by id or name -for bus in (-1, 0, 1, 2, 3, "YA", "YB", "YC"): +# test we can correctly create by id (2 handled in can2.py test) +for bus in (-1, 0, 1, 3): try: CAN(bus, CAN.LOOPBACK) print("CAN", bus) @@ -273,18 +273,11 @@ while can.any(0): # Testing rtr messages bus1 = CAN(1, CAN.LOOPBACK) -bus2 = CAN(2, CAN.LOOPBACK, extframe = True) while bus1.any(0): bus1.recv(0) -while bus2.any(0): - bus2.recv(0) bus1.setfilter(0, CAN.LIST16, 0, (1, 2, 3, 4)) bus1.setfilter(1, CAN.LIST16, 0, (5, 6, 7, 8), rtr=(True, True, True, True)) bus1.setfilter(2, CAN.MASK16, 0, (64, 64, 32, 32), rtr=(False, True)) -bus2.setfilter(0, CAN.LIST32, 0, (1, 2), rtr=(True, True)) -bus2.setfilter(1, CAN.LIST32, 0, (3, 4), rtr=(True, False)) -bus2.setfilter(2, CAN.MASK32, 0, (16, 16), rtr=(False,)) -bus2.setfilter(2, CAN.MASK32, 0, (32, 32), rtr=(True,)) bus1.send('',1,rtr=True) print(bus1.any(0)) @@ -299,11 +292,9 @@ print(bus1.any(0)) bus1.send('',32,rtr=True) print(bus1.recv(0)) -bus2.send('',1,rtr=True) -print(bus2.recv(0)) -bus2.send('',2,rtr=True) -print(bus2.recv(0)) -bus2.send('',3,rtr=True) -print(bus2.recv(0)) -bus2.send('',4,rtr=True) -print(bus2.any(0)) +# test HAL error, timeout +can = pyb.CAN(1, pyb.CAN.NORMAL) +try: + can.send('1', 1, timeout=50) +except OSError as e: + print(repr(e)) diff --git a/tests/pyb/can.py.exp b/tests/pyb/can.py.exp index 687935e7f..a27907cc5 100644 --- a/tests/pyb/can.py.exp +++ b/tests/pyb/can.py.exp @@ -1,11 +1,7 @@ ValueError -1 ValueError 0 CAN 1 -CAN 2 ValueError 3 -CAN YA -CAN YB -ValueError YC CAN(1) True CAN(1, CAN.LOOPBACK, extframe=False, auto_restart=False) @@ -70,7 +66,4 @@ False (7, True, 6, b'') False (32, True, 9, b'') -(1, True, 0, b'') -(2, True, 1, b'') -(3, True, 2, b'') -False +OSError(110,) diff --git a/tests/pyb/can2.py b/tests/pyb/can2.py new file mode 100644 index 000000000..440ae4925 --- /dev/null +++ b/tests/pyb/can2.py @@ -0,0 +1,24 @@ +try: + from pyb import CAN + CAN(2) +except (ImportError, ValueError): + print('SKIP') + raise SystemExit + +# Testing rtr messages +bus2 = CAN(2, CAN.LOOPBACK, extframe=True) +while bus2.any(0): + bus2.recv(0) +bus2.setfilter(0, CAN.LIST32, 0, (1, 2), rtr=(True, True)) +bus2.setfilter(1, CAN.LIST32, 0, (3, 4), rtr=(True, False)) +bus2.setfilter(2, CAN.MASK32, 0, (16, 16), rtr=(False,)) +bus2.setfilter(2, CAN.MASK32, 0, (32, 32), rtr=(True,)) + +bus2.send('',1,rtr=True) +print(bus2.recv(0)) +bus2.send('',2,rtr=True) +print(bus2.recv(0)) +bus2.send('',3,rtr=True) +print(bus2.recv(0)) +bus2.send('',4,rtr=True) +print(bus2.any(0)) diff --git a/tests/pyb/can2.py.exp b/tests/pyb/can2.py.exp new file mode 100644 index 000000000..371ad2bdc --- /dev/null +++ b/tests/pyb/can2.py.exp @@ -0,0 +1,4 @@ +(1, True, 0, b'') +(2, True, 1, b'') +(3, True, 2, b'') +False diff --git a/tests/pyb/extint.py b/tests/pyb/extint.py index ae98ccd5a..8b6bc5651 100644 --- a/tests/pyb/extint.py +++ b/tests/pyb/extint.py @@ -1,7 +1,7 @@ import pyb # test basic functionality -ext = pyb.ExtInt('Y1', pyb.ExtInt.IRQ_RISING, pyb.Pin.PULL_DOWN, lambda l:print('line:', l)) +ext = pyb.ExtInt('X5', pyb.ExtInt.IRQ_RISING, pyb.Pin.PULL_DOWN, lambda l:print('line:', l)) ext.disable() ext.enable() print(ext.line()) diff --git a/tests/pyb/extint.py.exp b/tests/pyb/extint.py.exp index 1f9da9844..fbd820ed4 100644 --- a/tests/pyb/extint.py.exp +++ b/tests/pyb/extint.py.exp @@ -1,3 +1,3 @@ -6 -line: 6 -line: 6 +4 +line: 4 +line: 4 diff --git a/tests/pyb/halerror.py b/tests/pyb/halerror.py deleted file mode 100644 index 1a6bce1a7..000000000 --- a/tests/pyb/halerror.py +++ /dev/null @@ -1,15 +0,0 @@ -# test hal errors - -import pyb - -i2c = pyb.I2C(2, pyb.I2C.MASTER) -try: - i2c.recv(1, 1) -except OSError as e: - print(repr(e)) - -can = pyb.CAN(1, pyb.CAN.NORMAL) -try: - can.send('1', 1, timeout=50) -except OSError as e: - print(repr(e)) diff --git a/tests/pyb/halerror.py.exp b/tests/pyb/halerror.py.exp deleted file mode 100644 index 0f3f26253..000000000 --- a/tests/pyb/halerror.py.exp +++ /dev/null @@ -1,2 +0,0 @@ -OSError(5,) -OSError(110,) diff --git a/tests/pyb/i2c.py b/tests/pyb/i2c.py index 6875e5a5a..bc9dba878 100644 --- a/tests/pyb/i2c.py +++ b/tests/pyb/i2c.py @@ -1,8 +1,8 @@ import pyb from pyb import I2C -# test we can correctly create by id or name -for bus in (-1, 0, 1, 2, 3, "X", "Y", "Z"): +# test we can correctly create by id +for bus in (-1, 0, 1): try: I2C(bus) print("I2C", bus) @@ -14,19 +14,3 @@ i2c = I2C(1) i2c.init(I2C.MASTER, baudrate=400000) print(i2c.scan()) i2c.deinit() - -# use accelerometer to test i2c bus - -accel_addr = 76 - -pyb.Accel() # this will init the MMA for us -i2c.init(I2C.MASTER, baudrate=400000) - -print(i2c.scan()) -print(i2c.is_ready(accel_addr)) - -print(i2c.mem_read(1, accel_addr, 7, timeout=500)) -i2c.mem_write(0, accel_addr, 0, timeout=500) - -i2c.send(7, addr=accel_addr) -i2c.recv(1, addr=accel_addr) diff --git a/tests/pyb/i2c.py.exp b/tests/pyb/i2c.py.exp index 709fb412d..bd896ea74 100644 --- a/tests/pyb/i2c.py.exp +++ b/tests/pyb/i2c.py.exp @@ -1,12 +1,4 @@ ValueError -1 ValueError 0 I2C 1 -I2C 2 -ValueError 3 -I2C X -I2C Y -ValueError Z [] -[76] -True -b'\x01' diff --git a/tests/pyb/i2c_accel.py b/tests/pyb/i2c_accel.py new file mode 100644 index 000000000..e42cba178 --- /dev/null +++ b/tests/pyb/i2c_accel.py @@ -0,0 +1,23 @@ +# use accelerometer to test i2c bus + +import pyb +from pyb import I2C + +if not hasattr(pyb, 'Accel'): + print('SKIP') + raise SystemExit + +accel_addr = 76 + +pyb.Accel() # this will init the MMA for us + +i2c = I2C(1, I2C.MASTER, baudrate=400000) + +print(i2c.scan()) +print(i2c.is_ready(accel_addr)) + +print(i2c.mem_read(1, accel_addr, 7, timeout=500)) +i2c.mem_write(0, accel_addr, 0, timeout=500) + +i2c.send(7, addr=accel_addr) +i2c.recv(1, addr=accel_addr) diff --git a/tests/pyb/i2c_accel.py.exp b/tests/pyb/i2c_accel.py.exp new file mode 100644 index 000000000..206a2eb4c --- /dev/null +++ b/tests/pyb/i2c_accel.py.exp @@ -0,0 +1,3 @@ +[76] +True +b'\x01' diff --git a/tests/pyb/i2c_error.py b/tests/pyb/i2c_error.py index 3201d6367..e0f721179 100644 --- a/tests/pyb/i2c_error.py +++ b/tests/pyb/i2c_error.py @@ -3,6 +3,10 @@ import pyb from pyb import I2C +if not hasattr(pyb, 'Accel'): + print('SKIP') + raise SystemExit + # init accelerometer pyb.Accel() diff --git a/tests/pyb/led.py b/tests/pyb/led.py index d2a17ebc9..38e9993cb 100644 --- a/tests/pyb/led.py +++ b/tests/pyb/led.py @@ -1,17 +1,19 @@ -import pyb -from pyb import LED +import os, pyb -l1 = pyb.LED(1) -l2 = pyb.LED(2) -l3 = pyb.LED(3) -l4 = pyb.LED(4) - -leds = [LED(i) for i in range(1, 5)] -pwm_leds = leds[2:] +machine = os.uname().machine +if 'PYBv1.' in machine or 'PYBLITEv1.' in machine: + leds = [pyb.LED(i) for i in range(1, 5)] + pwm_leds = leds[2:] +elif 'PYBD' in machine: + leds = [pyb.LED(i) for i in range(1, 4)] + pwm_leds = [] +else: + print('SKIP') + raise SystemExit # test printing -for l in leds: - print(l) +for i in range(3): + print(leds[i]) # test on and off for l in leds: diff --git a/tests/pyb/led.py.exp b/tests/pyb/led.py.exp index 4e8d856cd..b4170c41e 100644 --- a/tests/pyb/led.py.exp +++ b/tests/pyb/led.py.exp @@ -1,4 +1,3 @@ LED(1) LED(2) LED(3) -LED(4) diff --git a/tests/pyb/pin.py b/tests/pyb/pin.py index 9b3788343..ea42cc206 100644 --- a/tests/pyb/pin.py +++ b/tests/pyb/pin.py @@ -1,14 +1,14 @@ from pyb import Pin -p = Pin('Y1', Pin.IN) +p = Pin('X8', Pin.IN) print(p) print(p.name()) print(p.pin()) print(p.port()) -p = Pin('Y1', Pin.IN, Pin.PULL_UP) -p = Pin('Y1', Pin.IN, pull=Pin.PULL_UP) -p = Pin('Y1', mode=Pin.IN, pull=Pin.PULL_UP) +p = Pin('X8', Pin.IN, Pin.PULL_UP) +p = Pin('X8', Pin.IN, pull=Pin.PULL_UP) +p = Pin('X8', mode=Pin.IN, pull=Pin.PULL_UP) print(p) print(p.value()) diff --git a/tests/pyb/pin.py.exp b/tests/pyb/pin.py.exp index f2f7038fd..a2a7e42d9 100644 --- a/tests/pyb/pin.py.exp +++ b/tests/pyb/pin.py.exp @@ -1,10 +1,10 @@ -Pin(Pin.cpu.C6, mode=Pin.IN) -C6 -6 -2 -Pin(Pin.cpu.C6, mode=Pin.IN, pull=Pin.PULL_UP) +Pin(Pin.cpu.A7, mode=Pin.IN) +A7 +7 +0 +Pin(Pin.cpu.A7, mode=Pin.IN, pull=Pin.PULL_UP) 1 -Pin(Pin.cpu.C6, mode=Pin.IN, pull=Pin.PULL_DOWN) +Pin(Pin.cpu.A7, mode=Pin.IN, pull=Pin.PULL_DOWN) 0 0 1 diff --git a/tests/pyb/pyb_f405.py b/tests/pyb/pyb_f405.py index 2f161ae09..f49dd1bc7 100644 --- a/tests/pyb/pyb_f405.py +++ b/tests/pyb/pyb_f405.py @@ -8,3 +8,11 @@ if not 'STM32F405' in os.uname().machine: print(pyb.freq()) print(type(pyb.rng())) + +# test HAL error specific to F405 +i2c = pyb.I2C(2, pyb.I2C.MASTER) +try: + i2c.recv(1, 1) +except OSError as e: + print(repr(e)) + diff --git a/tests/pyb/pyb_f405.py.exp b/tests/pyb/pyb_f405.py.exp index a90aa0268..a52f40920 100644 --- a/tests/pyb/pyb_f405.py.exp +++ b/tests/pyb/pyb_f405.py.exp @@ -1,2 +1,3 @@ (168000000, 168000000, 42000000, 84000000) +OSError(5,) diff --git a/tests/pyb/spi.py b/tests/pyb/spi.py index 88cc975bb..577737420 100644 --- a/tests/pyb/spi.py +++ b/tests/pyb/spi.py @@ -1,7 +1,7 @@ from pyb import SPI -# test we can correctly create by id or name -for bus in (-1, 0, 1, 2, 3, "X", "Y", "Z"): +# test we can correctly create by id +for bus in (-1, 0, 1, 2): try: SPI(bus) print("SPI", bus) @@ -14,7 +14,7 @@ print(spi) spi = SPI(1, SPI.MASTER) spi = SPI(1, SPI.MASTER, baudrate=500000) spi = SPI(1, SPI.MASTER, 500000, polarity=1, phase=0, bits=8, firstbit=SPI.MSB, ti=False, crc=None) -print(spi) +print(str(spi)[:28], str(spi)[49:]) # don't print baudrate/prescaler spi.init(SPI.SLAVE, phase=1) print(spi) diff --git a/tests/pyb/spi.py.exp b/tests/pyb/spi.py.exp index a0d835700..473c173a5 100644 --- a/tests/pyb/spi.py.exp +++ b/tests/pyb/spi.py.exp @@ -2,12 +2,8 @@ ValueError -1 ValueError 0 SPI 1 SPI 2 -ValueError 3 -SPI X -SPI Y -ValueError Z SPI(1) -SPI(1, SPI.MASTER, baudrate=328125, prescaler=256, polarity=1, phase=0, bits=8) +SPI(1, SPI.MASTER, baudrate= , polarity=1, phase=0, bits=8) SPI(1, SPI.SLAVE, polarity=1, phase=1, bits=8) OSError b'\xff' diff --git a/tests/pyb/timer.py b/tests/pyb/timer.py index e83550abd..0e24ff4ad 100644 --- a/tests/pyb/timer.py +++ b/tests/pyb/timer.py @@ -16,4 +16,4 @@ print(tim.period()) tim = Timer(2, freq=100) print(tim.freq()) tim.freq(0.001) -print(tim.freq()) +print('{:.3f}'.format(tim.freq())) diff --git a/tests/pyb/uart.py b/tests/pyb/uart.py index 836b073a6..9dcb1f75c 100644 --- a/tests/pyb/uart.py +++ b/tests/pyb/uart.py @@ -1,7 +1,7 @@ from pyb import UART -# test we can correctly create by id or name -for bus in (-1, 0, 1, 2, 3, 4, 5, 6, 7, "XA", "XB", "YA", "YB", "Z"): +# test we can correctly create by id +for bus in (-1, 0, 1, 2, 5, 6): try: UART(bus, 9600) print("UART", bus) diff --git a/tests/pyb/uart.py.exp b/tests/pyb/uart.py.exp index d302468ed..70e4ab850 100644 --- a/tests/pyb/uart.py.exp +++ b/tests/pyb/uart.py.exp @@ -2,16 +2,8 @@ ValueError -1 ValueError 0 UART 1 UART 2 -UART 3 -UART 4 ValueError 5 UART 6 -ValueError 7 -UART XA -UART XB -UART YA -UART YB -ValueError Z UART(1, baudrate=9600, bits=8, parity=None, stop=1, flow=0, timeout=0, timeout_char=3, rxbuf=64) UART(1, baudrate=2400, bits=8, parity=None, stop=1, flow=0, timeout=0, timeout_char=7, rxbuf=64) 0 diff --git a/tests/run-natmodtests.py b/tests/run-natmodtests.py new file mode 100755 index 000000000..3f49a1d68 --- /dev/null +++ b/tests/run-natmodtests.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python3 + +# This file is part of the MicroPython project, http://micropython.org/ +# The MIT License (MIT) +# Copyright (c) 2019 Damien P. George + +import os +import subprocess +import sys +import argparse + +sys.path.append('../tools') +import pyboard + +# Paths for host executables +CPYTHON3 = os.getenv('MICROPY_CPYTHON3', 'python3') +MICROPYTHON = os.getenv('MICROPY_MICROPYTHON', '../ports/unix/micropython_coverage') + +NATMOD_EXAMPLE_DIR = '../examples/natmod/' + +# Supported tests and their corresponding mpy module +TEST_MAPPINGS = { + 'btree': 'btree/btree_$(ARCH).mpy', + 'framebuf': 'framebuf/framebuf_$(ARCH).mpy', + 'uheapq': 'uheapq/uheapq_$(ARCH).mpy', + 'urandom': 'urandom/urandom_$(ARCH).mpy', + 'ure': 'ure/ure_$(ARCH).mpy', + 'uzlib': 'uzlib/uzlib_$(ARCH).mpy', +} + +# Code to allow a target MicroPython to import an .mpy from RAM +injected_import_hook_code = """\ +import sys, uos, uio +class __File(uio.IOBase): + def __init__(self): + self.off = 0 + def ioctl(self, request, arg): + return 0 + def readinto(self, buf): + buf[:] = memoryview(__buf)[self.off:self.off + len(buf)] + self.off += len(buf) + return len(buf) +class __FS: + def mount(self, readonly, mkfs): + pass + def chdir(self, path): + pass + def stat(self, path): + if path == '__injected.mpy': + return tuple(0 for _ in range(10)) + else: + raise OSError(-2) # ENOENT + def open(self, path, mode): + return __File() +uos.mount(__FS(), '/__remote') +uos.chdir('/__remote') +sys.modules['{}'] = __import__('__injected') +""" + +class TargetSubprocess: + def __init__(self, cmd): + self.cmd = cmd + + def close(self): + pass + + def run_script(self, script): + try: + p = subprocess.run(self.cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, input=script) + return p.stdout, None + except subprocess.CalledProcessError as er: + return b'', er + +class TargetPyboard: + def __init__(self, pyb): + self.pyb = pyb + self.pyb.enter_raw_repl() + + def close(self): + self.pyb.exit_raw_repl() + self.pyb.close() + + def run_script(self, script): + try: + self.pyb.enter_raw_repl() + output = self.pyb.exec_(script) + output = output.replace(b'\r\n', b'\n') + return output, None + except pyboard.PyboardError as er: + return b'', er + +def run_tests(target_truth, target, args, stats): + for test_file in args.files: + # Find supported test + for k, v in TEST_MAPPINGS.items(): + if test_file.find(k) != -1: + test_module = k + test_mpy = v.replace('$(ARCH)', args.arch) + break + else: + print('---- {} - no matching mpy'.format(test_file)) + continue + + # Read test script + with open(test_file, 'rb') as f: + test_file_data = f.read() + + # Create full test with embedded .mpy + try: + with open(NATMOD_EXAMPLE_DIR + test_mpy, 'rb') as f: + test_script = b'__buf=' + bytes(repr(f.read()), 'ascii') + b'\n' + except OSError: + print('---- {} - mpy file not compiled'.format(test_file)) + continue + test_script += bytes(injected_import_hook_code.format(test_module), 'ascii') + test_script += test_file_data + + # Run test under MicroPython + result_out, error = target.run_script(test_script) + + # Work out result of test + extra = '' + if error is None and result_out == b'SKIP\n': + result = 'SKIP' + elif error is not None: + result = 'FAIL' + extra = ' - ' + str(error) + else: + # Check result against truth + try: + with open(test_file + '.exp', 'rb') as f: + result_exp = f.read() + error = None + except OSError: + result_exp, error = target_truth.run_script(test_file_data) + if error is not None: + result = 'TRUTH FAIL' + elif result_out != result_exp: + result = 'FAIL' + print(result_out) + else: + result = 'pass' + + # Accumulate statistics + stats['total'] += 1 + if result == 'pass': + stats['pass'] += 1 + elif result == 'SKIP': + stats['skip'] += 1 + else: + stats['fail'] += 1 + + # Print result + print('{:4} {}{}'.format(result, test_file, extra)) + +def main(): + cmd_parser = argparse.ArgumentParser(description='Run dynamic-native-module tests under MicroPython') + cmd_parser.add_argument('-p', '--pyboard', action='store_true', help='run tests via pyboard.py') + cmd_parser.add_argument('-d', '--device', default='/dev/ttyACM0', help='the device for pyboard.py') + cmd_parser.add_argument('-a', '--arch', default='x64', help='native architecture of the target') + cmd_parser.add_argument('files', nargs='*', help='input test files') + args = cmd_parser.parse_args() + + target_truth = TargetSubprocess([CPYTHON3]) + + if args.pyboard: + target = TargetPyboard(pyboard.Pyboard(args.device)) + else: + target = TargetSubprocess([MICROPYTHON]) + + stats = {'total': 0, 'pass': 0, 'fail':0, 'skip': 0} + run_tests(target_truth, target, args, stats) + + target.close() + target_truth.close() + + print('{} tests performed'.format(stats['total'])) + print('{} tests passed'.format(stats['pass'])) + if stats['fail']: + print('{} tests failed'.format(stats['fail'])) + if stats['skip']: + print('{} tests skipped'.format(stats['skip'])) + + if stats['fail']: + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/tests/stress/qstr_limit.py b/tests/stress/qstr_limit.py index 90c85dae9..889ab7e51 100644 --- a/tests/stress/qstr_limit.py +++ b/tests/stress/qstr_limit.py @@ -64,19 +64,17 @@ for l in range(254, 259): print('RuntimeError', l) # import module -# (different OS's have different results so only print those that are consistent) -for l in range(150, 259): +# (different OS's have different results so only run those that are consistent) +for l in (100, 101, 256, 257, 258): try: __import__(make_id(l)) except ImportError: - if l < 152: - print('ok', l) + print('ok', l) except RuntimeError: - if l > 255: - print('RuntimeError', l) + print('RuntimeError', l) # import package -for l in range(125, 130): +for l in (100, 101, 102, 128, 129): try: exec('import ' + make_id(l) + '.' + make_id(l, 'A')) except ImportError: diff --git a/tests/stress/qstr_limit.py.exp b/tests/stress/qstr_limit.py.exp index 516c9d7f6..455761bc7 100644 --- a/tests/stress/qstr_limit.py.exp +++ b/tests/stress/qstr_limit.py.exp @@ -31,13 +31,13 @@ RuntimeError 258 RuntimeError 256 RuntimeError 257 RuntimeError 258 -ok 150 -ok 151 +ok 100 +ok 101 RuntimeError 256 RuntimeError 257 RuntimeError 258 -ok 125 -ok 126 -ok 127 +ok 100 +ok 101 +ok 102 RuntimeError 128 RuntimeError 129 diff --git a/tools/mpy-tool.py b/tools/mpy-tool.py index 581603249..77a129944 100755 --- a/tools/mpy-tool.py +++ b/tools/mpy-tool.py @@ -314,7 +314,7 @@ class RawCode(object): print(' MP_ROM_QSTR(%s),' % global_qstrs[qst].qstr_id) for i in range(len(self.objs)): if self.objs[i] is MPFunTable: - print(' mp_fun_table,') + print(' &mp_fun_table,') elif type(self.objs[i]) is float: print('#if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_A || MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_B') print(' MP_ROM_PTR(&const_obj_%s_%u),' % (self.escaped_name, i)) @@ -474,6 +474,9 @@ class RawCodeNative(RawCode): assert 0 def freeze(self, parent_name): + if self.prelude[2] & ~0x0f: + raise FreezeError('unable to freeze code with relocations') + self.freeze_children(parent_name) # generate native code data @@ -691,7 +694,10 @@ def read_mpy(filename): raise Exception('native architecture mismatch') config.mp_small_int_bits = header[3] qstr_win = QStrWindow(qw_size) - return read_raw_code(f, qstr_win) + rc = read_raw_code(f, qstr_win) + rc.mpy_source_file = filename + rc.qstr_win_size = qw_size + return rc def dump_mpy(raw_codes): for rc in raw_codes: @@ -711,6 +717,7 @@ def freeze_mpy(base_qstrs, raw_codes): print('#include "py/objint.h"') print('#include "py/objstr.h"') print('#include "py/emitglue.h"') + print('#include "py/nativeglue.h"') print() print('#if MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE != %u' % config.MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE) @@ -788,6 +795,55 @@ def freeze_mpy(base_qstrs, raw_codes): print(' &raw_code_%s,' % rc.escaped_name) print('};') +def merge_mpy(raw_codes, output_file): + assert len(raw_codes) <= 31 # so var-uints all fit in 1 byte + merged_mpy = bytearray() + + if len(raw_codes) == 1: + with open(raw_codes[0].mpy_source_file, 'rb') as f: + merged_mpy.extend(f.read()) + else: + header = bytearray(5) + header[0] = ord('M') + header[1] = config.MPY_VERSION + header[2] = (config.native_arch << 2 + | config.MICROPY_PY_BUILTINS_STR_UNICODE << 1 + | config.MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE) + header[3] = config.mp_small_int_bits + header[4] = 32 # qstr_win_size + merged_mpy.extend(header) + + bytecode = bytearray() + bytecode_len = 6 + len(raw_codes) * 4 + 2 + bytecode.append(bytecode_len << 2) # kind and length + bytecode.append(0b00000000) # signature prelude + bytecode.append(0b00001000) # size prelude + bytecode.extend(b'\x00\x01') # MP_QSTR_ + bytecode.extend(b'\x00\x01') # MP_QSTR_ + 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'\x51\x63') # MP_BC_LOAD_NONE, MP_BC_RETURN_VALUE + + bytecode.append(0) # n_obj + bytecode.append(len(raw_codes)) # n_raw_code + + merged_mpy.extend(bytecode) + + for rc in raw_codes: + with open(rc.mpy_source_file, 'rb') as f: + f.read(4) # skip header + read_uint(f) # skip qstr_win_size + data = f.read() # read rest of mpy file + merged_mpy.extend(data) + + if output_file is None: + sys.stdout.buffer.write(merged_mpy) + else: + with open(output_file, 'wb') as f: + f.write(merged_mpy) + def main(): import argparse cmd_parser = argparse.ArgumentParser(description='A tool to work with MicroPython .mpy files.') @@ -795,12 +851,16 @@ def main(): help='dump contents of files') cmd_parser.add_argument('-f', '--freeze', action='store_true', help='freeze files') + cmd_parser.add_argument('--merge', action='store_true', + help='merge multiple .mpy files into one') cmd_parser.add_argument('-q', '--qstr-header', help='qstr header file to freeze against') cmd_parser.add_argument('-mlongint-impl', choices=['none', 'longlong', 'mpz'], default='mpz', help='long-int implementation used by target (default mpz)') cmd_parser.add_argument('-mmpz-dig-size', metavar='N', type=int, default=16, help='mpz digit size used by target (default 16)') + cmd_parser.add_argument('-o', '--output', default=None, + help='output file') cmd_parser.add_argument('files', nargs='+', help='input .mpy files') args = cmd_parser.parse_args() @@ -834,6 +894,8 @@ def main(): except FreezeError as er: print(er, file=sys.stderr) sys.exit(1) + elif args.merge: + merged_mpy = merge_mpy(raw_codes, args.output) if __name__ == '__main__': main() diff --git a/tools/mpy_ld.py b/tools/mpy_ld.py new file mode 100755 index 000000000..31c391299 --- /dev/null +++ b/tools/mpy_ld.py @@ -0,0 +1,964 @@ +#!/usr/bin/env python3 +# +# 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. + +""" +Link .o files to .mpy +""" + +import sys, os, struct, re +from elftools.elf import elffile + +sys.path.append(os.path.dirname(__file__) + '/../py') +import makeqstrdata as qstrutil + +# MicroPython constants +MPY_VERSION = 5 +MP_NATIVE_ARCH_X86 = 1 +MP_NATIVE_ARCH_X64 = 2 +MP_NATIVE_ARCH_ARMV7M = 5 +MP_NATIVE_ARCH_ARMV7EMSP = 7 +MP_NATIVE_ARCH_ARMV7EMDP = 8 +MP_NATIVE_ARCH_XTENSA = 9 +MP_NATIVE_ARCH_XTENSAWIN = 10 +MP_CODE_BYTECODE = 2 +MP_CODE_NATIVE_VIPER = 4 +MP_SCOPE_FLAG_VIPERRELOC = 0x10 +MP_SCOPE_FLAG_VIPERRODATA = 0x20 +MP_SCOPE_FLAG_VIPERBSS = 0x40 +MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE = 1 +MICROPY_PY_BUILTINS_STR_UNICODE = 2 +MP_SMALL_INT_BITS = 31 +QSTR_WINDOW_SIZE = 32 + +# ELF constants +R_386_32 = 1 +R_X86_64_64 = 1 +R_XTENSA_32 = 1 +R_386_PC32 = 2 +R_X86_64_PC32 = 2 +R_ARM_ABS32 = 2 +R_386_GOT32 = 3 +R_ARM_REL32 = 3 +R_386_PLT32 = 4 +R_X86_64_PLT32 = 4 +R_XTENSA_PLT = 6 +R_386_GOTOFF = 9 +R_386_GOTPC = 10 +R_ARM_THM_CALL = 10 +R_XTENSA_DIFF32 = 19 +R_XTENSA_SLOT0_OP = 20 +R_ARM_BASE_PREL = 25 # aka R_ARM_GOTPC +R_ARM_GOT_BREL = 26 # aka R_ARM_GOT32 +R_ARM_THM_JUMP24 = 30 +R_X86_64_REX_GOTPCRELX = 42 +R_386_GOT32X = 43 + +################################################################################ +# Architecture configuration + +def asm_jump_x86(entry): + return struct.pack('> 11 == 0 or b_off >> 11 == -1: + # Signed value fits in 12 bits + b0 = 0xe000 | (b_off >> 1 & 0x07ff) + b1 = 0 + else: + # Use large jump + b0 = 0xf000 | (b_off >> 12 & 0x07ff) + b1 = 0xb800 | (b_off >> 1 & 0x7ff) + return struct.pack('> 8) + +class ArchData: + def __init__(self, name, mpy_feature, qstr_entry_size, word_size, arch_got, asm_jump): + self.name = name + self.mpy_feature = mpy_feature + self.qstr_entry_size = qstr_entry_size + self.word_size = word_size + self.arch_got = arch_got + self.asm_jump = asm_jump + self.separate_rodata = name == 'EM_XTENSA' and qstr_entry_size == 4 + +ARCH_DATA = { + 'x86': ArchData( + 'EM_386', + MP_NATIVE_ARCH_X86 << 2 | MICROPY_PY_BUILTINS_STR_UNICODE | MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE, + 2, 4, (R_386_PC32, R_386_GOT32, R_386_GOT32X), asm_jump_x86, + ), + 'x64': ArchData( + 'EM_X86_64', + MP_NATIVE_ARCH_X64 << 2 | MICROPY_PY_BUILTINS_STR_UNICODE | MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE, + 2, 8, (R_X86_64_REX_GOTPCRELX,), asm_jump_x86, + ), + 'armv7m': ArchData( + 'EM_ARM', + MP_NATIVE_ARCH_ARMV7M << 2 | MICROPY_PY_BUILTINS_STR_UNICODE, + 2, 4, (R_ARM_GOT_BREL,), asm_jump_arm, + ), + 'armv7emsp': ArchData( + 'EM_ARM', + MP_NATIVE_ARCH_ARMV7EMSP << 2 | MICROPY_PY_BUILTINS_STR_UNICODE, + 2, 4, (R_ARM_GOT_BREL,), asm_jump_arm, + ), + 'armv7emdp': ArchData( + 'EM_ARM', + MP_NATIVE_ARCH_ARMV7EMDP << 2 | MICROPY_PY_BUILTINS_STR_UNICODE, + 2, 4, (R_ARM_GOT_BREL,), asm_jump_arm, + ), + 'xtensa': ArchData( + 'EM_XTENSA', + MP_NATIVE_ARCH_XTENSA << 2 | MICROPY_PY_BUILTINS_STR_UNICODE, + 2, 4, (R_XTENSA_32, R_XTENSA_PLT), asm_jump_xtensa, + ), + 'xtensawin': ArchData( + 'EM_XTENSA', + MP_NATIVE_ARCH_XTENSAWIN << 2 | MICROPY_PY_BUILTINS_STR_UNICODE, + 4, 4, (R_XTENSA_32, R_XTENSA_PLT), asm_jump_xtensa, + ), +} + +################################################################################ +# Helper functions + +def align_to(value, align): + return (value + align - 1) & ~(align - 1) + +def unpack_u24le(data, offset): + return data[offset] | data[offset + 1] << 8 | data[offset + 2] << 16 + +def pack_u24le(data, offset, value): + data[offset] = value & 0xff + data[offset + 1] = value >> 8 & 0xff + data[offset + 2] = value >> 16 & 0xff + +def xxd(text): + for i in range(0, len(text), 16): + print('{:08x}:'.format(i), end='') + for j in range(4): + off = i + j * 4 + if off < len(text): + d = int.from_bytes(text[off:off + 4], 'little') + print(' {:08x}'.format(d), end='') + print() + +# Smaller numbers are enabled first +LOG_LEVEL_1 = 1 +LOG_LEVEL_2 = 2 +LOG_LEVEL_3 = 3 +log_level = LOG_LEVEL_1 + +def log(level, msg): + if level <= log_level: + print(msg) + +################################################################################ +# Qstr extraction + +def extract_qstrs(source_files): + def read_qstrs(f): + with open(f) as f: + vals = set() + objs = set() + for line in f: + while line: + m = re.search(r'MP_OBJ_NEW_QSTR\((MP_QSTR_[A-Za-z0-9_]*)\)', line) + if m: + objs.add(m.group(1)) + else: + m = re.search(r'MP_QSTR_[A-Za-z0-9_]*', line) + if m: + vals.add(m.group()) + if m: + s = m.span() + line = line[:s[0]] + line[s[1]:] + else: + line = '' + return vals, objs + + static_qstrs = ['MP_QSTR_' + qstrutil.qstr_escape(q) for q in qstrutil.static_qstr_list] + + qstr_vals = set() + qstr_objs = set() + for f in source_files: + vals, objs = read_qstrs(f) + qstr_vals.update(vals) + qstr_objs.update(objs) + qstr_vals.difference_update(static_qstrs) + + return static_qstrs, qstr_vals, qstr_objs + +################################################################################ +# Linker + +class LinkError(Exception): + pass + +class Section: + def __init__(self, name, data, alignment, filename=None): + self.filename = filename + self.name = name + self.data = data + self.alignment = alignment + self.addr = 0 + self.reloc = [] + + @staticmethod + def from_elfsec(elfsec, filename): + assert elfsec.header.sh_addr == 0 + return Section(elfsec.name, elfsec.data(), elfsec.data_alignment, filename) + +class GOTEntry: + def __init__(self, name, sym, link_addr=0): + self.name = name + self.sym = sym + self.offset = None + self.link_addr = link_addr + + def isexternal(self): + return self.sec_name.startswith('.external') + + def istext(self): + return self.sec_name.startswith('.text') + + def isrodata(self): + return self.sec_name.startswith(('.rodata', '.data.rel.ro')) + + def isbss(self): + return self.sec_name.startswith('.bss') + +class LiteralEntry: + def __init__(self, value, offset): + self.value = value + self.offset = offset + +class LinkEnv: + def __init__(self, arch): + self.arch = ARCH_DATA[arch] + self.sections = [] # list of sections in order of output + self.literal_sections = [] # list of literal sections (xtensa only) + self.known_syms = {} # dict of symbols that are defined + self.unresolved_syms = [] # list of unresolved symbols + self.mpy_relocs = [] # list of relocations needed in the output .mpy file + + def check_arch(self, arch_name): + if arch_name != self.arch.name: + raise LinkError('incompatible arch') + + def print_sections(self): + log(LOG_LEVEL_2, 'sections:') + for sec in self.sections: + log(LOG_LEVEL_2, ' {:08x} {} size={}'.format(sec.addr, sec.name, len(sec.data))) + + def find_addr(self, name): + if name in self.known_syms: + s = self.known_syms[name] + return s.section.addr + s['st_value'] + raise LinkError('unknown symbol: {}'.format(name)) + +def build_got_generic(env): + env.got_entries = {} + for sec in env.sections: + for r in sec.reloc: + s = r.sym + if not (s.entry['st_info']['bind'] == 'STB_GLOBAL' and r['r_info_type'] in env.arch.arch_got): + continue + s_type = s.entry['st_info']['type'] + assert s_type in ('STT_NOTYPE', 'STT_FUNC', 'STT_OBJECT'), s_type + assert s.name + if s.name in env.got_entries: + continue + env.got_entries[s.name] = GOTEntry(s.name, s) + +def build_got_xtensa(env): + env.got_entries = {} + env.lit_entries = {} + env.xt_literals = {} + + # Extract the values from the literal table + for sec in env.literal_sections: + assert len(sec.data) % env.arch.word_size == 0 + + # Look through literal relocations to find any global pointers that should be GOT entries + for r in sec.reloc: + s = r.sym + s_type = s.entry['st_info']['type'] + assert s_type in ('STT_NOTYPE', 'STT_FUNC', 'STT_OBJECT', 'STT_SECTION'), s_type + assert r['r_info_type'] in env.arch.arch_got + assert r['r_offset'] % env.arch.word_size == 0 + # This entry is a global pointer + existing = struct.unpack_from(' {}+{:08x}'.format(g.offset, g.name, g.sec_name, g.link_addr)) + +def populate_lit(env): + log(LOG_LEVEL_2, 'LIT: {:08x}'.format(env.lit_section.addr)) + for lit_entry in env.lit_entries.values(): + value = lit_entry.value + log(LOG_LEVEL_2, ' {:08x} = {:08x}'.format(lit_entry.offset, value)) + o = env.lit_section.addr + lit_entry.offset + env.full_text[o:o + env.arch.word_size] = value.to_bytes(env.arch.word_size, 'little') + +def do_relocation_text(env, text_addr, r): + # Extract relevant info about symbol that's being relocated + s = r.sym + s_bind = s.entry['st_info']['bind'] + s_shndx = s.entry['st_shndx'] + s_type = s.entry['st_info']['type'] + r_offset = r['r_offset'] + text_addr + r_info_type = r['r_info_type'] + try: + # only for RELA sections + r_addend = r['r_addend'] + except KeyError: + r_addend = 0 + + # Default relocation type and name for logging + reloc_type = 'le32' + log_name = None + + if (env.arch.name == 'EM_386' and r_info_type in (R_386_PC32, R_386_PLT32) + or env.arch.name == 'EM_X86_64' and r_info_type in (R_X86_64_PC32, R_X86_64_PLT32) + or env.arch.name == 'EM_ARM' and r_info_type in (R_ARM_REL32, R_ARM_THM_CALL, R_ARM_THM_JUMP24) + or s_bind == 'STB_LOCAL' and env.arch.name == 'EM_XTENSA' and r_info_type == R_XTENSA_32 # not GOT + ): + # Standard relocation to fixed location within text/rodata + if hasattr(s, 'resolved'): + s = s.resolved + + sec = s.section + + if env.arch.separate_rodata and sec.name.startswith('.rodata'): + raise LinkError('fixed relocation to rodata with rodata referenced via GOT') + + if sec.name.startswith('.bss'): + raise LinkError('{}: fixed relocation to bss (bss variables can\'t be static)'.format(s.filename)) + + if sec.name.startswith('.external'): + raise LinkError('{}: fixed relocation to external symbol: {}'.format(s.filename, s.name)) + + addr = sec.addr + s['st_value'] + reloc = addr - r_offset + r_addend + + if r_info_type in (R_ARM_THM_CALL, R_ARM_THM_JUMP24): + # Both relocations have the same bit pattern to rewrite: + # R_ARM_THM_CALL: bl + # R_ARM_THM_JUMP24: b.w + reloc_type = 'thumb_b' + + elif (env.arch.name == 'EM_386' and r_info_type == R_386_GOTPC + or env.arch.name == 'EM_ARM' and r_info_type == R_ARM_BASE_PREL + ): + # Relocation to GOT address itself + assert s.name == '_GLOBAL_OFFSET_TABLE_' + addr = env.got_section.addr + reloc = addr - r_offset + r_addend + + elif (env.arch.name == 'EM_386' and r_info_type in (R_386_GOT32, R_386_GOT32X) + or env.arch.name == 'EM_ARM' and r_info_type == R_ARM_GOT_BREL + ): + # Relcation pointing to GOT + reloc = addr = env.got_entries[s.name].offset + + elif env.arch.name == 'EM_X86_64' and r_info_type == R_X86_64_REX_GOTPCRELX: + # Relcation pointing to GOT + got_entry = env.got_entries[s.name] + addr = env.got_section.addr + got_entry.offset + reloc = addr - r_offset + r_addend + + elif env.arch.name == 'EM_386' and r_info_type == R_386_GOTOFF: + # Relocation relative to GOT + addr = s.section.addr + s['st_value'] + reloc = addr - env.got_section.addr + r_addend + + elif env.arch.name == 'EM_XTENSA' and r_info_type == R_XTENSA_SLOT0_OP: + # Relocation pointing to GOT, xtensa specific + sec = s.section + if sec.name.startswith('.text'): + # it looks like R_XTENSA_SLOT0_OP into .text is already correctly relocated + return + assert sec.name.startswith('.literal'), sec.name + lit_idx = '{}+0x{:x}'.format(sec.filename, r_addend) + lit_ptr = env.xt_literals[lit_idx] + if isinstance(lit_ptr, str): + addr = env.got_section.addr + env.got_entries[lit_ptr].offset + log_name = 'GOT {}'.format(lit_ptr) + else: + addr = env.lit_section.addr + env.lit_entries[lit_ptr].offset + log_name = 'LIT' + reloc = addr - r_offset + reloc_type = 'xtensa_l32r' + + elif env.arch.name == 'EM_XTENSA' and r_info_type == R_XTENSA_DIFF32: + if s.section.name.startswith('.text'): + # it looks like R_XTENSA_DIFF32 into .text is already correctly relocated + return + assert 0 + + else: + # Unknown/unsupported relocation + assert 0, r_info_type + + # Write relocation + if reloc_type == 'le32': + existing, = struct.unpack_from('= 0x400000: # 2's complement + existing -= 0x800000 + new = existing + reloc + b_h = (b_h & 0xf800) | (new >> 12) & 0x7ff + b_l = (b_l & 0xf800) | (new >> 1) & 0x7ff + struct.pack_into('> 8 + l32r_imm16 = (l32r_imm16 + reloc >> 2) & 0xffff + l32r = l32r & 0xff | l32r_imm16 << 8 + pack_u24le(env.full_text, r_offset, l32r) + else: + assert 0, reloc_type + + # Log information about relocation + if log_name is None: + if s_type == 'STT_SECTION': + log_name = s.section.name + else: + log_name = s.name + log(LOG_LEVEL_3, ' {:08x} {} -> {:08x}'.format(r_offset, log_name, addr)) + +def do_relocation_data(env, text_addr, r): + s = r.sym + s_type = s.entry['st_info']['type'] + r_offset = r['r_offset'] + text_addr + r_info_type = r['r_info_type'] + try: + # only for RELA sections + r_addend = r['r_addend'] + except KeyError: + r_addend = 0 + + if (env.arch.name == 'EM_386' and r_info_type == R_386_32 + or env.arch.name == 'EM_X86_64' and r_info_type == R_X86_64_64 + or env.arch.name == 'EM_ARM' and r_info_type == R_ARM_ABS32 + or env.arch.name == 'EM_XTENSA' and r_info_type == R_XTENSA_32): + # Relocation in data.rel.ro to internal/external symbol + if env.arch.word_size == 4: + struct_type = ' {} {:08x}'.format(r_offset, log_name, addr)) + if env.arch.separate_rodata: + data = env.full_rodata + else: + data = env.full_text + existing, = struct.unpack_from(struct_type, data, r_offset) + if sec.name.startswith(('.text', '.rodata', '.data.rel.ro', '.bss')): + struct.pack_into(struct_type, data, r_offset, existing + addr) + kind = sec.name + elif sec.name == '.external.mp_fun_table': + assert addr == 0 + kind = s.mp_fun_table_offset + else: + assert 0, sec.name + if env.arch.separate_rodata: + base = '.rodata' + else: + base = '.text' + env.mpy_relocs.append((base, r_offset, kind)) + + else: + # Unknown/unsupported relocation + assert 0, r_info_type + +def load_object_file(env, felf): + with open(felf, 'rb') as f: + elf = elffile.ELFFile(f) + env.check_arch(elf['e_machine']) + + # Get symbol table + symtab = list(elf.get_section_by_name('.symtab').iter_symbols()) + + # Load needed sections from ELF file + sections_shndx = {} # maps elf shndx to Section object + for idx, s in enumerate(elf.iter_sections()): + if s.header.sh_type in ('SHT_PROGBITS', 'SHT_NOBITS'): + if s.data_size == 0: + # Ignore empty sections + pass + elif s.name.startswith(('.literal', '.text', '.rodata', '.data.rel.ro', '.bss')): + sec = Section.from_elfsec(s, felf) + sections_shndx[idx] = sec + if s.name.startswith('.literal'): + env.literal_sections.append(sec) + else: + env.sections.append(sec) + elif s.name.startswith('.data'): + raise LinkError('{}: {} non-empty'.format(felf, s.name)) + else: + # Ignore section + pass + elif s.header.sh_type in ('SHT_REL', 'SHT_RELA'): + shndx = s.header.sh_info + if shndx in sections_shndx: + sec = sections_shndx[shndx] + sec.reloc_name = s.name + sec.reloc = list(s.iter_relocations()) + for r in sec.reloc: + r.sym = symtab[r['r_info_sym']] + + # Link symbols to their sections, and update known and unresolved symbols + for sym in symtab: + sym.filename = felf + shndx = sym.entry['st_shndx'] + if shndx in sections_shndx: + # Symbol with associated section + sym.section = sections_shndx[shndx] + if sym['st_info']['bind'] == 'STB_GLOBAL': + # Defined global symbol + if sym.name in env.known_syms and not sym.name.startswith('__x86.get_pc_thunk.'): + raise LinkError('duplicate symbol: {}'.format(sym.name)) + env.known_syms[sym.name] = sym + elif sym.entry['st_shndx'] == 'SHN_UNDEF' and sym['st_info']['bind'] == 'STB_GLOBAL': + # Undefined global symbol, needs resolving + env.unresolved_syms.append(sym) + +def link_objects(env, native_qstr_vals_len, native_qstr_objs_len): + # Build GOT information + if env.arch.name == 'EM_XTENSA': + build_got_xtensa(env) + else: + build_got_generic(env) + + # Creat GOT section + got_size = len(env.got_entries) * env.arch.word_size + env.got_section = Section('GOT', bytearray(got_size), env.arch.word_size) + if env.arch.name == 'EM_XTENSA': + env.sections.insert(0, env.got_section) + else: + env.sections.append(env.got_section) + + # Create optional literal section + if env.arch.name == 'EM_XTENSA': + lit_size = len(env.lit_entries) * env.arch.word_size + env.lit_section = Section('LIT', bytearray(lit_size), env.arch.word_size) + env.sections.insert(1, env.lit_section) + + # Create section to contain mp_native_qstr_val_table + env.qstr_val_section = Section('.text.QSTR_VAL', bytearray(native_qstr_vals_len * env.arch.qstr_entry_size), env.arch.qstr_entry_size) + env.sections.append(env.qstr_val_section) + + # Create section to contain mp_native_qstr_obj_table + env.qstr_obj_section = Section('.text.QSTR_OBJ', bytearray(native_qstr_objs_len * env.arch.word_size), env.arch.word_size) + env.sections.append(env.qstr_obj_section) + + # Resolve unknown symbols + mp_fun_table_sec = Section('.external.mp_fun_table', b'', 0) + fun_table = {key: 67 + idx + for idx, key in enumerate([ + 'mp_type_type', + 'mp_type_str', + 'mp_type_list', + 'mp_type_dict', + 'mp_type_fun_builtin_0', + 'mp_type_fun_builtin_1', + 'mp_type_fun_builtin_2', + 'mp_type_fun_builtin_3', + 'mp_type_fun_builtin_var', + 'mp_stream_read_obj', + 'mp_stream_readinto_obj', + 'mp_stream_unbuffered_readline_obj', + 'mp_stream_write_obj', + ]) + } + for sym in env.unresolved_syms: + assert sym['st_value'] == 0 + if sym.name == '_GLOBAL_OFFSET_TABLE_': + pass + elif sym.name == 'mp_fun_table': + sym.section = Section('.external', b'', 0) + elif sym.name == 'mp_native_qstr_val_table': + sym.section = env.qstr_val_section + elif sym.name == 'mp_native_qstr_obj_table': + sym.section = env.qstr_obj_section + elif sym.name in env.known_syms: + sym.resolved = env.known_syms[sym.name] + else: + if sym.name in fun_table: + sym.section = mp_fun_table_sec + sym.mp_fun_table_offset = fun_table[sym.name] + else: + raise LinkError('{}: undefined symbol: {}'.format(sym.filename, sym.name)) + + # Align sections, assign their addresses, and create full_text + env.full_text = bytearray(env.arch.asm_jump(8)) # dummy, to be filled in later + env.full_rodata = bytearray(0) + env.full_bss = bytearray(0) + for sec in env.sections: + if env.arch.separate_rodata and sec.name.startswith(('.rodata', '.data.rel.ro')): + data = env.full_rodata + elif sec.name.startswith('.bss'): + data = env.full_bss + else: + data = env.full_text + sec.addr = align_to(len(data), sec.alignment) + data.extend(b'\x00' * (sec.addr - len(data))) + data.extend(sec.data) + + env.print_sections() + + populate_got(env) + if env.arch.name == 'EM_XTENSA': + populate_lit(env) + + # Fill in relocations + for sec in env.sections: + if not sec.reloc: + continue + log(LOG_LEVEL_3, '{}: {} relocations via {}:'.format(sec.filename, sec.name, sec.reloc_name)) + for r in sec.reloc: + if sec.name.startswith(('.text', '.rodata')): + do_relocation_text(env, sec.addr, r) + elif sec.name.startswith('.data.rel.ro'): + do_relocation_data(env, sec.addr, r) + else: + assert 0, sec.name + +################################################################################ +# .mpy output + +class MPYOutput: + def open(self, fname): + self.f = open(fname, 'wb') + self.prev_base = -1 + self.prev_offset = -1 + + def close(self): + self.f.close() + + def write_bytes(self, buf): + self.f.write(buf) + + def write_uint(self, val): + b = bytearray() + b.insert(0, val & 0x7f) + val >>= 7 + while val: + b.insert(0, 0x80 | (val & 0x7f)) + val >>= 7 + self.write_bytes(b) + + def write_qstr(self, s): + if s in qstrutil.static_qstr_list: + self.write_bytes(bytes([0, qstrutil.static_qstr_list.index(s) + 1])) + else: + s = bytes(s, 'ascii') + self.write_uint(len(s) << 1) + self.write_bytes(s) + + def write_reloc(self, base, offset, dest, n): + need_offset = not (base == self.prev_base and offset == self.prev_offset + 1) + self.prev_offset = offset + n - 1 + if dest <= 2: + dest = (dest << 1) | (n > 1) + else: + assert 6 <= dest <= 127 + assert n == 1 + dest = dest << 1 | need_offset + assert 0 <= dest <= 0xfe, dest + self.write_bytes(bytes([dest])) + if need_offset: + if base == '.text': + base = 0 + elif base == '.rodata': + base = 1 + self.write_uint(offset << 1 | base) + if n > 1: + self.write_uint(n) + +def build_mpy(env, entry_offset, fmpy, native_qstr_vals, native_qstr_objs): + # Write jump instruction to start of text + jump = env.arch.asm_jump(entry_offset) + env.full_text[:len(jump)] = jump + + log(LOG_LEVEL_1, 'arch: {}'.format(env.arch.name)) + log(LOG_LEVEL_1, 'text size: {}'.format(len(env.full_text))) + if len(env.full_rodata): + log(LOG_LEVEL_1, 'rodata size: {}'.format(len(env.full_rodata))) + log(LOG_LEVEL_1, 'bss size: {}'.format(len(env.full_bss))) + log(LOG_LEVEL_1, 'GOT entries: {}'.format(len(env.got_entries))) + + #xxd(env.full_text) + + out = MPYOutput() + out.open(fmpy) + + # MPY: header + out.write_bytes(bytearray([ + ord('M'), + MPY_VERSION, + env.arch.mpy_feature, + MP_SMALL_INT_BITS, + QSTR_WINDOW_SIZE, + ])) + + # MPY: kind/len + out.write_uint(len(env.full_text) << 2 | (MP_CODE_NATIVE_VIPER - MP_CODE_BYTECODE)) + + # MPY: machine code + out.write_bytes(env.full_text) + + # MPY: n_qstr_link (assumes little endian) + out.write_uint(len(native_qstr_vals) + len(native_qstr_objs)) + for q in range(len(native_qstr_vals)): + off = env.qstr_val_section.addr + q * env.arch.qstr_entry_size + out.write_uint(off << 2) + out.write_qstr(native_qstr_vals[q]) + for q in range(len(native_qstr_objs)): + off = env.qstr_obj_section.addr + q * env.arch.word_size + out.write_uint(off << 2 | 3) + out.write_qstr(native_qstr_objs[q]) + + # MPY: scope_flags + scope_flags = MP_SCOPE_FLAG_VIPERRELOC + if len(env.full_rodata): + scope_flags |= MP_SCOPE_FLAG_VIPERRODATA + if len(env.full_bss): + scope_flags |= MP_SCOPE_FLAG_VIPERBSS + out.write_uint(scope_flags) + + # MPY: n_obj + out.write_uint(0) + + # MPY: n_raw_code + out.write_uint(0) + + # MPY: rodata and/or bss + if len(env.full_rodata): + rodata_const_table_idx = 1 + out.write_uint(len(env.full_rodata)) + out.write_bytes(env.full_rodata) + if len(env.full_bss): + bss_const_table_idx = bool(env.full_rodata) + 1 + out.write_uint(len(env.full_bss)) + + # MPY: relocation information + prev_kind = None + for base, addr, kind in env.mpy_relocs: + if isinstance(kind, str) and kind.startswith('.text'): + kind = 0 + elif kind in ('.rodata', '.data.rel.ro'): + if env.arch.separate_rodata: + kind = rodata_const_table_idx + else: + kind = 0 + elif isinstance(kind, str) and kind.startswith('.bss'): + kind = bss_const_table_idx + elif kind == 'mp_fun_table': + kind = 6 + else: + kind = 7 + kind + assert addr % env.arch.word_size == 0, addr + offset = addr // env.arch.word_size + if kind == prev_kind and base == prev_base and offset == prev_offset + 1: + prev_n += 1 + prev_offset += 1 + else: + if prev_kind is not None: + out.write_reloc(prev_base, prev_offset - prev_n + 1, prev_kind, prev_n) + prev_kind = kind + prev_base = base + prev_offset = offset + prev_n = 1 + if prev_kind is not None: + out.write_reloc(prev_base, prev_offset - prev_n + 1, prev_kind, prev_n) + + # MPY: sentinel for end of relocations + out.write_bytes(b'\xff') + + out.close() + +################################################################################ +# main + +def do_preprocess(args): + if args.output is None: + assert args.files[0].endswith('.c') + args.output = args.files[0][:-1] + 'config.h' + static_qstrs, qstr_vals, qstr_objs = extract_qstrs(args.files) + with open(args.output, 'w') as f: + print('#include \n' + 'typedef uintptr_t mp_uint_t;\n' + 'typedef intptr_t mp_int_t;\n' + 'typedef uintptr_t mp_off_t;', file=f) + for i, q in enumerate(static_qstrs): + print('#define %s (%u)' % (q, i + 1), file=f) + for i, q in enumerate(sorted(qstr_vals)): + print('#define %s (mp_native_qstr_val_table[%d])' % (q, i), file=f) + for i, q in enumerate(sorted(qstr_objs)): + print('#define MP_OBJ_NEW_QSTR_%s ((mp_obj_t)mp_native_qstr_obj_table[%d])' % (q, i), file=f) + if args.arch == 'xtensawin': + qstr_type = 'uint32_t' # esp32 can only read 32-bit values from IRAM + else: + qstr_type = 'uint16_t' + print('extern const {} mp_native_qstr_val_table[];'.format(qstr_type), file=f) + print('extern const mp_uint_t mp_native_qstr_obj_table[];', file=f) + +def do_link(args): + if args.output is None: + assert args.files[0].endswith('.o') + args.output = args.files[0][:-1] + 'mpy' + native_qstr_vals = [] + native_qstr_objs = [] + if args.qstrs is not None: + with open(args.qstrs) as f: + for l in f: + m = re.match(r'#define MP_QSTR_([A-Za-z0-9_]*) \(mp_native_', l) + if m: + native_qstr_vals.append(m.group(1)) + else: + m = re.match(r'#define MP_OBJ_NEW_QSTR_MP_QSTR_([A-Za-z0-9_]*)', l) + if m: + native_qstr_objs.append(m.group(1)) + log(LOG_LEVEL_2, 'qstr vals: ' + ', '.join(native_qstr_vals)) + log(LOG_LEVEL_2, 'qstr objs: ' + ', '.join(native_qstr_objs)) + env = LinkEnv(args.arch) + try: + for file in args.files: + load_object_file(env, file) + link_objects(env, len(native_qstr_vals), len(native_qstr_objs)) + build_mpy(env, env.find_addr('mpy_init'), args.output, native_qstr_vals, native_qstr_objs) + except LinkError as er: + print('LinkError:', er.args[0]) + sys.exit(1) + +def main(): + import argparse + cmd_parser = argparse.ArgumentParser(description='Run scripts on the pyboard.') + cmd_parser.add_argument('--verbose', '-v', action='count', default=1, help='increase verbosity') + cmd_parser.add_argument('--arch', default='x64', help='architecture') + cmd_parser.add_argument('--preprocess', action='store_true', help='preprocess source files') + cmd_parser.add_argument('--qstrs', default=None, help='file defining additional qstrs') + cmd_parser.add_argument('--output', '-o', default=None, help='output .mpy file (default to input with .o->.mpy)') + cmd_parser.add_argument('files', nargs='+', help='input files') + args = cmd_parser.parse_args() + + global log_level + log_level = args.verbose + + if args.preprocess: + do_preprocess(args) + else: + do_link(args) + +if __name__ == '__main__': + main() diff --git a/tools/pyboard.py b/tools/pyboard.py index c32fb002c..2a255e91c 100755 --- a/tools/pyboard.py +++ b/tools/pyboard.py @@ -484,6 +484,33 @@ def filesystem_command(pyb, args): pyb.close() sys.exit(1) +_injected_import_hook_code = """\ +import uos, uio +class _FS: + class File(uio.IOBase): + def __init__(self): + self.off = 0 + def ioctl(self, request, arg): + return 0 + def readinto(self, buf): + buf[:] = memoryview(_injected_buf)[self.off:self.off + len(buf)] + self.off += len(buf) + return len(buf) + mount = umount = chdir = lambda *args: None + def stat(self, path): + if path == '_injected.mpy': + return tuple(0 for _ in range(10)) + else: + raise OSError(-2) # ENOENT + def open(self, path, mode): + return self.File() +uos.mount(_FS(), '/_') +uos.chdir('/_') +from _injected import * +uos.umount('/_') +del _injected_buf, _FS +""" + def main(): import argparse cmd_parser = argparse.ArgumentParser(description='Run scripts on the pyboard.') @@ -544,6 +571,9 @@ def main(): for filename in args.files: with open(filename, 'rb') as f: pyfile = f.read() + if filename.endswith('.mpy') and pyfile[0] == ord('M'): + pyb.exec_('_injected_buf=' + repr(pyfile)) + pyfile = _injected_import_hook_code execbuffer(pyfile) # exiting raw-REPL just drops to friendly-REPL mode diff --git a/tools/tinytest-codegen.py b/tools/tinytest-codegen.py index 7580522ee..424f70a9f 100755 --- a/tools/tinytest-codegen.py +++ b/tools/tinytest-codegen.py @@ -33,8 +33,10 @@ test_function = ( "void {name}(void* data) {{\n" " static const char pystr[] = {script};\n" " static const char exp[] = {output};\n" + ' printf("\\n");\n' " upytest_set_expected_output(exp, sizeof(exp) - 1);\n" " upytest_execute_test(pystr);\n" + ' printf("result: ");\n' "}}" )