diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 83726922a5..c5039f58b5 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -1,3 +1,6 @@ +# tools/gen-cpydiff.py: Fix formatting of doc strings for new Black. +0f78c36c5aa458a954eed39a46942209107a553e + # tests/run-tests.py: Reformat with Black. 2a38d7103672580882fb621a5b76e8d26805d593 diff --git a/.github/workflows/ports_mimxrt.yml b/.github/workflows/ports_mimxrt.yml new file mode 100644 index 0000000000..8fbc2209e4 --- /dev/null +++ b/.github/workflows/ports_mimxrt.yml @@ -0,0 +1,23 @@ +name: mimxrt port + +on: + push: + pull_request: + paths: + - '.github/workflows/*.yml' + - 'tools/**' + - 'py/**' + - 'extmod/**' + - 'lib/**' + - 'drivers/**' + - 'ports/mimxrt/**' + +jobs: + build: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: Install packages + run: source tools/ci.sh && ci_mimxrt_setup + - name: Build + run: source tools/ci.sh && ci_mimxrt_build diff --git a/docs/esp8266/tutorial/ssd1306.rst b/docs/esp8266/tutorial/ssd1306.rst new file mode 100644 index 0000000000..4dca82afc4 --- /dev/null +++ b/docs/esp8266/tutorial/ssd1306.rst @@ -0,0 +1,93 @@ +.. _ssd1306: + +Using a SSD1306 OLED display +============================ + +The SSD1306 OLED display uses either a SPI or I2C interface and comes in a variety of +sizes (128x64, 128x32, 72x40, 64x48) and colours (white, yellow, blue, yellow + blue). + +Hardware SPI interface:: + + from machine import Pin, SPI + import ssd1306 + + hspi = SPI(1) # sck=14 (scl), mosi=13 (sda), miso=12 (unused) + + dc = Pin(4) # data/command + rst = Pin(5) # reset + cs = Pin(15) # chip select, some modules do not have a pin for this + + display = ssd1306.SSD1306_SPI(128, 64, hspi, dc, rst, cs) + +Software SPI interface:: + + from machine import Pin, SoftSPI + import ssd1306 + + spi = SoftSPI(baudrate=500000, polarity=1, phase=0, sck=Pin(14), mosi=Pin(13), miso=Pin(12)) + + dc = Pin(4) # data/command + rst = Pin(5) # reset + cs = Pin(15) # chip select, some modules do not have a pin for this + + display = ssd1306.SSD1306_SPI(128, 64, spi, dc, rst, cs) + +I2C interface:: + + from machine import Pin, I2C + import ssd1306 + + # using default address 0x3C + i2c = I2C(sda=Pin(4), scl=Pin(5)) + display = ssd1306.SSD1306_I2C(128, 64, i2c) + +Print Hello World on the first line:: + + display.text('Hello, World!', 0, 0, 1) + display.show() + +Basic functions:: + + display.poweroff() # power off the display, pixels persist in memory + display.poweron() # power on the display, pixels redrawn + display.contrast(0) # dim + display.contrast(255) # bright + display.invert(1) # display inverted + display.invert(0) # display normal + display.rotate(True) # rotate 180 degrees + display.rotate(False) # rotate 0 degrees + display.show() # write the contents of the FrameBuffer to display memory + +Subclassing FrameBuffer provides support for graphics primitives:: + + display.fill(0) # fill entire screen with colour=0 + display.pixel(0, 10) # get pixel at x=0, y=10 + display.pixel(0, 10, 1) # set pixel at x=0, y=10 to colour=1 + display.hline(0, 8, 4, 1) # draw horizontal line x=0, y=8, width=4, colour=1 + display.vline(0, 8, 4, 1) # draw vertical line x=0, y=8, height=4, colour=1 + display.line(0, 0, 127, 63, 1) # draw a line from 0,0 to 127,63 + display.rect(10, 10, 107, 43, 1) # draw a rectangle outline 10,10 to 107,43, colour=1 + display.fill_rect(10, 10, 107, 43, 1) # draw a solid rectangle 10,10 to 107,43, colour=1 + display.text('Hello World', 0, 0, 1) # draw some text at x=0, y=0, colour=1 + display.scroll(20, 0) # scroll 20 pixels to the right + + # draw another FrameBuffer on top of the current one at the given coordinates + import framebuf + fbuf = framebuf.FrameBuffer(bytearray(8 * 8 * 1), 8, 8, framebuf.MONO_VLSB) + fbuf.line(0, 0, 7, 7, 1) + display.blit(fbuf, 10, 10, 0) # draw on top at x=10, y=10, key=0 + display.show() + +Draw the MicroPython logo and print some text:: + + display.fill(0) + display.fill_rect(0, 0, 32, 32, 1) + display.fill_rect(2, 2, 28, 28, 0) + display.vline(9, 8, 22, 1) + display.vline(16, 2, 22, 1) + display.vline(23, 8, 22, 1) + display.fill_rect(26, 24, 2, 4, 1) + display.text('MicroPython', 40, 0, 1) + display.text('SSD1306', 40, 12, 1) + display.text('OLED 128x64', 40, 24, 1) + display.show() diff --git a/docs/library/builtins.rst b/docs/library/builtins.rst index 6cbf95d53c..c7f983010e 100644 --- a/docs/library/builtins.rst +++ b/docs/library/builtins.rst @@ -182,10 +182,6 @@ Exceptions .. exception:: OSError - |see_cpython| :py:class:`cpython:OSError`. CircuitPython doesn't implement the ``errno`` - attribute, instead use the standard way to access exception arguments: - ``exc.args[0]``. - .. exception:: RuntimeError .. exception:: ReloadException diff --git a/docs/library/machine.PWM.rst b/docs/library/machine.PWM.rst new file mode 100644 index 0000000000..f2273d8b45 --- /dev/null +++ b/docs/library/machine.PWM.rst @@ -0,0 +1,79 @@ +.. currentmodule:: machine +.. _machine.PWM: + +class PWM -- pulse width modulation +=================================== + +This class provides pulse width modulation output. + +Example usage:: + + from machine import PWM + + pwm = PWM(pin) # create a PWM object on a pin + pwm.duty_u16(32768) # set duty to 50% + + # reinitialise with a period of 200us, duty of 5us + pwm.init(freq=5000, duty_ns=5000) + + pwm.duty_ns(3000) # set pulse width to 3us + + pwm.deinit() + +Constructors +------------ + +.. class:: PWM(dest, \*, freq, duty_u16, duty_ns) + + Construct and return a new PWM object using the following parameters: + + - *dest* is the entity on which the PWM is output, which is usually a + :ref:`machine.Pin ` object, but a port may allow other values, + like integers. + - *freq* should be an integer which sets the frequency in Hz for the + PWM cycle. + - *duty_u16* sets the duty cycle as a ratio ``duty_u16 / 65535``. + - *duty_ns* sets the pulse width in nanoseconds. + + Setting *freq* may affect other PWM objects if the objects share the same + underlying PWM generator (this is hardware specific). + Only one of *duty_u16* and *duty_ns* should be specified at a time. + +Methods +------- + +.. method:: PWM.init(\*, freq, duty_u16, duty_ns) + + Modify settings for the PWM object. See the above constructor for details + about the parameters. + +.. method:: PWM.deinit() + + Disable the PWM output. + +.. method:: PWM.freq([value]) + + Get or set the current frequency of the PWM output. + + With no arguments the frequency in Hz is returned. + + With a single *value* argument the frequency is set to that value in Hz. The + method may raise a ``ValueError`` if the frequency is outside the valid range. + +.. method:: PWM.duty_u16([value]) + + Get or set the current duty cycle of the PWM output, as an unsigned 16-bit + value in the range 0 to 65535 inclusive. + + With no arguments the duty cycle is returned. + + With a single *value* argument the duty cycle is set to that value, measured + as the ratio ``value / 65535``. + +.. method:: PWM.duty_ns([value]) + + Get or set the current pulse width of the PWM output, as a value in nanoseconds. + + With no arguments the pulse width in nanoseconds is returned. + + With a single *value* argument the pulse width is set to that value. diff --git a/docs/library/rp2.Flash.rst b/docs/library/rp2.Flash.rst new file mode 100644 index 0000000000..3b423adfdd --- /dev/null +++ b/docs/library/rp2.Flash.rst @@ -0,0 +1,36 @@ +.. currentmodule:: rp2 +.. _rp2.Flash: + +class Flash -- access to built-in flash storage +=============================================== + +This class gives access to the SPI flash memory. + +In most cases, to store persistent data on the device, you'll want to use a +higher-level abstraction, for example the filesystem via Python's standard file +API, but this interface is useful to :ref:`customise the filesystem +configuration ` or implement a low-level storage system for your +application. + + +Constructors +------------ + +.. class:: Flash() + + Gets the singleton object for accessing the SPI flash memory. + + +Methods +------- + +.. method:: Flash.readblocks(block_num, buf) + Flash.readblocks(block_num, buf, offset) +.. method:: Flash.writeblocks(block_num, buf) + Flash.writeblocks(block_num, buf, offset) +.. method:: Flash.ioctl(cmd, arg) + + These methods implement the simple and extended + :ref:`block protocol ` defined by + :class:`uos.AbstractBlockDev`. + diff --git a/docs/library/rp2.PIO.rst b/docs/library/rp2.PIO.rst new file mode 100644 index 0000000000..e0675af1e9 --- /dev/null +++ b/docs/library/rp2.PIO.rst @@ -0,0 +1,94 @@ +.. currentmodule:: rp2 +.. _rp2.PIO: + +class PIO -- advanced PIO usage +=============================== + +The :class:`PIO` class gives access to an instance of the RP2040's PIO +(programmable I/O) interface. + +The preferred way to interact with PIO is using :class:`rp2.StateMachine`, the +PIO class is for advanced use. + +For assembling PIO programs, see :func:`rp2.asm_pio`. + + +Constructors +------------ + +.. class:: PIO(id) + + Gets the PIO instance numbered *id*. The RP2040 has two PIO instances, + numbered 0 and 1. + + Raises a ``ValueError`` if any other argument is provided. + + +Methods +------- + +.. method:: PIO.add_program(program) + + Add the *program* to the instruction memory of this PIO instance. + + The amount of memory available for programs on each PIO instance is + limited. If there isn't enough space left in the PIO's program memory + this method will raise ``OSError(ENOMEM)``. + +.. method:: PIO.remove_program([program]) + + Remove *program* from the instruction memory of this PIO instance. + + If no program is provided, it removes all programs. + + It is not an error to remove a program which has already been removed. + +.. method:: PIO.state_machine(id, [program, ...]) + + Gets the state machine numbered *id*. On the RP2040, each PIO instance has + four state machines, numbered 0 to 3. + + Optionally initialize it with a *program*: see `StateMachine.init`. + + >>> rp2.PIO(1).state_machine(3) + StateMachine(7) + +.. method:: PIO.irq(handler=None, trigger=IRQ_SM0|IRQ_SM1|IRQ_SM2|IRQ_SM3, hard=False) + + Returns the IRQ object for this PIO instance. + + MicroPython only uses IRQ 0 on each PIO instance. IRQ 1 is not available. + + Optionally configure it. + + +Constants +--------- + +.. data:: PIO.IN_LOW + PIO.IN_HIGH + PIO.OUT_LOW + PIO.OUT_HIGH + + These constants are used for the *out_init*, *set_init*, and *sideset_init* + arguments to `asm_pio`. + +.. data:: PIO.SHIFT_LEFT + PIO.SHIFT_RIGHT + + These constants are used for the *in_shiftdir* and *out_shiftdir* arguments + to `asm_pio` or `StateMachine.init`. + +.. data:: PIO.JOIN_NONE + PIO.JOIN_TX + PIO.JOIN_RX + + These constants are used for the *fifo_join* argument to `asm_pio`. + +.. data:: PIO.IRQ_SM0 + PIO.IRQ_SM1 + PIO.IRQ_SM2 + PIO.IRQ_SM3 + + These constants are used for the *trigger* argument to `PIO.irq`. + diff --git a/docs/library/rp2.StateMachine.rst b/docs/library/rp2.StateMachine.rst new file mode 100644 index 0000000000..8d73ccf772 --- /dev/null +++ b/docs/library/rp2.StateMachine.rst @@ -0,0 +1,131 @@ +.. currentmodule:: rp2 +.. _rp2.StateMachine: + +class StateMachine -- access to the RP2040's programmable I/O interface +======================================================================= + +The :class:`StateMachine` class gives access to the RP2040's PIO (programmable +I/O) interface. + +For assembling PIO programs, see :func:`rp2.asm_pio`. + + +Constructors +------------ + +.. class:: StateMachine(id, [program, ...]) + + Get the state machine numbered *id*. The RP2040 has two identical PIO + instances, each with 4 state machines: so there are 8 state machines in + total, numbered 0 to 7. + + Optionally initialize it with the given program *program*: see + `StateMachine.init`. + + +Methods +------- + +.. method:: StateMachine.init(program, freq=-1, *, in_base=None, out_base=None, set_base=None, jmp_pin=None, sideset_base=None, in_shiftdir=None, out_shiftdir=None, push_thresh=None, pull_thresh=None) + + Configure the state machine instance to run the given *program*. + + The program is added to the instruction memory of this PIO instance. If the + instruction memory already contains this program, then its offset is + re-used so as to save on instruction memory. + + - *freq* is the frequency in Hz to run the state machine at. Defaults to + the system clock frequency. + + The clock divider is computed as ``system clock frequency / freq``, so + there can be slight rounding errors. + + The minimum possible clock divider is one 65536th of the system clock: so + at the default system clock frequency of 125MHz, the minimum value of + *freq* is ``1908``. To run state machines at slower frequencies, you'll + need to reduce the system clock speed with `machine.freq()`. + - *in_base* is the first pin to use for ``in()`` instructions. + - *out_base* is the first pin to use for ``out()`` instructions. + - *set_base* is the first pin to use for ``set()`` instructions. + - *jmp_pin* is the first pin to use for ``jmp(pin, ...)`` instructions. + - *sideset_base* is the first pin to use for side-setting. + - *in_shiftdir* is the direction the ISR will shift, either + `PIO.SHIFT_LEFT` or `PIO.SHIFT_RIGHT`. + - *out_shiftdir* is the direction the OSR will shift, either + `PIO.SHIFT_LEFT` or `PIO.SHIFT_RIGHT`. + - *push_thresh* is the threshold in bits before auto-push or conditional + re-pushing is triggered. + - *pull_thresh* is the threshold in bits before auto-push or conditional + re-pushing is triggered. + +.. method:: StateMachine.active([value]) + + Gets or sets whether the state machine is currently running. + + >>> sm.active() + True + >>> sm.active(0) + False + +.. method:: StateMachine.restart() + + Restarts the state machine and jumps to the beginning of the program. + + This method clears the state machine's internal state using the RP2040's + ``SM_RESTART`` register. This includes: + + - input and output shift counters + - the contents of the input shift register + - the delay counter + - the waiting-on-IRQ state + - a stalled instruction run using `StateMachine.exec()` + +.. method:: StateMachine.exec(instr) + + Execute a single PIO instruction. Uses `asm_pio_encode` to encode the + instruction from the given string *instr*. + + >>> sm.exec("set(0, 1)") + +.. method:: StateMachine.get(buf=None, shift=0) + + Pull a word from the state machine's RX FIFO. + + If the FIFO is empty, it blocks until data arrives (i.e. the state machine + pushes a word). + + The value is shifted right by *shift* bits before returning, i.e. the + return value is ``word >> shift``. + +.. method:: StateMachine.put(value, shift=0) + + Push a word onto the state machine's TX FIFO. + + If the FIFO is full, it blocks until there is space (i.e. the state machine + pulls a word). + + The value is first shifted left by *shift* bits, i.e. the state machine + receives ``value << shift``. + +.. method:: StateMachine.rx_fifo() + + Returns the number of words in the state machine's RX FIFO. A value of 0 + indicates the FIFO is empty. + + Useful for checking if data is waiting to be read, before calling + `StateMachine.get()`. + +.. method:: StateMachine.tx_fifo() + + Returns the number of words in the state machine's TX FIFO. A value of 0 + indicates the FIFO is empty. + + Useful for checking if there is space to push another word using + `StateMachine.put()`. + +.. method:: StateMachine.irq(handler=None, trigger=0|1, hard=False) + + Returns the IRQ object for the given StateMachine. + + Optionally configure it. + diff --git a/docs/library/rp2.rst b/docs/library/rp2.rst new file mode 100644 index 0000000000..5d168bce20 --- /dev/null +++ b/docs/library/rp2.rst @@ -0,0 +1,83 @@ +.. currentmodule:: rp2 + +:mod:`rp2` --- functionality specific to the RP2040 +=================================================== + +.. module:: rp2 + :synopsis: functionality specific to the RP2 + +The ``rp2`` module contains functions and classes specific to the RP2040, as +used in the Raspberry Pi Pico. + +See the `RP2040 Python datasheet +`_ +for more information, and `pico-micropython-examples +`_ +for example code. + + +PIO related functions +--------------------- + +The ``rp2`` module includes functions for assembling PIO programs. + +For running PIO programs, see :class:`rp2.StateMachine`. + +.. function:: asm_pio(*, out_init=None, set_init=None, sideset_init=None, in_shiftdir=0, out_shiftdir=0, autopush=False, autopull=False, push_thresh=32, pull_thresh=32, fifo_join=PIO.JOIN_NONE) + + Assemble a PIO program. + + The following parameters control the initial state of the GPIO pins, as one + of `PIO.IN_LOW`, `PIO.IN_HIGH`, `PIO.OUT_LOW` or `PIO.OUT_HIGH`. If the + program uses more than one pin, provide a tuple, e.g. + ``out_init=(PIO.OUT_LOW, PIO.OUT_LOW)``. + + - *out_init* configures the pins used for ``out()`` instructions. + - *set_init* configures the pins used for ``set()`` instructions. There can + be at most 5. + - *sideset_init* configures the pins used side-setting. There can be at + most 5. + + The following parameters are used by default, but can be overridden in + `StateMachine.init()`: + + - *in_shiftdir* is the default direction the ISR will shift, either + `PIO.SHIFT_LEFT` or `PIO.SHIFT_RIGHT`. + - *out_shiftdir* is the default direction the OSR will shift, either + `PIO.SHIFT_LEFT` or `PIO.SHIFT_RIGHT`. + - *push_thresh* is the threshold in bits before auto-push or conditional + re-pushing is triggered. + - *pull_thresh* is the threshold in bits before auto-push or conditional + re-pushing is triggered. + + The remaining parameters are: + + - *autopush* configures whether auto-push is enabled. + - *autopull* configures whether auto-pull is enabled. + - *fifo_join* configures whether the 4-word TX and RX FIFOs should be + combined into a single 8-word FIFO for one direction only. The options + are `PIO.JOIN_NONE`, `PIO.JOIN_RX` and `PIO.JOIN_TX`. + +.. function:: asm_pio_encode(instr, sideset_count) + + Assemble a single PIO instruction. You usually want to use `asm_pio()` + instead. + + >>> rp2.asm_pio_encode("set(0, 1)", 0) + 57345 + +.. class:: PIOASMError + + This exception is raised from `asm_pio()` or `asm_pio_encode()` if there is + an error assembling a PIO program. + + +Classes +------- + +.. toctree:: + :maxdepth: 1 + + rp2.Flash.rst + rp2.PIO.rst + rp2.StateMachine.rst diff --git a/docs/library/uasyncio.rst b/docs/library/uasyncio.rst index 0b79b9448e..10170fee85 100644 --- a/docs/library/uasyncio.rst +++ b/docs/library/uasyncio.rst @@ -214,6 +214,14 @@ TCP stream connections This is a coroutine. +.. method:: Stream.readinto(buf) + + Read up to n bytes into *buf* with n being equal to the length of *buf*. + + Return the number of bytes read into *buf*. + + This is a coroutine, and a MicroPython extension. + .. method:: Stream.readline() Read a line and return it. diff --git a/docs/library/uctypes.rst b/docs/library/uctypes.rst index 80f88a39d8..9e15c4c178 100644 --- a/docs/library/uctypes.rst +++ b/docs/library/uctypes.rst @@ -247,7 +247,7 @@ Module contents .. data:: VOID - ``VOID`` is an alias for ``UINT8``, and is provided to conviniently define + ``VOID`` is an alias for ``UINT8``, and is provided to conveniently define C's void pointers: ``(uctypes.PTR, uctypes.VOID)``. .. data:: PTR diff --git a/docs/library/uheapq.rst b/docs/library/uheapq.rst index 67da5f7380..6ee79fcc19 100644 --- a/docs/library/uheapq.rst +++ b/docs/library/uheapq.rst @@ -8,9 +8,11 @@ |see_cpython_module| :mod:`cpython:heapq`. -This module implements the heap queue algorithm. +This module implements the +`min heap queue algorithm `_. -A heap queue is simply a list that has its elements stored in a certain way. +A heap queue is essentially a list that has its elements stored in such a way +that the first item of the list is always the smallest. Functions --------- @@ -21,8 +23,10 @@ Functions .. function:: heappop(heap) - Pop the first item from the ``heap``, and return it. Raises IndexError if - heap is empty. + Pop the first item from the ``heap``, and return it. Raise ``IndexError`` if + ``heap`` is empty. + + The returned item will be the smallest item in the ``heap``. .. function:: heapify(x) diff --git a/docs/library/uselect.rst b/docs/library/uselect.rst index a01c5b62dd..cb7818dc06 100644 --- a/docs/library/uselect.rst +++ b/docs/library/uselect.rst @@ -89,11 +89,11 @@ Methods ``callee-owned tuples``. This function provides efficient, allocation-free way to poll on streams. - If *flags* is 1, one-shot behavior for events is employed: streams for + If *flags* is 1, one-shot behaviour for events is employed: streams for which events happened will have their event masks automatically reset (equivalent to ``poll.modify(obj, 0)``), so new events for such a stream won't be processed until new mask is set with `poll.modify()`. This - behavior is useful for asynchronous I/O schedulers. + behaviour is useful for asynchronous I/O schedulers. .. admonition:: Difference to CPython :class: attention diff --git a/docs/rp2/general.rst b/docs/rp2/general.rst new file mode 100644 index 0000000000..9ff83a9656 --- /dev/null +++ b/docs/rp2/general.rst @@ -0,0 +1,18 @@ +.. _rp2_general: + +General information about the RP2xxx port +========================================= + +The rp2 port supports boards powered by the Raspberry Pi Foundation's RP2xxx +family of microcontrollers, most notably the Raspberry Pi Pico that employs +the RP2040. + +Technical specifications and SoC datasheets +------------------------------------------- + +Datasheets! + +Short summary of tech specs! + +Description of general structure of the port (it's built on top of the APIs +provided by the Raspberry Pi SDK). diff --git a/docs/rp2/img/rpipico.jpg b/docs/rp2/img/rpipico.jpg new file mode 100644 index 0000000000..336d358277 Binary files /dev/null and b/docs/rp2/img/rpipico.jpg differ diff --git a/docs/rp2/quickref.rst b/docs/rp2/quickref.rst new file mode 100644 index 0000000000..6bbe179541 --- /dev/null +++ b/docs/rp2/quickref.rst @@ -0,0 +1,288 @@ +.. _rp2_quickref: + +Quick reference for the RP2 +=========================== + +.. image:: img/rpipico.jpg + :alt: Raspberry Pi Pico + :width: 640px + +The Raspberry Pi Pico Development Board (image attribution: Raspberry Pi Foundation). + +Below is a quick reference for Raspberry Pi RP2xxx boards. If it is your first time +working with this board it may be useful to get an overview of the microcontroller: + +.. toctree:: + :maxdepth: 1 + + general.rst + tutorial/intro.rst + +Installing MicroPython +---------------------- + +See the corresponding section of tutorial: :ref:`rp2_intro`. It also includes +a troubleshooting subsection. + +General board control +--------------------- + +The MicroPython REPL is on the USB serial port. +Tab-completion is useful to find out what methods an object has. +Paste mode (ctrl-E) is useful to paste a large slab of Python code into +the REPL. + +The :mod:`machine` module:: + + import machine + + machine.freq() # get the current frequency of the CPU + machine.freq(240000000) # set the CPU frequency to 240 MHz + +The :mod:`rp2` module:: + + import rp2 + +Delay and timing +---------------- + +Use the :mod:`time ` module:: + + import time + + time.sleep(1) # sleep for 1 second + time.sleep_ms(500) # sleep for 500 milliseconds + time.sleep_us(10) # sleep for 10 microseconds + start = time.ticks_ms() # get millisecond counter + delta = time.ticks_diff(time.ticks_ms(), start) # compute time difference + +Timers +------ + +How do they work? + +.. _rp2_Pins_and_GPIO: + +Pins and GPIO +------------- + +Use the :ref:`machine.Pin ` class:: + + from machine import Pin + + p0 = Pin(0, Pin.OUT) # create output pin on GPIO0 + p0.on() # set pin to "on" (high) level + p0.off() # set pin to "off" (low) level + p0.value(1) # set pin to on/high + + p2 = Pin(2, Pin.IN) # create input pin on GPIO2 + print(p2.value()) # get value, 0 or 1 + + p4 = Pin(4, Pin.IN, Pin.PULL_UP) # enable internal pull-up resistor + p5 = Pin(5, Pin.OUT, value=1) # set pin high on creation + +UART (serial bus) +----------------- + +See :ref:`machine.UART `. :: + + from machine import UART + + uart1 = UART(1, baudrate=9600, tx=33, rx=32) + uart1.write('hello') # write 5 bytes + uart1.read(5) # read up to 5 bytes + + +PWM (pulse width modulation) +---------------------------- + +How does PWM work on the RPi RP2xxx? + +Use the ``machine.PWM`` class:: + + from machine import Pin, PWM + + pwm0 = PWM(Pin(0)) # create PWM object from a pin + pwm0.freq() # get current frequency + pwm0.freq(1000) # set frequency + pwm0.duty_u16() # get current duty cycle, range 0-65535 + pwm0.duty_u16(200) # set duty cycle, range 0-65535 + pwm0.deinit() # turn off PWM on the pin + +ADC (analog to digital conversion) +---------------------------------- + +How does the ADC module work? + +Use the :ref:`machine.ADC ` class:: + + from machine import ADC + + adc = ADC(Pin(32)) # create ADC object on ADC pin + adc.read_u16() # read value, 0-65535 across voltage range 0.0v - 3.3v + +Software SPI bus +---------------- + +Software SPI (using bit-banging) works on all pins, and is accessed via the +:ref:`machine.SoftSPI ` class:: + + from machine import Pin, SoftSPI + + # construct a SoftSPI bus on the given pins + # polarity is the idle state of SCK + # phase=0 means sample on the first edge of SCK, phase=1 means the second + spi = SoftSPI(baudrate=100000, polarity=1, phase=0, sck=Pin(0), mosi=Pin(2), miso=Pin(4)) + + spi.init(baudrate=200000) # set the baudrate + + spi.read(10) # read 10 bytes on MISO + spi.read(10, 0xff) # read 10 bytes while outputting 0xff on MOSI + + buf = bytearray(50) # create a buffer + spi.readinto(buf) # read into the given buffer (reads 50 bytes in this case) + spi.readinto(buf, 0xff) # read into the given buffer and output 0xff on MOSI + + spi.write(b'12345') # write 5 bytes on MOSI + + buf = bytearray(4) # create a buffer + spi.write_readinto(b'1234', buf) # write to MOSI and read from MISO into the buffer + spi.write_readinto(buf, buf) # write buf to MOSI and read MISO back into buf + +.. Warning:: + Currently *all* of ``sck``, ``mosi`` and ``miso`` *must* be specified when + initialising Software SPI. + +Hardware SPI bus +---------------- + +Hardware SPI is accessed via the :ref:`machine.SPI ` class and +has the same methods as software SPI above:: + + from machine import Pin, SPI + + spi = SPI(1, 10000000) + spi = SPI(1, 10000000, sck=Pin(14), mosi=Pin(13), miso=Pin(12)) + spi = SPI(2, baudrate=80000000, polarity=0, phase=0, bits=8, firstbit=0, sck=Pin(18), mosi=Pin(23), miso=Pin(19)) + +Software I2C bus +---------------- + +Software I2C (using bit-banging) works on all output-capable pins, and is +accessed via the :ref:`machine.SoftI2C ` class:: + + from machine import Pin, SoftI2C + + i2c = SoftI2C(scl=Pin(5), sda=Pin(4), freq=100000) + + i2c.scan() # scan for devices + + i2c.readfrom(0x3a, 4) # read 4 bytes from device with address 0x3a + i2c.writeto(0x3a, '12') # write '12' to device with address 0x3a + + buf = bytearray(10) # create a buffer with 10 bytes + i2c.writeto(0x3a, buf) # write the given buffer to the slave + +Hardware I2C bus +---------------- + +The driver is accessed via the :ref:`machine.I2C ` class and +has the same methods as software I2C above:: + + from machine import Pin, I2C + + i2c = I2C(0) + i2c = I2C(1, scl=Pin(5), sda=Pin(4), freq=400000) + +Real time clock (RTC) +--------------------- + +See :ref:`machine.RTC ` :: + + from machine import RTC + + rtc = RTC() + rtc.datetime((2017, 8, 23, 1, 12, 48, 0, 0)) # set a specific date and time + rtc.datetime() # get date and time + +WDT (Watchdog timer) +-------------------- + +Is there a watchdog timer? + +See :ref:`machine.WDT `. :: + + from machine import WDT + + # enable the WDT with a timeout of 5s (1s is the minimum) + wdt = WDT(timeout=5000) + wdt.feed() + +Deep-sleep mode +--------------- + +Is there deep-sleep support for the rp2? + +The following code can be used to sleep, wake and check the reset cause:: + + import machine + + # check if the device woke from a deep sleep + if machine.reset_cause() == machine.DEEPSLEEP_RESET: + print('woke from a deep sleep') + + # put the device to sleep for 10 seconds + machine.deepsleep(10000) + +OneWire driver +-------------- + +The OneWire driver is implemented in software and works on all pins:: + + from machine import Pin + import onewire + + ow = onewire.OneWire(Pin(12)) # create a OneWire bus on GPIO12 + ow.scan() # return a list of devices on the bus + ow.reset() # reset the bus + ow.readbyte() # read a byte + ow.writebyte(0x12) # write a byte on the bus + ow.write('123') # write bytes on the bus + ow.select_rom(b'12345678') # select a specific device by its ROM code + +There is a specific driver for DS18S20 and DS18B20 devices:: + + import time, ds18x20 + ds = ds18x20.DS18X20(ow) + roms = ds.scan() + ds.convert_temp() + time.sleep_ms(750) + for rom in roms: + print(ds.read_temp(rom)) + +Be sure to put a 4.7k pull-up resistor on the data line. Note that +the ``convert_temp()`` method must be called each time you want to +sample the temperature. + +NeoPixel and APA106 driver +-------------------------- + +Use the ``neopixel`` and ``apa106`` modules:: + + from machine import Pin + from neopixel import NeoPixel + + pin = Pin(0, Pin.OUT) # set GPIO0 to output to drive NeoPixels + np = NeoPixel(pin, 8) # create NeoPixel driver on GPIO0 for 8 pixels + np[0] = (255, 255, 255) # set the first pixel to white + np.write() # write data to all pixels + r, g, b = np[0] # get first pixel colour + + +The APA106 driver extends NeoPixel, but internally uses a different colour order:: + + from apa106 import APA106 + ap = APA106(pin, 8) + r, g, b = ap[0] + +APA102 (DotStar) uses a different driver as it has an additional clock pin. diff --git a/docs/rp2/tutorial/intro.rst b/docs/rp2/tutorial/intro.rst new file mode 100644 index 0000000000..5609ab3798 --- /dev/null +++ b/docs/rp2/tutorial/intro.rst @@ -0,0 +1,6 @@ +.. _rp2_intro: + +Getting started with MicroPython on the RP2xxx +============================================== + +Let's get started! diff --git a/extmod/moduasyncio.c b/extmod/moduasyncio.c index d4b749f5ba..fcf86751a5 100644 --- a/extmod/moduasyncio.c +++ b/extmod/moduasyncio.c @@ -31,12 +31,19 @@ #if MICROPY_PY_UASYNCIO +#define TASK_STATE_RUNNING_NOT_WAITED_ON (mp_const_true) +#define TASK_STATE_DONE_NOT_WAITED_ON (mp_const_none) +#define TASK_STATE_DONE_WAS_WAITED_ON (mp_const_false) + +#define TASK_IS_DONE(task) ( \ + (task)->state == TASK_STATE_DONE_NOT_WAITED_ON \ + || (task)->state == TASK_STATE_DONE_WAS_WAITED_ON) + typedef struct _mp_obj_task_t { mp_pairheap_t pairheap; mp_obj_t coro; mp_obj_t data; - mp_obj_t waiting; - + mp_obj_t state; mp_obj_t ph_key; } mp_obj_task_t; @@ -146,9 +153,6 @@ STATIC const mp_obj_type_t task_queue_type = { /******************************************************************************/ // Task class -// For efficiency, the task object is stored to the coro entry when the task is done. -#define TASK_IS_DONE(task) ((task)->coro == MP_OBJ_FROM_PTR(task)) - // This is the core uasyncio context with cur_task, _task_queue and CancelledError. STATIC mp_obj_t uasyncio_context = MP_OBJ_NULL; @@ -159,7 +163,7 @@ STATIC mp_obj_t task_make_new(const mp_obj_type_t *type, size_t n_args, const mp mp_pairheap_init_node(task_lt, &self->pairheap); self->coro = args[0]; self->data = mp_const_none; - self->waiting = mp_const_none; + self->state = TASK_STATE_RUNNING_NOT_WAITED_ON; self->ph_key = MP_OBJ_NEW_SMALL_INT(0); if (n_args == 2) { uasyncio_context = args[1]; @@ -218,24 +222,6 @@ STATIC mp_obj_t task_cancel(mp_obj_t self_in) { } STATIC MP_DEFINE_CONST_FUN_OBJ_1(task_cancel_obj, task_cancel); -STATIC mp_obj_t task_throw(mp_obj_t self_in, mp_obj_t value_in) { - // This task raised an exception which was uncaught; handle that now. - mp_obj_task_t *self = MP_OBJ_TO_PTR(self_in); - // Set the data because it was cleared by the main scheduling loop. - self->data = value_in; - if (self->waiting == mp_const_none) { - // Nothing await'ed on the task so call the exception handler. - mp_obj_t _exc_context = mp_obj_dict_get(uasyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR__exc_context)); - mp_obj_dict_store(_exc_context, MP_OBJ_NEW_QSTR(MP_QSTR_exception), value_in); - mp_obj_dict_store(_exc_context, MP_OBJ_NEW_QSTR(MP_QSTR_future), self_in); - mp_obj_t Loop = mp_obj_dict_get(uasyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR_Loop)); - mp_obj_t call_exception_handler = mp_load_attr(Loop, MP_QSTR_call_exception_handler); - mp_call_function_1(call_exception_handler, _exc_context); - } - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_2(task_throw_obj, task_throw); - STATIC void task_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { mp_obj_task_t *self = MP_OBJ_TO_PTR(self_in); if (dest[0] == MP_OBJ_NULL) { @@ -244,32 +230,24 @@ STATIC void task_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { dest[0] = self->coro; } else if (attr == MP_QSTR_data) { dest[0] = self->data; - } else if (attr == MP_QSTR_waiting) { - if (self->waiting != mp_const_none && self->waiting != mp_const_false) { - dest[0] = self->waiting; - } + } else if (attr == MP_QSTR_state) { + dest[0] = self->state; } else if (attr == MP_QSTR_done) { dest[0] = MP_OBJ_FROM_PTR(&task_done_obj); dest[1] = self_in; } else if (attr == MP_QSTR_cancel) { dest[0] = MP_OBJ_FROM_PTR(&task_cancel_obj); dest[1] = self_in; - } else if (attr == MP_QSTR_throw) { - dest[0] = MP_OBJ_FROM_PTR(&task_throw_obj); - dest[1] = self_in; } else if (attr == MP_QSTR_ph_key) { dest[0] = self->ph_key; } } else if (dest[1] != MP_OBJ_NULL) { // Store - if (attr == MP_QSTR_coro) { - self->coro = dest[1]; - dest[0] = MP_OBJ_NULL; - } else if (attr == MP_QSTR_data) { + if (attr == MP_QSTR_data) { self->data = dest[1]; dest[0] = MP_OBJ_NULL; - } else if (attr == MP_QSTR_waiting) { - self->waiting = dest[1]; + } else if (attr == MP_QSTR_state) { + self->state = dest[1]; dest[0] = MP_OBJ_NULL; } } @@ -278,15 +256,12 @@ STATIC void task_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { STATIC mp_obj_t task_getiter(mp_obj_t self_in, mp_obj_iter_buf_t *iter_buf) { (void)iter_buf; mp_obj_task_t *self = MP_OBJ_TO_PTR(self_in); - if (self->waiting == mp_const_none) { - // The is the first access of the "waiting" entry. - if (TASK_IS_DONE(self)) { - // Signal that the completed-task has been await'ed on. - self->waiting = mp_const_false; - } else { - // Lazily allocate the waiting queue. - self->waiting = task_queue_make_new(&task_queue_type, 0, 0, NULL); - } + if (TASK_IS_DONE(self)) { + // Signal that the completed-task has been await'ed on. + self->state = TASK_STATE_DONE_WAS_WAITED_ON; + } else if (self->state == TASK_STATE_RUNNING_NOT_WAITED_ON) { + // Allocate the waiting queue. + self->state = task_queue_make_new(&task_queue_type, 0, 0, NULL); } return self_in; } @@ -299,7 +274,7 @@ STATIC mp_obj_t task_iternext(mp_obj_t self_in) { } else { // Put calling task on waiting queue. mp_obj_t cur_task = mp_obj_dict_get(uasyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR_cur_task)); - mp_obj_t args[2] = { self->waiting, cur_task }; + mp_obj_t args[2] = { self->state, cur_task }; task_queue_push_sorted(2, args); // Set calling task's data to this task that it waits on, to double-link it. ((mp_obj_task_t *)MP_OBJ_TO_PTR(cur_task))->data = self_in; diff --git a/extmod/moductypes.c b/extmod/moductypes.c index f78b892464..3de3a080af 100644 --- a/extmod/moductypes.c +++ b/extmod/moductypes.c @@ -15,38 +15,10 @@ #if MICROPY_PY_UCTYPES -/// \module uctypes - Access data structures in memory -/// -/// The module allows to define layout of raw data structure (using terms -/// of C language), and then access memory buffers using this definition. -/// The module also provides convenience functions to access memory buffers -/// contained in Python objects or wrap memory buffers in Python objects. -/// \constant UINT8_1 - uint8_t value type - -/// \class struct - C-like structure -/// -/// Encapsulalation of in-memory data structure. This class doesn't define -/// any methods, only attribute access (for structure fields) and -/// indexing (for pointer and array fields). -/// -/// Usage: -/// -/// # Define layout of a structure with 2 fields -/// # 0 and 4 are byte offsets of fields from the beginning of struct -/// # they are logically ORed with field type -/// FOO_STRUCT = {"a": 0 | uctypes.UINT32, "b": 4 | uctypes.UINT8} -/// -/// # Example memory buffer to access (contained in bytes object) -/// buf = b"\x64\0\0\0\0x14" -/// -/// # Create structure object referring to address of -/// # the data in the buffer above -/// s = uctypes.struct(FOO_STRUCT, uctypes.addressof(buf)) -/// -/// # Access fields -/// print(s.a, s.b) -/// # Result: -/// # 100, 20 +// The uctypes module allows defining the layout of a raw data structure (using +// terms of the C language), and then access memory buffers using this definition. +// The module also provides convenience functions to access memory buffers +// contained in Python objects or wrap memory buffers in Python objects. #define LAYOUT_LITTLE_ENDIAN (0) #define LAYOUT_BIG_ENDIAN (1) @@ -56,6 +28,7 @@ #define BITF_LEN_BITS 5 #define BITF_OFF_BITS 5 #define OFFSET_BITS 17 +#define LEN_BITS (OFFSET_BITS + BITF_OFF_BITS) #if VAL_TYPE_BITS + BITF_LEN_BITS + BITF_OFF_BITS + OFFSET_BITS != 31 #error Invalid encoding field length #endif @@ -172,7 +145,7 @@ STATIC mp_uint_t uctypes_struct_agg_size(mp_obj_tuple_t *t, int layout_type, mp_ mp_uint_t item_s; if (t->len == 2) { // Elements of array are scalar - item_s = GET_SCALAR_SIZE(val_type); + item_s = uctypes_struct_scalar_size(val_type); if (item_s > *max_field_size) { *max_field_size = item_s; } @@ -400,10 +373,8 @@ STATIC mp_obj_t uctypes_struct_attr_op(mp_obj_t self_in, qstr attr, mp_obj_t set mp_int_t offset = MP_OBJ_SMALL_INT_VALUE(deref); mp_uint_t val_type = GET_TYPE(offset, VAL_TYPE_BITS); offset &= VALUE_MASK(VAL_TYPE_BITS); -// printf("scalar type=%d offset=%x\n", val_type, offset); if (val_type <= INT64 || val_type == FLOAT32 || val_type == FLOAT64) { -// printf("size=%d\n", GET_SCALAR_SIZE(val_type)); if (self->flags == LAYOUT_NATIVE) { if (set_val == MP_OBJ_NULL) { return get_aligned(val_type, self->addr + offset, 0); @@ -420,9 +391,9 @@ STATIC mp_obj_t uctypes_struct_attr_op(mp_obj_t self_in, qstr attr, mp_obj_t set } } } else if (val_type >= BFUINT8 && val_type <= BFINT32) { - uint bit_offset = (offset >> 17) & 31; - uint bit_len = (offset >> 22) & 31; - offset &= (1 << 17) - 1; + uint bit_offset = (offset >> OFFSET_BITS) & 31; + uint bit_len = (offset >> LEN_BITS) & 31; + offset &= (1 << OFFSET_BITS) - 1; mp_uint_t val; if (self->flags == LAYOUT_NATIVE) { val = get_aligned_basic(val_type & 6, self->addr + offset); @@ -470,7 +441,6 @@ STATIC mp_obj_t uctypes_struct_attr_op(mp_obj_t self_in, qstr attr, mp_obj_t set mp_int_t offset = MP_OBJ_SMALL_INT_VALUE(sub->items[0]); mp_uint_t agg_type = GET_TYPE(offset, AGG_TYPE_BITS); offset &= VALUE_MASK(AGG_TYPE_BITS); -// printf("agg type=%d offset=%x\n", agg_type, offset); switch (agg_type) { case STRUCT: { @@ -495,7 +465,6 @@ STATIC mp_obj_t uctypes_struct_attr_op(mp_obj_t self_in, qstr attr, mp_obj_t set o->desc = MP_OBJ_FROM_PTR(sub); o->addr = self->addr + offset; o->flags = self->flags; -// printf("PTR/ARR base addr=%p\n", o->addr); return MP_OBJ_FROM_PTR(o); } } @@ -553,7 +522,7 @@ STATIC mp_obj_t uctypes_struct_subscr(mp_obj_t base_in, mp_obj_t index_in, mp_ob return value; // just !MP_OBJ_NULL } } else { - byte *p = self->addr + GET_SCALAR_SIZE(val_type) * index; + byte *p = self->addr + uctypes_struct_scalar_size(val_type) * index; if (value == MP_OBJ_SENTINEL) { return get_unaligned(val_type, p, self->flags); } else { @@ -628,9 +597,8 @@ STATIC mp_int_t uctypes_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, return 0; } -/// \function addressof() -/// Return address of object's data (applies to object providing buffer -/// interface). +// addressof() +// Return address of object's data (applies to objects providing the buffer interface). STATIC mp_obj_t uctypes_struct_addressof(mp_obj_t buf) { mp_buffer_info_t bufinfo; mp_get_buffer_raise(buf, &bufinfo, MP_BUFFER_READ); @@ -638,25 +606,20 @@ STATIC mp_obj_t uctypes_struct_addressof(mp_obj_t buf) { } MP_DEFINE_CONST_FUN_OBJ_1(uctypes_struct_addressof_obj, uctypes_struct_addressof); -/// \function bytearray_at() -/// Capture memory at given address of given size as bytearray. Memory is -/// captured by reference (and thus memory pointed by bytearray may change -/// or become invalid at later time). Use bytes_at() to capture by value. +// bytearray_at() +// Capture memory at given address of given size as bytearray. STATIC mp_obj_t uctypes_struct_bytearray_at(mp_obj_t ptr, mp_obj_t size) { return mp_obj_new_bytearray_by_ref(mp_obj_int_get_truncated(size), (void *)(uintptr_t)mp_obj_int_get_truncated(ptr)); } MP_DEFINE_CONST_FUN_OBJ_2(uctypes_struct_bytearray_at_obj, uctypes_struct_bytearray_at); -/// \function bytes_at() -/// Capture memory at given address of given size as bytes. Memory is -/// captured by value, i.e. copied. Use bytearray_at() to capture by reference -/// ("zero copy"). +// bytes_at() +// Capture memory at given address of given size as bytes. STATIC mp_obj_t uctypes_struct_bytes_at(mp_obj_t ptr, mp_obj_t size) { return mp_obj_new_bytes((void *)(uintptr_t)mp_obj_int_get_truncated(ptr), mp_obj_int_get_truncated(size)); } MP_DEFINE_CONST_FUN_OBJ_2(uctypes_struct_bytes_at_obj, uctypes_struct_bytes_at); - STATIC const mp_obj_type_t uctypes_struct_type = { { &mp_type_type }, .name = MP_QSTR_struct, @@ -676,81 +639,63 @@ STATIC const mp_rom_map_elem_t mp_module_uctypes_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_bytes_at), MP_ROM_PTR(&uctypes_struct_bytes_at_obj) }, { MP_ROM_QSTR(MP_QSTR_bytearray_at), MP_ROM_PTR(&uctypes_struct_bytearray_at_obj) }, - /// \moduleref uctypes - - /// \constant NATIVE - Native structure layout - native endianness, - /// platform-specific field alignment { MP_ROM_QSTR(MP_QSTR_NATIVE), MP_ROM_INT(LAYOUT_NATIVE) }, - /// \constant LITTLE_ENDIAN - Little-endian structure layout, tightly packed - /// (no alignment constraints) { MP_ROM_QSTR(MP_QSTR_LITTLE_ENDIAN), MP_ROM_INT(LAYOUT_LITTLE_ENDIAN) }, - /// \constant BIG_ENDIAN - Big-endian structure layout, tightly packed - /// (no alignment constraints) { MP_ROM_QSTR(MP_QSTR_BIG_ENDIAN), MP_ROM_INT(LAYOUT_BIG_ENDIAN) }, - /// \constant VOID - void value type, may be used only as pointer target type. { MP_ROM_QSTR(MP_QSTR_VOID), MP_ROM_INT(TYPE2SMALLINT(UINT8, VAL_TYPE_BITS)) }, - /// \constant UINT8 - uint8_t value type - { MP_ROM_QSTR(MP_QSTR_UINT8), MP_ROM_INT(TYPE2SMALLINT(UINT8, 4)) }, - /// \constant INT8 - int8_t value type - { MP_ROM_QSTR(MP_QSTR_INT8), MP_ROM_INT(TYPE2SMALLINT(INT8, 4)) }, - /// \constant UINT16 - uint16_t value type - { MP_ROM_QSTR(MP_QSTR_UINT16), MP_ROM_INT(TYPE2SMALLINT(UINT16, 4)) }, - /// \constant INT16 - int16_t value type - { MP_ROM_QSTR(MP_QSTR_INT16), MP_ROM_INT(TYPE2SMALLINT(INT16, 4)) }, - /// \constant UINT32 - uint32_t value type - { MP_ROM_QSTR(MP_QSTR_UINT32), MP_ROM_INT(TYPE2SMALLINT(UINT32, 4)) }, - /// \constant INT32 - int32_t value type - { MP_ROM_QSTR(MP_QSTR_INT32), MP_ROM_INT(TYPE2SMALLINT(INT32, 4)) }, - /// \constant UINT64 - uint64_t value type - { MP_ROM_QSTR(MP_QSTR_UINT64), MP_ROM_INT(TYPE2SMALLINT(UINT64, 4)) }, - /// \constant INT64 - int64_t value type - { MP_ROM_QSTR(MP_QSTR_INT64), MP_ROM_INT(TYPE2SMALLINT(INT64, 4)) }, + { MP_ROM_QSTR(MP_QSTR_UINT8), MP_ROM_INT(TYPE2SMALLINT(UINT8, VAL_TYPE_BITS)) }, + { MP_ROM_QSTR(MP_QSTR_INT8), MP_ROM_INT(TYPE2SMALLINT(INT8, VAL_TYPE_BITS)) }, + { MP_ROM_QSTR(MP_QSTR_UINT16), MP_ROM_INT(TYPE2SMALLINT(UINT16, VAL_TYPE_BITS)) }, + { MP_ROM_QSTR(MP_QSTR_INT16), MP_ROM_INT(TYPE2SMALLINT(INT16, VAL_TYPE_BITS)) }, + { MP_ROM_QSTR(MP_QSTR_UINT32), MP_ROM_INT(TYPE2SMALLINT(UINT32, VAL_TYPE_BITS)) }, + { MP_ROM_QSTR(MP_QSTR_INT32), MP_ROM_INT(TYPE2SMALLINT(INT32, VAL_TYPE_BITS)) }, + { MP_ROM_QSTR(MP_QSTR_UINT64), MP_ROM_INT(TYPE2SMALLINT(UINT64, VAL_TYPE_BITS)) }, + { MP_ROM_QSTR(MP_QSTR_INT64), MP_ROM_INT(TYPE2SMALLINT(INT64, VAL_TYPE_BITS)) }, - { MP_ROM_QSTR(MP_QSTR_BFUINT8), MP_ROM_INT(TYPE2SMALLINT(BFUINT8, 4)) }, - { MP_ROM_QSTR(MP_QSTR_BFINT8), MP_ROM_INT(TYPE2SMALLINT(BFINT8, 4)) }, - { MP_ROM_QSTR(MP_QSTR_BFUINT16), MP_ROM_INT(TYPE2SMALLINT(BFUINT16, 4)) }, - { MP_ROM_QSTR(MP_QSTR_BFINT16), MP_ROM_INT(TYPE2SMALLINT(BFINT16, 4)) }, - { MP_ROM_QSTR(MP_QSTR_BFUINT32), MP_ROM_INT(TYPE2SMALLINT(BFUINT32, 4)) }, - { MP_ROM_QSTR(MP_QSTR_BFINT32), MP_ROM_INT(TYPE2SMALLINT(BFINT32, 4)) }, + { MP_ROM_QSTR(MP_QSTR_BFUINT8), MP_ROM_INT(TYPE2SMALLINT(BFUINT8, VAL_TYPE_BITS)) }, + { MP_ROM_QSTR(MP_QSTR_BFINT8), MP_ROM_INT(TYPE2SMALLINT(BFINT8, VAL_TYPE_BITS)) }, + { MP_ROM_QSTR(MP_QSTR_BFUINT16), MP_ROM_INT(TYPE2SMALLINT(BFUINT16, VAL_TYPE_BITS)) }, + { MP_ROM_QSTR(MP_QSTR_BFINT16), MP_ROM_INT(TYPE2SMALLINT(BFINT16, VAL_TYPE_BITS)) }, + { MP_ROM_QSTR(MP_QSTR_BFUINT32), MP_ROM_INT(TYPE2SMALLINT(BFUINT32, VAL_TYPE_BITS)) }, + { MP_ROM_QSTR(MP_QSTR_BFINT32), MP_ROM_INT(TYPE2SMALLINT(BFINT32, VAL_TYPE_BITS)) }, - { MP_ROM_QSTR(MP_QSTR_BF_POS), MP_ROM_INT(17) }, - { MP_ROM_QSTR(MP_QSTR_BF_LEN), MP_ROM_INT(22) }, + { MP_ROM_QSTR(MP_QSTR_BF_POS), MP_ROM_INT(OFFSET_BITS) }, + { MP_ROM_QSTR(MP_QSTR_BF_LEN), MP_ROM_INT(LEN_BITS) }, #if MICROPY_PY_BUILTINS_FLOAT - { MP_ROM_QSTR(MP_QSTR_FLOAT32), MP_ROM_INT(TYPE2SMALLINT(FLOAT32, 4)) }, - { MP_ROM_QSTR(MP_QSTR_FLOAT64), MP_ROM_INT(TYPE2SMALLINT(FLOAT64, 4)) }, + { MP_ROM_QSTR(MP_QSTR_FLOAT32), MP_ROM_INT(TYPE2SMALLINT(FLOAT32, VAL_TYPE_BITS)) }, + { MP_ROM_QSTR(MP_QSTR_FLOAT64), MP_ROM_INT(TYPE2SMALLINT(FLOAT64, VAL_TYPE_BITS)) }, #endif #if MICROPY_PY_UCTYPES_NATIVE_C_TYPES // C native type aliases. These depend on GCC-compatible predefined // preprocessor macros. #if __SIZEOF_SHORT__ == 2 - { MP_ROM_QSTR(MP_QSTR_SHORT), MP_ROM_INT(TYPE2SMALLINT(INT16, 4)) }, - { MP_ROM_QSTR(MP_QSTR_USHORT), MP_ROM_INT(TYPE2SMALLINT(UINT16, 4)) }, + { MP_ROM_QSTR(MP_QSTR_SHORT), MP_ROM_INT(TYPE2SMALLINT(INT16, VAL_TYPE_BITS)) }, + { MP_ROM_QSTR(MP_QSTR_USHORT), MP_ROM_INT(TYPE2SMALLINT(UINT16, VAL_TYPE_BITS)) }, #endif #if __SIZEOF_INT__ == 4 - { MP_ROM_QSTR(MP_QSTR_INT), MP_ROM_INT(TYPE2SMALLINT(INT32, 4)) }, - { MP_ROM_QSTR(MP_QSTR_UINT), MP_ROM_INT(TYPE2SMALLINT(UINT32, 4)) }, + { MP_ROM_QSTR(MP_QSTR_INT), MP_ROM_INT(TYPE2SMALLINT(INT32, VAL_TYPE_BITS)) }, + { MP_ROM_QSTR(MP_QSTR_UINT), MP_ROM_INT(TYPE2SMALLINT(UINT32, VAL_TYPE_BITS)) }, #endif #if __SIZEOF_LONG__ == 4 - { MP_ROM_QSTR(MP_QSTR_LONG), MP_ROM_INT(TYPE2SMALLINT(INT32, 4)) }, - { MP_ROM_QSTR(MP_QSTR_ULONG), MP_ROM_INT(TYPE2SMALLINT(UINT32, 4)) }, + { MP_ROM_QSTR(MP_QSTR_LONG), MP_ROM_INT(TYPE2SMALLINT(INT32, VAL_TYPE_BITS)) }, + { MP_ROM_QSTR(MP_QSTR_ULONG), MP_ROM_INT(TYPE2SMALLINT(UINT32, VAL_TYPE_BITS)) }, #elif __SIZEOF_LONG__ == 8 - { MP_ROM_QSTR(MP_QSTR_LONG), MP_ROM_INT(TYPE2SMALLINT(INT64, 4)) }, - { MP_ROM_QSTR(MP_QSTR_ULONG), MP_ROM_INT(TYPE2SMALLINT(UINT64, 4)) }, + { MP_ROM_QSTR(MP_QSTR_LONG), MP_ROM_INT(TYPE2SMALLINT(INT64, VAL_TYPE_BITS)) }, + { MP_ROM_QSTR(MP_QSTR_ULONG), MP_ROM_INT(TYPE2SMALLINT(UINT64, VAL_TYPE_BITS)) }, #endif #if __SIZEOF_LONG_LONG__ == 8 - { MP_ROM_QSTR(MP_QSTR_LONGLONG), MP_ROM_INT(TYPE2SMALLINT(INT64, 4)) }, - { MP_ROM_QSTR(MP_QSTR_ULONGLONG), MP_ROM_INT(TYPE2SMALLINT(UINT64, 4)) }, + { MP_ROM_QSTR(MP_QSTR_LONGLONG), MP_ROM_INT(TYPE2SMALLINT(INT64, VAL_TYPE_BITS)) }, + { MP_ROM_QSTR(MP_QSTR_ULONGLONG), MP_ROM_INT(TYPE2SMALLINT(UINT64, VAL_TYPE_BITS)) }, #endif #endif // MICROPY_PY_UCTYPES_NATIVE_C_TYPES { MP_ROM_QSTR(MP_QSTR_PTR), MP_ROM_INT(TYPE2SMALLINT(PTR, AGG_TYPE_BITS)) }, { MP_ROM_QSTR(MP_QSTR_ARRAY), MP_ROM_INT(TYPE2SMALLINT(ARRAY, AGG_TYPE_BITS)) }, }; - STATIC MP_DEFINE_CONST_DICT(mp_module_uctypes_globals, mp_module_uctypes_globals_table); const mp_obj_module_t mp_module_uctypes = { diff --git a/extmod/moduhashlib.c b/extmod/moduhashlib.c index 96b64f1718..0dfd2c790a 100644 --- a/extmod/moduhashlib.c +++ b/extmod/moduhashlib.c @@ -42,9 +42,16 @@ typedef struct _mp_obj_hash_t { mp_obj_base_t base; - char state[0]; + bool final; // if set, update and digest raise an exception + uintptr_t state[0]; // must be aligned to a machine word } mp_obj_hash_t; +static void uhashlib_ensure_not_final(mp_obj_hash_t *self) { + if (self->final) { + mp_raise_ValueError(MP_ERROR_TEXT("hash is final")); + } +} + #if MICROPY_PY_UHASHLIB_SHA256 STATIC mp_obj_t uhashlib_sha256_update(mp_obj_t self_in, mp_obj_t arg); @@ -60,6 +67,7 @@ STATIC mp_obj_t uhashlib_sha256_make_new(const mp_obj_type_t *type, size_t n_arg mp_arg_check_num(n_args, n_kw, 0, 1, false); mp_obj_hash_t *o = m_new_obj_var(mp_obj_hash_t, char, sizeof(mbedtls_sha256_context)); o->base.type = type; + o->final = false; mbedtls_sha256_init((mbedtls_sha256_context *)&o->state); mbedtls_sha256_starts_ret((mbedtls_sha256_context *)&o->state, 0); if (n_args == 1) { @@ -70,6 +78,7 @@ STATIC mp_obj_t uhashlib_sha256_make_new(const mp_obj_type_t *type, size_t n_arg STATIC mp_obj_t uhashlib_sha256_update(mp_obj_t self_in, mp_obj_t arg) { mp_obj_hash_t *self = MP_OBJ_TO_PTR(self_in); + uhashlib_ensure_not_final(self); mp_buffer_info_t bufinfo; mp_get_buffer_raise(arg, &bufinfo, MP_BUFFER_READ); mbedtls_sha256_update_ret((mbedtls_sha256_context *)&self->state, bufinfo.buf, bufinfo.len); @@ -78,6 +87,8 @@ STATIC mp_obj_t uhashlib_sha256_update(mp_obj_t self_in, mp_obj_t arg) { STATIC mp_obj_t uhashlib_sha256_digest(mp_obj_t self_in) { mp_obj_hash_t *self = MP_OBJ_TO_PTR(self_in); + uhashlib_ensure_not_final(self); + self->final = true; vstr_t vstr; vstr_init_len(&vstr, 32); mbedtls_sha256_finish_ret((mbedtls_sha256_context *)&self->state, (unsigned char *)vstr.buf); @@ -102,6 +113,7 @@ STATIC mp_obj_t uhashlib_sha256_make_new(const mp_obj_type_t *type, size_t n_arg mp_arg_check_num(n_args, kw_args, 0, 1, false); mp_obj_hash_t *o = m_new_obj_var(mp_obj_hash_t, char, sizeof(CRYAL_SHA256_CTX)); o->base.type = type; + o->final = false; sha256_init((CRYAL_SHA256_CTX *)o->state); if (n_args == 1) { uhashlib_sha256_update(MP_OBJ_FROM_PTR(o), args[0]); @@ -112,6 +124,7 @@ STATIC mp_obj_t uhashlib_sha256_make_new(const mp_obj_type_t *type, size_t n_arg STATIC mp_obj_t uhashlib_sha256_update(mp_obj_t self_in, mp_obj_t arg) { check_not_unicode(arg); mp_obj_hash_t *self = MP_OBJ_TO_PTR(self_in); + uhashlib_ensure_not_final(self); mp_buffer_info_t bufinfo; mp_get_buffer_raise(arg, &bufinfo, MP_BUFFER_READ); sha256_update((CRYAL_SHA256_CTX *)self->state, bufinfo.buf, bufinfo.len); @@ -120,6 +133,8 @@ STATIC mp_obj_t uhashlib_sha256_update(mp_obj_t self_in, mp_obj_t arg) { STATIC mp_obj_t uhashlib_sha256_digest(mp_obj_t self_in) { mp_obj_hash_t *self = MP_OBJ_TO_PTR(self_in); + uhashlib_ensure_not_final(self); + self->final = true; vstr_t vstr; vstr_init_len(&vstr, SHA256_BLOCK_SIZE); sha256_final((CRYAL_SHA256_CTX *)self->state, (byte *)vstr.buf); @@ -153,6 +168,7 @@ STATIC mp_obj_t uhashlib_sha1_make_new(const mp_obj_type_t *type, size_t n_args, mp_arg_check_num(n_args, kw_args, 0, 1, false); mp_obj_hash_t *o = m_new_obj_var(mp_obj_hash_t, char, sizeof(SHA1_CTX)); o->base.type = type; + o->final = false; SHA1_Init((SHA1_CTX *)o->state); if (n_args == 1) { uhashlib_sha1_update(MP_OBJ_FROM_PTR(o), args[0]); @@ -163,6 +179,7 @@ STATIC mp_obj_t uhashlib_sha1_make_new(const mp_obj_type_t *type, size_t n_args, STATIC mp_obj_t uhashlib_sha1_update(mp_obj_t self_in, mp_obj_t arg) { check_not_unicode(arg); mp_obj_hash_t *self = MP_OBJ_TO_PTR(self_in); + uhashlib_ensure_not_final(self); mp_buffer_info_t bufinfo; mp_get_buffer_raise(arg, &bufinfo, MP_BUFFER_READ); SHA1_Update((SHA1_CTX *)self->state, bufinfo.buf, bufinfo.len); @@ -171,6 +188,8 @@ STATIC mp_obj_t uhashlib_sha1_update(mp_obj_t self_in, mp_obj_t arg) { STATIC mp_obj_t uhashlib_sha1_digest(mp_obj_t self_in) { mp_obj_hash_t *self = MP_OBJ_TO_PTR(self_in); + uhashlib_ensure_not_final(self); + self->final = true; vstr_t vstr; vstr_init_len(&vstr, SHA1_SIZE); SHA1_Final((byte *)vstr.buf, (SHA1_CTX *)self->state); @@ -190,6 +209,7 @@ STATIC mp_obj_t uhashlib_sha1_make_new(const mp_obj_type_t *type, size_t n_args, mp_arg_check_num(n_args, n_kw, 0, 1, false); mp_obj_hash_t *o = m_new_obj_var(mp_obj_hash_t, char, sizeof(mbedtls_sha1_context)); o->base.type = type; + o->final = false; mbedtls_sha1_init((mbedtls_sha1_context *)o->state); mbedtls_sha1_starts_ret((mbedtls_sha1_context *)o->state); if (n_args == 1) { @@ -200,6 +220,7 @@ STATIC mp_obj_t uhashlib_sha1_make_new(const mp_obj_type_t *type, size_t n_args, STATIC mp_obj_t uhashlib_sha1_update(mp_obj_t self_in, mp_obj_t arg) { mp_obj_hash_t *self = MP_OBJ_TO_PTR(self_in); + uhashlib_ensure_not_final(self); mp_buffer_info_t bufinfo; mp_get_buffer_raise(arg, &bufinfo, MP_BUFFER_READ); mbedtls_sha1_update_ret((mbedtls_sha1_context *)self->state, bufinfo.buf, bufinfo.len); @@ -208,6 +229,8 @@ STATIC mp_obj_t uhashlib_sha1_update(mp_obj_t self_in, mp_obj_t arg) { STATIC mp_obj_t uhashlib_sha1_digest(mp_obj_t self_in) { mp_obj_hash_t *self = MP_OBJ_TO_PTR(self_in); + uhashlib_ensure_not_final(self); + self->final = true; vstr_t vstr; vstr_init_len(&vstr, 20); mbedtls_sha1_finish_ret((mbedtls_sha1_context *)self->state, (byte *)vstr.buf); @@ -241,6 +264,7 @@ STATIC mp_obj_t uhashlib_md5_make_new(const mp_obj_type_t *type, size_t n_args, mp_arg_check_num(n_args, n_kw, 0, 1, false); mp_obj_hash_t *o = m_new_obj_var(mp_obj_hash_t, char, sizeof(MD5_CTX)); o->base.type = type; + o->final = false; MD5_Init((MD5_CTX *)o->state); if (n_args == 1) { uhashlib_md5_update(MP_OBJ_FROM_PTR(o), args[0]); @@ -250,6 +274,7 @@ STATIC mp_obj_t uhashlib_md5_make_new(const mp_obj_type_t *type, size_t n_args, STATIC mp_obj_t uhashlib_md5_update(mp_obj_t self_in, mp_obj_t arg) { mp_obj_hash_t *self = MP_OBJ_TO_PTR(self_in); + uhashlib_ensure_not_final(self); mp_buffer_info_t bufinfo; mp_get_buffer_raise(arg, &bufinfo, MP_BUFFER_READ); MD5_Update((MD5_CTX *)self->state, bufinfo.buf, bufinfo.len); @@ -258,6 +283,8 @@ STATIC mp_obj_t uhashlib_md5_update(mp_obj_t self_in, mp_obj_t arg) { STATIC mp_obj_t uhashlib_md5_digest(mp_obj_t self_in) { mp_obj_hash_t *self = MP_OBJ_TO_PTR(self_in); + uhashlib_ensure_not_final(self); + self->final = true; vstr_t vstr; vstr_init_len(&vstr, MD5_SIZE); MD5_Final((byte *)vstr.buf, (MD5_CTX *)self->state); @@ -277,6 +304,7 @@ STATIC mp_obj_t uhashlib_md5_make_new(const mp_obj_type_t *type, size_t n_args, mp_arg_check_num(n_args, n_kw, 0, 1, false); mp_obj_hash_t *o = m_new_obj_var(mp_obj_hash_t, char, sizeof(mbedtls_md5_context)); o->base.type = type; + o->final = false; mbedtls_md5_init((mbedtls_md5_context *)o->state); mbedtls_md5_starts_ret((mbedtls_md5_context *)o->state); if (n_args == 1) { @@ -287,6 +315,7 @@ STATIC mp_obj_t uhashlib_md5_make_new(const mp_obj_type_t *type, size_t n_args, STATIC mp_obj_t uhashlib_md5_update(mp_obj_t self_in, mp_obj_t arg) { mp_obj_hash_t *self = MP_OBJ_TO_PTR(self_in); + uhashlib_ensure_not_final(self); mp_buffer_info_t bufinfo; mp_get_buffer_raise(arg, &bufinfo, MP_BUFFER_READ); mbedtls_md5_update_ret((mbedtls_md5_context *)self->state, bufinfo.buf, bufinfo.len); @@ -295,6 +324,8 @@ STATIC mp_obj_t uhashlib_md5_update(mp_obj_t self_in, mp_obj_t arg) { STATIC mp_obj_t uhashlib_md5_digest(mp_obj_t self_in) { mp_obj_hash_t *self = MP_OBJ_TO_PTR(self_in); + uhashlib_ensure_not_final(self); + self->final = true; vstr_t vstr; vstr_init_len(&vstr, 16); mbedtls_md5_finish_ret((mbedtls_md5_context *)self->state, (byte *)vstr.buf); diff --git a/extmod/modurandom.c b/extmod/modurandom.c index b33d0eb23f..e304d0b728 100644 --- a/extmod/modurandom.c +++ b/extmod/modurandom.c @@ -66,8 +66,11 @@ STATIC uint32_t yasmarang_randbelow(uint32_t n) { STATIC mp_obj_t mod_urandom_getrandbits(mp_obj_t num_in) { int n = mp_obj_get_int(num_in); - if (n > 32 || n == 0) { - mp_raise_ValueError(NULL); + if (n > 32 || n < 0) { + mp_raise_ValueError(MP_ERROR_TEXT("bits must be 32 or less")); + } + if (n == 0) { + return MP_OBJ_NEW_SMALL_INT(0); } uint32_t mask = ~0; // Beware of C undefined behavior when shifting by >= than bit size diff --git a/extmod/moduselect.c b/extmod/moduselect.c index e14fd73536..3c66d69e32 100644 --- a/extmod/moduselect.c +++ b/extmod/moduselect.c @@ -20,10 +20,6 @@ // Flags for poll() #define FLAG_ONESHOT (1) -/// \module select - Provides select function to wait for events on a stream -/// -/// This module provides the select function. - typedef struct _poll_obj_t { mp_obj_t obj; mp_uint_t (*ioctl)(mp_obj_t obj, mp_uint_t request, uintptr_t arg, int *errcode); @@ -91,7 +87,7 @@ STATIC mp_uint_t poll_map_poll(mp_map_t *poll_map, size_t *rwx_num) { return n_ready; } -/// \function select(rlist, wlist, xlist[, timeout]) +// select(rlist, wlist, xlist[, timeout]) STATIC mp_obj_t select_select(size_t n_args, const mp_obj_t *args) { // get array data from tuple/list arguments size_t rwx_len[3]; @@ -158,8 +154,6 @@ STATIC mp_obj_t select_select(size_t n_args, const mp_obj_t *args) { } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_select_select_obj, 3, 4, select_select); -/// \class Poll - poll class - typedef struct _mp_obj_poll_t { mp_obj_base_t base; mp_map_t poll_map; @@ -170,7 +164,7 @@ typedef struct _mp_obj_poll_t { mp_obj_t ret_tuple; } mp_obj_poll_t; -/// \method register(obj[, eventmask]) +// register(obj[, eventmask]) STATIC mp_obj_t poll_register(size_t n_args, const mp_obj_t *args) { mp_obj_poll_t *self = MP_OBJ_TO_PTR(args[0]); mp_uint_t flags; @@ -184,7 +178,7 @@ STATIC mp_obj_t poll_register(size_t n_args, const mp_obj_t *args) { } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(poll_register_obj, 2, 3, poll_register); -/// \method unregister(obj) +// unregister(obj) STATIC mp_obj_t poll_unregister(mp_obj_t self_in, mp_obj_t obj_in) { mp_obj_poll_t *self = MP_OBJ_TO_PTR(self_in); mp_map_lookup(&self->poll_map, mp_obj_id(obj_in), MP_MAP_LOOKUP_REMOVE_IF_FOUND); @@ -193,7 +187,7 @@ STATIC mp_obj_t poll_unregister(mp_obj_t self_in, mp_obj_t obj_in) { } MP_DEFINE_CONST_FUN_OBJ_2(poll_unregister_obj, poll_unregister); -/// \method modify(obj, eventmask) +// modify(obj, eventmask) STATIC mp_obj_t poll_modify(mp_obj_t self_in, mp_obj_t obj_in, mp_obj_t eventmask_in) { mp_obj_poll_t *self = MP_OBJ_TO_PTR(self_in); mp_map_elem_t *elem = mp_map_lookup(&self->poll_map, mp_obj_id(obj_in), MP_MAP_LOOKUP); @@ -328,7 +322,7 @@ STATIC const mp_obj_type_t mp_type_poll = { .locals_dict = (void *)&poll_locals_dict, }; -/// \function poll() +// poll() STATIC mp_obj_t select_poll(void) { mp_obj_poll_t *poll = m_new_obj(mp_obj_poll_t); poll->base.type = &mp_type_poll; diff --git a/extmod/uasyncio/core.py b/extmod/uasyncio/core.py index d74763f6a6..12833cf0cd 100644 --- a/extmod/uasyncio/core.py +++ b/extmod/uasyncio/core.py @@ -175,6 +175,10 @@ def run_until_complete(main_task=None): if not exc: t.coro.send(None) else: + # If the task is finished and on the run queue and gets here, then it + # had an exception and was not await'ed on. Throwing into it now will + # raise StopIteration and the code below will catch this and run the + # call_exception_handler function. t.data = None t.coro.throw(exc) except excs_all as er: @@ -185,22 +189,32 @@ def run_until_complete(main_task=None): if isinstance(er, StopIteration): return er.value raise er - # Schedule any other tasks waiting on the completion of this task - waiting = False - if hasattr(t, "waiting"): - while t.waiting.peek(): - _task_queue.push_head(t.waiting.pop_head()) - waiting = True - t.waiting = None # Free waiting queue head - if not waiting and not isinstance(er, excs_stop): - # An exception ended this detached task, so queue it for later - # execution to handle the uncaught exception if no other task retrieves - # the exception in the meantime (this is handled by Task.throw). - _task_queue.push_head(t) - # Indicate task is done by setting coro to the task object itself - t.coro = t - # Save return value of coro to pass up to caller - t.data = er + if t.state: + # Task was running but is now finished. + waiting = False + if t.state is True: + # "None" indicates that the task is complete and not await'ed on (yet). + t.state = None + else: + # Schedule any other tasks waiting on the completion of this task. + while t.state.peek(): + _task_queue.push_head(t.state.pop_head()) + waiting = True + # "False" indicates that the task is complete and has been await'ed on. + t.state = False + if not waiting and not isinstance(er, excs_stop): + # An exception ended this detached task, so queue it for later + # execution to handle the uncaught exception if no other task retrieves + # the exception in the meantime (this is handled by Task.throw). + _task_queue.push_head(t) + # Save return value of coro to pass up to caller. + t.data = er + elif t.state is None: + # Task is already finished and nothing await'ed on the task, + # so call the exception handler. + _exc_context["exception"] = exc + _exc_context["future"] = t + Loop.call_exception_handler(_exc_context) # Create a new task from a coroutine and run it until it finishes diff --git a/extmod/uasyncio/stream.py b/extmod/uasyncio/stream.py index b6d787e4f0..3a68881da3 100644 --- a/extmod/uasyncio/stream.py +++ b/extmod/uasyncio/stream.py @@ -30,6 +30,10 @@ class Stream: yield core._io_queue.queue_read(self.s) return self.s.read(n) + async def readinto(self, buf): + yield core._io_queue.queue_read(self.s) + return self.s.readinto(buf) + async def readexactly(self, n): r = b"" while n: @@ -82,7 +86,7 @@ async def open_connection(host, port): try: s.connect(ai[-1]) except OSError as er: - if er.args[0] != EINPROGRESS: + if er.errno != EINPROGRESS: raise er yield core._io_queue.queue_write(s) return ss, ss @@ -112,7 +116,6 @@ class Server: s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind(ai[-1]) s.listen(backlog) - self.task = core.cur_task # Accept incoming connections while True: try: @@ -135,7 +138,7 @@ class Server: # TODO could use an accept-callback on socket read activity instead of creating a task async def start_server(cb, host, port, backlog=5): s = Server() - core.create_task(s._serve(cb, host, port, backlog)) + s.task = core.create_task(s._serve(cb, host, port, backlog)) return s diff --git a/extmod/uasyncio/task.py b/extmod/uasyncio/task.py index 68ddf496f0..26df7b1725 100644 --- a/extmod/uasyncio/task.py +++ b/extmod/uasyncio/task.py @@ -123,6 +123,7 @@ class Task: def __init__(self, coro, globals=None): self.coro = coro # Coroutine of this Task self.data = None # General data for queue it is waiting on + self.state = True # None, False, True or a TaskQueue instance self.ph_key = 0 # Pairing heap self.ph_child = None # Paring heap self.ph_child_last = None # Paring heap @@ -130,30 +131,30 @@ class Task: self.ph_rightmost_parent = None # Paring heap def __iter__(self): - if self.coro is self: - # Signal that the completed-task has been await'ed on. - self.waiting = None - elif not hasattr(self, "waiting"): - # Lazily allocated head of linked list of Tasks waiting on completion of this task. - self.waiting = TaskQueue() + if not self.state: + # Task finished, signal that is has been await'ed on. + self.state = False + elif self.state is True: + # Allocated head of linked list of Tasks waiting on completion of this task. + self.state = TaskQueue() return self def __next__(self): - if self.coro is self: + if not self.state: # Task finished, raise return value to caller so it can continue. raise self.data else: # Put calling task on waiting queue. - self.waiting.push_head(core.cur_task) + self.state.push_head(core.cur_task) # Set calling task's data to this task that it waits on, to double-link it. core.cur_task.data = self def done(self): - return self.coro is self + return not self.state def cancel(self): # Check if task is already finished. - if self.coro is self: + if not self.state: return False # Can't cancel self (not supported yet). if self is core.cur_task: @@ -172,13 +173,3 @@ class Task: core._task_queue.push_head(self) self.data = core.CancelledError return True - - def throw(self, value): - # This task raised an exception which was uncaught; handle that now. - # Set the data because it was cleared by the main scheduling loop. - self.data = value - if not hasattr(self, "waiting"): - # Nothing await'ed on the task so call the exception handler. - core._exc_context["exception"] = value - core._exc_context["future"] = self - core.Loop.call_exception_handler(core._exc_context) diff --git a/extmod/vfs_fat.c b/extmod/vfs_fat.c index 0090500cdc..be144ec821 100644 --- a/extmod/vfs_fat.c +++ b/extmod/vfs_fat.c @@ -264,7 +264,7 @@ STATIC mp_obj_t fat_vfs_mkdir(mp_obj_t vfs_in, mp_obj_t path_o) { } STATIC MP_DEFINE_CONST_FUN_OBJ_2(fat_vfs_mkdir_obj, fat_vfs_mkdir); -/// Change current directory. +// Change current directory. STATIC mp_obj_t fat_vfs_chdir(mp_obj_t vfs_in, mp_obj_t path_in) { mp_obj_fat_vfs_t *self = MP_OBJ_TO_PTR(vfs_in); const char *path; @@ -280,7 +280,7 @@ STATIC mp_obj_t fat_vfs_chdir(mp_obj_t vfs_in, mp_obj_t path_in) { } STATIC MP_DEFINE_CONST_FUN_OBJ_2(fat_vfs_chdir_obj, fat_vfs_chdir); -/// Get the current directory. +// Get the current directory. STATIC mp_obj_t fat_vfs_getcwd(mp_obj_t vfs_in) { mp_obj_fat_vfs_t *self = MP_OBJ_TO_PTR(vfs_in); char buf[MICROPY_ALLOC_PATH_MAX + 1]; @@ -292,8 +292,7 @@ STATIC mp_obj_t fat_vfs_getcwd(mp_obj_t vfs_in) { } STATIC MP_DEFINE_CONST_FUN_OBJ_1(fat_vfs_getcwd_obj, fat_vfs_getcwd); -/// \function stat(path) -/// Get the status of a file or directory. +// Get the status of a file or directory. STATIC mp_obj_t fat_vfs_stat(mp_obj_t vfs_in, mp_obj_t path_in) { mp_obj_fat_vfs_t *self = MP_OBJ_TO_PTR(vfs_in); const char *path = mp_obj_str_get_str(path_in); diff --git a/lib/utils/pyexec.c b/lib/utils/pyexec.c index 8bd027cf2c..b0249f9d30 100644 --- a/lib/utils/pyexec.c +++ b/lib/utils/pyexec.c @@ -622,8 +622,8 @@ friendly_repl_reset: // If the GC is locked at this point there is no way out except a reset, // so force the GC to be unlocked to help the user debug what went wrong. - if (MP_STATE_MEM(gc_lock_depth) != 0) { - MP_STATE_MEM(gc_lock_depth) = 0; + if (MP_STATE_THREAD(gc_lock_depth) != 0) { + MP_STATE_THREAD(gc_lock_depth) = 0; } vstr_reset(&line); diff --git a/lib/utils/pyexec.h b/lib/utils/pyexec.h index 6ec2e629f0..4924f8776a 100644 --- a/lib/utils/pyexec.h +++ b/lib/utils/pyexec.h @@ -47,9 +47,8 @@ extern pyexec_mode_kind_t pyexec_mode_kind; extern int pyexec_system_exit; #define PYEXEC_FORCED_EXIT (0x100) -#define PYEXEC_SWITCH_MODE (0x200) -#define PYEXEC_EXCEPTION (0x400) -#define PYEXEC_DEEP_SLEEP (0x800) +#define PYEXEC_EXCEPTION (0x200) +#define PYEXEC_DEEP_SLEEP (0x400) int pyexec_raw_repl(void); int pyexec_friendly_repl(void); diff --git a/lib/utils/semihosting.c b/lib/utils/semihosting.c new file mode 100644 index 0000000000..18c7f5d57a --- /dev/null +++ b/lib/utils/semihosting.c @@ -0,0 +1,132 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Ayke van Laethem + * + * 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 "semihosting.h" + +// Resources: +// http://embed.rs/articles/2016/semi-hosting-rust/ +// https://wiki.dlang.org/Minimal_semihosted_ARM_Cortex-M_%22Hello_World%22 +// https://github.com/arduino/OpenOCD/blob/master/src/target/arm_semihosting.c + +#define SYS_OPEN 0x01 +#define SYS_WRITEC 0x03 +#define SYS_WRITE 0x05 +#define SYS_READC 0x07 + +// Constants: +#define OPEN_MODE_READ (0) // mode "r" +#define OPEN_MODE_WRITE (4) // mode "w" + +#ifndef __thumb__ +#error Semihosting is only implemented for ARM microcontrollers. +#endif + +static int mp_semihosting_stdout; + +static uint32_t mp_semihosting_call(uint32_t num, const void *arg) { + // A semihosting call works as follows, similar to a SVCall: + // * the call is invoked by a special breakpoint: 0xAB + // * the command is placed in r0 + // * a pointer to the arguments is placed in r1 + // * the return value is placed in r0 + // Note that because it uses the breakpoint instruction, applications + // will hang if they're not connected to a debugger. And they'll be + // stuck in a breakpoint if semihosting is not specifically enabled in + // the debugger. + // Also note that semihosting is extremely slow (sometimes >100ms per + // call). + register uint32_t num_reg __asm__ ("r0") = num; + register const void *args_reg __asm__ ("r1") = arg; + __asm__ __volatile__ ( + "bkpt 0xAB\n" // invoke semihosting call + : "+r" (num_reg) // call number and result + : "r" (args_reg) // arguments + : "memory"); // make sure args aren't optimized away + return num_reg; // r0, which became the result +} + +static int mp_semihosting_open_console(uint32_t mode) { + struct { + char *name; + uint32_t mode; + uint32_t name_len; + } args = { + .name = ":tt", // magic path to console + .mode = mode, // e.g. "r", "w" (see OPEN_MODE_* constants) + .name_len = 3, // strlen(":tt") + }; + return mp_semihosting_call(SYS_OPEN, &args); +} + +void mp_semihosting_init() { + mp_semihosting_stdout = mp_semihosting_open_console(OPEN_MODE_WRITE); +} + +int mp_semihosting_rx_char() { + return mp_semihosting_call(SYS_READC, NULL); +} + +static void mp_semihosting_tx_char(char c) { + mp_semihosting_call(SYS_WRITEC, &c); +} + +uint32_t mp_semihosting_tx_strn(const char *str, size_t len) { + if (len == 0) { + return 0; // nothing to do + } + if (len == 1) { + mp_semihosting_tx_char(*str); // maybe faster? + return 0; + } + + struct { + uint32_t fd; + const char *str; + uint32_t len; + } args = { + .fd = mp_semihosting_stdout, + .str = str, + .len = len, + }; + return mp_semihosting_call(SYS_WRITE, &args); +} + +uint32_t mp_semihosting_tx_strn_cooked(const char *str, size_t len) { + // Write chunks of data until (excluding) the first '\n' character, + // insert a '\r' character, and then continue with the next chunk + // (starting with '\n'). + // Doing byte-by-byte writes would be easier to implement but is far + // too slow. + size_t start = 0; + for (size_t i = 0; i < len; i++) { + if (str[i] == '\n') { + mp_semihosting_tx_strn(str + start, i - start); + mp_semihosting_tx_char('\r'); + start = i; + } + } + return mp_semihosting_tx_strn(str + start, len - start); +} diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index f261eb33d3..6fb48b0f18 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -2537,6 +2537,10 @@ msgstr "" msgid "binary op %q not implemented" msgstr "" +#: extmod/modurandom.c +msgid "bits must be 32 or less" +msgstr "" + #: shared-bindings/busio/UART.c msgid "bits must be in range 5 to 9" msgstr "" @@ -3148,6 +3152,10 @@ msgstr "" msgid "graphic must be 2048 bytes long" msgstr "" +#: extmod/moduhashlib.c +msgid "hash is final" +msgstr "" + #: extmod/moduheapq.c msgid "heap must be a list" msgstr "" diff --git a/ports/esp32/boards/M5STACK_ATOM/modules/atom.py b/ports/esp32/boards/M5STACK_ATOM/modules/atom.py new file mode 100644 index 0000000000..8f47585f19 --- /dev/null +++ b/ports/esp32/boards/M5STACK_ATOM/modules/atom.py @@ -0,0 +1,75 @@ +# M5Stack ATOM MicroPython Helper Library +# MIT license; Copyright (c) 2021 IAMLIUBO work for M5STACK +# +# Hardware details: +# ATOM Lite https://docs.m5stack.com/en/core/atom_lite +# ATOM Matrix https://docs.m5stack.com/en/core/atom_matrix + +from micropython import const +from machine import Pin +import neopixel + +# M5STACK ATOM Hardware Pin Assignments +""" + FRONT + |3V3| +|G21| IR G12 |G22| +|G25| BTN G39 |G19| +| 5V| WS2812 G27 |G23| +|GNG| MPU G21 G25 |G33| + G32 G26 5V GND + Grove Port +""" + +# WS2812 +WS2812_PIN = const(27) + +# Button +BUTTON_PIN = const(39) + +# IR +IR_PIN = const(12) + +# I2C +I2C0_SCL_PIN = const(21) +I2C0_SDA_PIN = const(25) + +# Grove port +GROVE_PORT_PIN = (const(26), const(32)) + + +class ATOM: + def __init__(self, np_n): + self._np = neopixel.NeoPixel(pin=Pin(WS2812_PIN), n=np_n) + self._btn = Pin(BUTTON_PIN, Pin.IN, Pin.PULL_UP) + + def get_button_status(self): + return self._btn.value() + + def set_button_callback(self, cb): + self._btn.irq(trigger=Pin.IRQ_FALLING, handler=cb) + + def set_pixel_color(self, num, r, g, b): + if num <= self._np.n: + self._np[num] = [r, g, b] + self._np.write() + + def get_pixel_color(self, num): + if num <= self._np.n: + return self._np[num] + + def set_pixels_color(self, r, g, b): + self._np.fill([r, g, b]) + self._np.write() + + +class Lite(ATOM): + # WS2812 number: 1 + def __init__(self): + super(Lite, self).__init__(np_n=1) + + +class Matrix(ATOM): + # WS2812 number: 25 + def __init__(self): + super(Matrix, self).__init__(np_n=25) diff --git a/ports/esp32/boards/M5STACK_ATOM/mpconfigboard.cmake b/ports/esp32/boards/M5STACK_ATOM/mpconfigboard.cmake new file mode 100644 index 0000000000..548337a479 --- /dev/null +++ b/ports/esp32/boards/M5STACK_ATOM/mpconfigboard.cmake @@ -0,0 +1,10 @@ +set(SDKCONFIG_DEFAULTS + boards/sdkconfig.base + boards/sdkconfig.ble + boards/sdkconfig.240mhz + boards/M5STACK_ATOM/sdkconfig.board +) + +if(NOT MICROPY_FROZEN_MANIFEST) + set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py) +endif() diff --git a/ports/esp32/boards/M5STACK_ATOM/mpconfigboard.h b/ports/esp32/boards/M5STACK_ATOM/mpconfigboard.h new file mode 100644 index 0000000000..4270b19caf --- /dev/null +++ b/ports/esp32/boards/M5STACK_ATOM/mpconfigboard.h @@ -0,0 +1,2 @@ +#define MICROPY_HW_BOARD_NAME "M5Stack ATOM" +#define MICROPY_HW_MCU_NAME "ESP32-PICO-D4" diff --git a/ports/esp32/boards/M5STACK_ATOM/sdkconfig.board b/ports/esp32/boards/M5STACK_ATOM/sdkconfig.board new file mode 100644 index 0000000000..b299822dcb --- /dev/null +++ b/ports/esp32/boards/M5STACK_ATOM/sdkconfig.board @@ -0,0 +1,5 @@ +CONFIG_FLASHMODE_QIO=y +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_ESP32_REV_MIN_1=y +CONFIG_LWIP_LOCAL_HOSTNAME="M5StackATOM" diff --git a/ports/esp32/boards/UM_FEATHERS2/manifest.py b/ports/esp32/boards/UM_FEATHERS2/manifest.py new file mode 100644 index 0000000000..82ad0c7e49 --- /dev/null +++ b/ports/esp32/boards/UM_FEATHERS2/manifest.py @@ -0,0 +1,3 @@ +include("$(PORT_DIR)/boards/manifest.py") +freeze("$(PORT_DIR)/boards/UM_TINYPICO/modules", "dotstar.py") +freeze("modules") diff --git a/ports/esp32/boards/UM_FEATHERS2/modules/feathers2.py b/ports/esp32/boards/UM_FEATHERS2/modules/feathers2.py new file mode 100644 index 0000000000..95e1f52681 --- /dev/null +++ b/ports/esp32/boards/UM_FEATHERS2/modules/feathers2.py @@ -0,0 +1,101 @@ +# FeatherS2 MicroPython Helper Library +# 2021 Seon Rozenblum - Unexpected Maker +# +# Project home: +# https://feathers2.io +# +# 2021-Mar-21 - v0.1 - Initial implementation + +# Import required libraries +from micropython import const +from machine import Pin, SPI, ADC +import machine, time + +# FeatherS2 Hardware Pin Assignments + +# LDO +LDO2 = const(21) + +# APA102 Dotstar pins +DOTSTAR_CLK = const(45) +DOTSTAR_DATA = const(40) + +# SPI +SPI_MOSI = const(35) +SPI_MISO = const(37) +SPI_CLK = const(36) + +# I2C +I2C_SDA = const(8) +I2C_SCL = const(9) + +# DAC +DAC1 = const(17) +DAC2 = const(18) + +# LED & Ambient Light Sensor +LED = const(13) +AMB_LIGHT = const(4) + +# Helper functions + +# LED & Ambient Light Sensor control +def set_led(state): + l = Pin(LED, Pin.OUT) + l.value(state) + + +def toggle_led(state): + l = Pin(LED, Pin.OUT) + l.value(not l.value()) + + +# Create ADC and set attenuation and return the ambient light value from the onboard sensor +def get_amb_light(): + adc = ADC(Pin(AMB_LIGHT)) + adc.atten(ADC.ATTN_11DB) + return adc.read() + + +# LDO2 power control +# When we manually turn off the second LDO we also set the DotStar DATA and CLK pins to input to +# prevent parasitic power from lighting the LED even with the LDO off, causing current use. +# The DotStar is a beautiful LED, but parasitic power makes it a terrible choice for battery use :( +def set_ldo2_power(state): + """Set the power for the on-board Dotstar to allow no current draw when not needed.""" + # Set the power pin to the inverse of state + ldo2 = Pin(LDO2, Pin.OUT) + ldo2.value(state) + + if state: + Pin(DOTSTAR_CLK, Pin.OUT) + Pin(DOTSTAR_DATA, Pin.OUT) # If power is on, set CLK to be output, otherwise input + else: + Pin(DOTSTAR_CLK, Pin.IN) + Pin(DOTSTAR_DATA, Pin.IN) # If power is on, set CLK to be output, otherwise input + + # A small delay to let the IO change state + time.sleep(0.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_ldo2_power(False) + machine.deepsleep(t) diff --git a/ports/esp32/boards/UM_FEATHERS2/mpconfigboard.cmake b/ports/esp32/boards/UM_FEATHERS2/mpconfigboard.cmake new file mode 100644 index 0000000000..5e570d513b --- /dev/null +++ b/ports/esp32/boards/UM_FEATHERS2/mpconfigboard.cmake @@ -0,0 +1,9 @@ +set(IDF_TARGET esp32s2) +set(SDKCONFIG_DEFAULTS + boards/sdkconfig.base + boards/sdkconfig.spiram_sx + boards/sdkconfig.usb + boards/UM_FEATHERS2/sdkconfig.board +) + +set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py) \ No newline at end of file diff --git a/ports/esp32/boards/UM_FEATHERS2/mpconfigboard.h b/ports/esp32/boards/UM_FEATHERS2/mpconfigboard.h new file mode 100644 index 0000000000..8d0c9f78c8 --- /dev/null +++ b/ports/esp32/boards/UM_FEATHERS2/mpconfigboard.h @@ -0,0 +1,12 @@ +#define MICROPY_HW_BOARD_NAME "FeatherS2" +#define MICROPY_HW_MCU_NAME "ESP32-S2" + +#define MICROPY_PY_BLUETOOTH (0) +#define MICROPY_HW_ENABLE_SDCARD (0) + +#define MICROPY_HW_I2C0_SCL (9) +#define MICROPY_HW_I2C0_SDA (8) + +#define MICROPY_HW_SPI1_MOSI (35) // SDO +#define MICROPY_HW_SPI1_MISO (37) // SDI +#define MICROPY_HW_SPI1_SCK (36) diff --git a/ports/esp32/boards/UM_FEATHERS2/sdkconfig.board b/ports/esp32/boards/UM_FEATHERS2/sdkconfig.board new file mode 100644 index 0000000000..ccda7bff68 --- /dev/null +++ b/ports/esp32/boards/UM_FEATHERS2/sdkconfig.board @@ -0,0 +1,16 @@ +CONFIG_FLASHMODE_QIO=y +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y +CONFIG_ESPTOOLPY_FLASHSIZE_DETECT=y +CONFIG_ESPTOOLPY_AFTER_NORESET=y + +CONFIG_SPIRAM_MEMTEST= + +CONFIG_ESPTOOLPY_FLASHSIZE_4MB= +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-16MiB.csv" +#CONFIG_USB_AND_UART=y + +# LWIP +CONFIG_LWIP_LOCAL_HOSTNAME="UMFeatherS2" +# end of LWIP diff --git a/ports/esp32/boards/UM_TINYPICO/manifest.py b/ports/esp32/boards/UM_TINYPICO/manifest.py new file mode 100644 index 0000000000..7ae2ed15d9 --- /dev/null +++ b/ports/esp32/boards/UM_TINYPICO/manifest.py @@ -0,0 +1,2 @@ +include("$(PORT_DIR)/boards/manifest.py") +freeze("modules") diff --git a/ports/esp32/boards/UM_TINYPICO/mpconfigboard.cmake b/ports/esp32/boards/UM_TINYPICO/mpconfigboard.cmake new file mode 100644 index 0000000000..bc2541c699 --- /dev/null +++ b/ports/esp32/boards/UM_TINYPICO/mpconfigboard.cmake @@ -0,0 +1,11 @@ +set(SDKCONFIG_DEFAULTS + boards/sdkconfig.base + boards/sdkconfig.ble + boards/sdkconfig.240mhz + boards/sdkconfig.spiram + boards/UM_TINYPICO/sdkconfig.board +) + +if(NOT MICROPY_FROZEN_MANIFEST) + set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py) +endif() diff --git a/ports/esp32/boards/UM_TINYPICO/mpconfigboard.h b/ports/esp32/boards/UM_TINYPICO/mpconfigboard.h new file mode 100644 index 0000000000..6bf70cc13b --- /dev/null +++ b/ports/esp32/boards/UM_TINYPICO/mpconfigboard.h @@ -0,0 +1,9 @@ +#define MICROPY_HW_BOARD_NAME "TinyPICO" +#define MICROPY_HW_MCU_NAME "ESP32-PICO-D4" + +#define MICROPY_HW_I2C0_SCL (22) +#define MICROPY_HW_I2C0_SDA (21) + +#define MICROPY_HW_SPI1_SCK (18) +#define MICROPY_HW_SPI1_MOSI (23) +#define MICROPY_HW_SPI1_MISO (19) diff --git a/ports/esp32/boards/UM_TINYS2/manifest.py b/ports/esp32/boards/UM_TINYS2/manifest.py new file mode 100644 index 0000000000..7ae2ed15d9 --- /dev/null +++ b/ports/esp32/boards/UM_TINYS2/manifest.py @@ -0,0 +1,2 @@ +include("$(PORT_DIR)/boards/manifest.py") +freeze("modules") diff --git a/ports/esp32/boards/UM_TINYS2/modules/tinys2.py b/ports/esp32/boards/UM_TINYS2/modules/tinys2.py new file mode 100644 index 0000000000..0a3eaf14d4 --- /dev/null +++ b/ports/esp32/boards/UM_TINYS2/modules/tinys2.py @@ -0,0 +1,82 @@ +# TinyS2 MicroPython Helper Library +# 2021 Seon Rozenblum - Unexpected Maker +# +# Project home: +# https://tinys2.io +# +# 2021-Apr-10 - v0.1 - Initial implementation + +# Import required libraries +from micropython import const +from machine import Pin, SPI, ADC +import machine, time + +# TinyS2 Hardware Pin Assignments + +# Sense Pins +VBUS_SENSE = const(21) +VBAT_SENSE = const(3) + + +# RGB LED Pins +RGB_DATA = const(1) +RGB_PWR = const(2) + +# SPI +SPI_MOSI = const(35) +SPI_MISO = const(36) +SPI_CLK = const(37) + +# I2C +I2C_SDA = const(8) +I2C_SCL = const(9) + +# DAC +DAC1 = const(17) +DAC2 = const(18) + + +# Helper functions +def set_pixel_power(state): + """Enable or Disable power to the onboard NeoPixel to either show colour, or to reduce power for deep sleep.""" + Pin(RGB_PWR, Pin.OUT).value(state) + + +def get_battery_voltage(): + """ + Returns the current battery voltage. If no battery is connected, returns 4.2V which is the charge voltage + This is an approximation only, but useful to detect if the charge state of the battery is getting low. + """ + adc = ADC(Pin(VBAT_SENSE)) # Assign the ADC pin to read + measuredvbat = adc.read() # Read the value + measuredvbat /= 8192 # divide by 8192 as we are using the default ADC voltage range of 0-1V + measuredvbat *= 4.2 # Multiply by 4.2V, our reference voltage + return round(measuredvbat, 2) + + +def get_vbus_present(): + """Detect if VBUS (5V) power source is present""" + return Pin(VBUS_SENSE, Pin.IN).value() == 1 + + +# NeoPixel rainbow colour wheel +def rgb_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 RGB LED 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 NeoPixel.""" + set_pixel_power(False) + machine.deepsleep(t) diff --git a/ports/esp32/boards/UM_TINYS2/mpconfigboard.cmake b/ports/esp32/boards/UM_TINYS2/mpconfigboard.cmake new file mode 100644 index 0000000000..928f9f8fc3 --- /dev/null +++ b/ports/esp32/boards/UM_TINYS2/mpconfigboard.cmake @@ -0,0 +1,8 @@ +set(IDF_TARGET esp32s2) +set(SDKCONFIG_DEFAULTS + boards/sdkconfig.base + boards/sdkconfig.spiram_sx + boards/sdkconfig.usb +) + +set(MICROPY_FROZEN_MANIFEST ${MICROPY_PORT_DIR}/boards/manifest.py) diff --git a/ports/esp32/boards/UM_TINYS2/mpconfigboard.h b/ports/esp32/boards/UM_TINYS2/mpconfigboard.h new file mode 100644 index 0000000000..1052f6d79c --- /dev/null +++ b/ports/esp32/boards/UM_TINYS2/mpconfigboard.h @@ -0,0 +1,12 @@ +#define MICROPY_HW_BOARD_NAME "TinyS2" +#define MICROPY_HW_MCU_NAME "ESP32-S2FN4R2" + +#define MICROPY_PY_BLUETOOTH (0) +#define MICROPY_HW_ENABLE_SDCARD (0) + +#define MICROPY_HW_I2C0_SCL (9) +#define MICROPY_HW_I2C0_SDA (8) + +#define MICROPY_HW_SPI1_MOSI (35) +#define MICROPY_HW_SPI1_MISO (36) +#define MICROPY_HW_SPI1_SCK (37) diff --git a/ports/esp32/boards/UM_TINYS2/sdkconfig.board b/ports/esp32/boards/UM_TINYS2/sdkconfig.board new file mode 100644 index 0000000000..48b6749c72 --- /dev/null +++ b/ports/esp32/boards/UM_TINYS2/sdkconfig.board @@ -0,0 +1,6 @@ +CONFIG_FLASHMODE_QIO=y +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y +CONFIG_USB_AND_UART=y +# LWIP +CONFIG_LWIP_LOCAL_HOSTNAME="UMTinyS2" +# end of LWIP diff --git a/ports/esp32/boards/sdkconfig.spiram_sx b/ports/esp32/boards/sdkconfig.spiram_sx new file mode 100644 index 0000000000..18a0712cbf --- /dev/null +++ b/ports/esp32/boards/sdkconfig.spiram_sx @@ -0,0 +1,11 @@ +# MicroPython on ESP32-S2 and ESP32-PAD1_subscript_3, ESP IDF configuration with SPIRAM support +CONFIG_ESP32S2_SPIRAM_SUPPORT=y +CONFIG_SPIRAM_TYPE_AUTO=y +CONFIG_DEFAULT_PSRAM_CLK_IO=30 +CONFIG_DEFAULT_PSRAM_CS_IO=26 +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_BOOT_INIT=y +CONFIG_SPIRAM_IGNORE_NOTFOUND=y +CONFIG_SPIRAM_USE_MEMMAP=y +CONFIG_SPIRAM_MEMTEST=y diff --git a/ports/esp32/partitions-16MiB.csv b/ports/esp32/partitions-16MiB.csv new file mode 100644 index 0000000000..20d06bad47 --- /dev/null +++ b/ports/esp32/partitions-16MiB.csv @@ -0,0 +1,7 @@ +# Notes: the offset of the partition table itself is set in +# $IDF_PATH/components/partition_table/Kconfig.projbuild. +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 0x180000, +vfs, data, fat, 0x200000, 0xD59F80, diff --git a/ports/esp8266/boards/GENERIC_512K/_boot.py b/ports/esp8266/boards/GENERIC_512K/_boot.py new file mode 100644 index 0000000000..1a55cfd36c --- /dev/null +++ b/ports/esp8266/boards/GENERIC_512K/_boot.py @@ -0,0 +1,3 @@ +import gc + +gc.threshold((gc.mem_free() + gc.mem_alloc()) // 4) diff --git a/ports/esp8266/boards/GENERIC_512K/manifest.py b/ports/esp8266/boards/GENERIC_512K/manifest.py new file mode 100644 index 0000000000..5674f280b5 --- /dev/null +++ b/ports/esp8266/boards/GENERIC_512K/manifest.py @@ -0,0 +1,5 @@ +freeze("$(BOARD_DIR)", "_boot.py", opt=3) +freeze("$(PORT_DIR)/modules", ("apa102.py", "neopixel.py", "ntptime.py", "port_diag.py")) +freeze("$(MPY_DIR)/drivers/dht", "dht.py") +freeze("$(MPY_DIR)/drivers/onewire") +include("$(MPY_DIR)/extmod/webrepl/manifest.py") diff --git a/ports/nrf/boards/evk_nina_b3/mpconfigboard.h b/ports/nrf/boards/evk_nina_b3/mpconfigboard.h new file mode 100644 index 0000000000..eaca796be4 --- /dev/null +++ b/ports/nrf/boards/evk_nina_b3/mpconfigboard.h @@ -0,0 +1,85 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Glenn Ruben Bakke + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// Pin numbering scheme for nrf52840-based boards +// +// Software Pins 0-31 correspond to physical pins +// 0.x and software Pins 32-47 correspond to physical pins 1.x. +// +// Example: Pin(47) would be 1.15 on the PCA10059 + +// Board data +#define MICROPY_HW_BOARD_NAME "EVK_NINA_B3" +#define MICROPY_HW_MCU_NAME "NRF52840" +#define MICROPY_PY_SYS_PLATFORM "nrf52" + +// Enable @viper and @native +#define MICROPY_EMIT_THUMB (1) +#define MICROPY_EMIT_INLINE_THUMB (1) + +// Enable optional modules +#define MICROPY_PY_UERRNO (1) +#define MICROPY_PY_UHASHLIB (1) + +// Peripherals Config +#define MICROPY_PY_MACHINE_UART (1) +#define MICROPY_PY_MACHINE_HW_PWM (1) +#define MICROPY_PY_MACHINE_HW_SPI (1) +#define MICROPY_PY_MACHINE_TIMER (1) +#define MICROPY_PY_MACHINE_RTCOUNTER (1) +#define MICROPY_PY_MACHINE_I2C (1) +#define MICROPY_PY_MACHINE_ADC (1) +#define MICROPY_PY_MACHINE_TEMP (1) +#define MICROPY_HW_ENABLE_RNG (1) + +// Configure LEDS +#define MICROPY_HW_HAS_LED (1) +#define MICROPY_HW_LED_COUNT (3) +#define MICROPY_HW_LED_PULLUP (1) +#define MICROPY_HW_LED1 (13) // LED1 RED +#define MICROPY_HW_LED2 (25) // LED2 GREEN +#define MICROPY_HW_LED3 (32) // LED3 BLUE + +// UART config +#define MICROPY_HW_UART1_RX (29) +#define MICROPY_HW_UART1_TX (45) +#define MICROPY_HW_UART1_CTS (44) +#define MICROPY_HW_UART1_RTS (31) +#define MICROPY_HW_UART1_HWFC (1) + +// SPI config +#define MICROPY_HW_SPI0_NAME "SPI0" +#define MICROPY_HW_SPI0_SCK (12) +#define MICROPY_HW_SPI0_MOSI (14) +#define MICROPY_HW_SPI0_MISO (15) + +// PWM Names +#define MICROPY_HW_PWM0_NAME "PWM0" +#define MICROPY_HW_PWM1_NAME "PWM1" +#define MICROPY_HW_PWM2_NAME "PWM2" + +// How Many LED indexes appear in the help() message +#define HELP_TEXT_BOARD_LED "1,2,3" diff --git a/ports/nrf/boards/evk_nina_b3/mpconfigboard.mk b/ports/nrf/boards/evk_nina_b3/mpconfigboard.mk new file mode 100644 index 0000000000..ca555d3932 --- /dev/null +++ b/ports/nrf/boards/evk_nina_b3/mpconfigboard.mk @@ -0,0 +1,7 @@ +MCU_SERIES = m4 +MCU_VARIANT = nrf52 +MCU_SUB_VARIANT = nrf52840 +SOFTDEV_VERSION = 6.1.1 +LD_FILES += boards/nrf52840_1M_256k.ld + +NRF_DEFINES += -DNRF52840_XXAA diff --git a/ports/nrf/boards/evk_nina_b3/pins.csv b/ports/nrf/boards/evk_nina_b3/pins.csv new file mode 100644 index 0000000000..e6190bac0a --- /dev/null +++ b/ports/nrf/boards/evk_nina_b3/pins.csv @@ -0,0 +1,48 @@ +P0,P0 +P1,P1 +P2,P2,ADC0_IN0 +P3,P3,ADC0_IN1 +P4,P4,ADC0_IN2 +P5,P5,ADC0_IN3 +P6,P6 +P7,P7 +P8,P8 +P9,P9 +P10,P10 +P11,P11 +P12,P12 +P13,P13 +P14,P14 +P15,P15 +P16,P16 +P17,P17 +P18,P18 +P19,P19 +P20,P20 +P21,P21 +P22,P22 +P23,P23 +P24,P24 +P25,P25 +P26,P26 +P27,P27 +P28,P28,ADC0_IN4 +P29,P29,ADC0_IN5 +P30,P30,ADC0_IN6 +P31,P31,ADC0_IN7 +P32,P32 +P33,P33 +P34,P34 +P35,P35 +P36,P36 +P37,P37 +P38,P38 +P39,P39 +P40,P40 +P41,P41 +P42,P42 +P43,P43 +P44,P44 +P45,P45 +P46,P46 +P47,P47 diff --git a/ports/qemu-arm/imx6.ld b/ports/qemu-arm/imx6.ld new file mode 100644 index 0000000000..7155956f15 --- /dev/null +++ b/ports/qemu-arm/imx6.ld @@ -0,0 +1,47 @@ +/* Vector table is at 0x00000000, entry point is 0x10000000. */ + +MEMORY +{ + ROM : ORIGIN = 0x00000000, LENGTH = 96K + RAM : ORIGIN = 0x10000000, LENGTH = 128M +} + +_estack = ORIGIN(RAM) + LENGTH(RAM); + +SECTIONS +{ + .rom : { + . = ALIGN(4); + KEEP(*(.isr_vector)) + . = ALIGN(4); + } > ROM + + .text : { + . = ALIGN(4); + *(.text.Reset_Handler) + *(.text*) + *(.rodata*) + . = ALIGN(4); + _etext = .; + _sidata = _etext; + } > RAM + + .data : AT ( _sidata ) + { + . = ALIGN(4); + _sdata = .; + *(.data*) + . = ALIGN(4); + _edata = .; + } > RAM + + .bss : + { + . = ALIGN(4); + _sbss = .; + *(.bss*) + *(COMMON) + . = ALIGN(4); + _ebss = .; + } > RAM +} diff --git a/ports/rp2/boards/SPARKFUN_PROMICRO/mpconfigboard.cmake b/ports/rp2/boards/SPARKFUN_PROMICRO/mpconfigboard.cmake new file mode 100644 index 0000000000..6ac8d7a446 --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_PROMICRO/mpconfigboard.cmake @@ -0,0 +1 @@ +# cmake file for SparkFun Pro Micro RP2040 diff --git a/ports/rp2/boards/SPARKFUN_PROMICRO/mpconfigboard.h b/ports/rp2/boards/SPARKFUN_PROMICRO/mpconfigboard.h new file mode 100644 index 0000000000..d6c8007ba0 --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_PROMICRO/mpconfigboard.h @@ -0,0 +1,3 @@ +// Board and hardware specific configuration +#define MICROPY_HW_BOARD_NAME "SparkFun Pro Micro RP2040" +#define MICROPY_HW_FLASH_STORAGE_BYTES (15 * 1024 * 1024) diff --git a/ports/rp2/boards/SPARKFUN_THINGPLUS/mpconfigboard.cmake b/ports/rp2/boards/SPARKFUN_THINGPLUS/mpconfigboard.cmake new file mode 100644 index 0000000000..b9090bbcec --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_THINGPLUS/mpconfigboard.cmake @@ -0,0 +1 @@ +# cmake file for SparkFun Thing Plus RP2040 diff --git a/ports/rp2/boards/SPARKFUN_THINGPLUS/mpconfigboard.h b/ports/rp2/boards/SPARKFUN_THINGPLUS/mpconfigboard.h new file mode 100644 index 0000000000..9749acd25a --- /dev/null +++ b/ports/rp2/boards/SPARKFUN_THINGPLUS/mpconfigboard.h @@ -0,0 +1,3 @@ +// Board and hardware specific configuration +#define MICROPY_HW_BOARD_NAME "SparkFun Thing Plus RP2040" +#define MICROPY_HW_FLASH_STORAGE_BYTES (15 * 1024 * 1024) diff --git a/ports/rp2/boards/manifest.py b/ports/rp2/boards/manifest.py new file mode 100644 index 0000000000..9df589f126 --- /dev/null +++ b/ports/rp2/boards/manifest.py @@ -0,0 +1,3 @@ +freeze("$(PORT_DIR)/modules") +freeze("$(MPY_DIR)/drivers/onewire") +include("$(MPY_DIR)/extmod/uasyncio/manifest.py") diff --git a/ports/rp2/machine_rtc.c b/ports/rp2/machine_rtc.c new file mode 100644 index 0000000000..797bee5ed3 --- /dev/null +++ b/ports/rp2/machine_rtc.c @@ -0,0 +1,122 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 "Krzysztof Adamski" + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include + +#include +#include + +#include "py/nlr.h" +#include "py/obj.h" +#include "py/runtime.h" +#include "py/mphal.h" +#include "py/mperrno.h" +#include "lib/timeutils/timeutils.h" +#include "hardware/rtc.h" +#include "pico/util/datetime.h" +#include "modmachine.h" + +typedef struct _machine_rtc_obj_t { + mp_obj_base_t base; +} machine_rtc_obj_t; + +// singleton RTC object +STATIC const machine_rtc_obj_t machine_rtc_obj = {{&machine_rtc_type}}; + +STATIC mp_obj_t machine_rtc_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + // check arguments + mp_arg_check_num(n_args, n_kw, 0, 0, false); + bool r = rtc_running(); + + if (!r) { + // This shouldn't happen as rtc_init() is already called in main so + // it's here just in case + rtc_init(); + datetime_t t = { .month = 1, .day = 1 }; + rtc_set_datetime(&t); + } + // return constant object + return (mp_obj_t)&machine_rtc_obj; +} + +STATIC mp_obj_t machine_rtc_datetime(mp_uint_t n_args, const mp_obj_t *args) { + if (n_args == 1) { + bool ret; + datetime_t t; + + ret = rtc_get_datetime(&t); + if (!ret) { + mp_raise_OSError(MP_EIO); + } + + mp_obj_t tuple[8] = { + mp_obj_new_int(t.year), + mp_obj_new_int(t.month), + mp_obj_new_int(t.day), + mp_obj_new_int(t.dotw), + mp_obj_new_int(t.hour), + mp_obj_new_int(t.min), + mp_obj_new_int(t.sec), + mp_obj_new_int(0) + }; + + return mp_obj_new_tuple(8, tuple); + } else { + mp_obj_t *items; + + mp_obj_get_array_fixed_n(args[1], 8, &items); + + datetime_t t = { + .year = mp_obj_get_int(items[0]), + .month = mp_obj_get_int(items[1]), + .day = mp_obj_get_int(items[2]), + .dotw = mp_obj_get_int(items[3]), + .hour = mp_obj_get_int(items[4]), + .min = mp_obj_get_int(items[5]), + .sec = mp_obj_get_int(items[6]), + }; + + if (!rtc_set_datetime(&t)) { + mp_raise_OSError(MP_EINVAL); + } + + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_rtc_datetime_obj, 1, 2, machine_rtc_datetime); + +STATIC const mp_rom_map_elem_t machine_rtc_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_datetime), MP_ROM_PTR(&machine_rtc_datetime_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(machine_rtc_locals_dict, machine_rtc_locals_dict_table); + +const mp_obj_type_t machine_rtc_type = { + { &mp_type_type }, + .name = MP_QSTR_RTC, + .make_new = machine_rtc_make_new, + .locals_dict = (mp_obj_t)&machine_rtc_locals_dict, +}; diff --git a/ports/unix/coverage.c b/ports/unix/coverage.c index 8df8fd5f1e..3426fc21e5 100644 --- a/ports/unix/coverage.c +++ b/ports/unix/coverage.c @@ -270,6 +270,13 @@ STATIC mp_obj_t extra_coverage(void) { size_t len = mp_repl_autocomplete("__n", 3, &mp_plat_print, &str); mp_printf(&mp_plat_print, "%.*s\n", (int)len, str); + len = mp_repl_autocomplete("i", 1, &mp_plat_print, &str); + mp_printf(&mp_plat_print, "%.*s\n", (int)len, str); + mp_repl_autocomplete("import ", 7, &mp_plat_print, &str); + len = mp_repl_autocomplete("import ut", 9, &mp_plat_print, &str); + mp_printf(&mp_plat_print, "%.*s\n", (int)len, str); + mp_repl_autocomplete("import utime", 12, &mp_plat_print, &str); + mp_store_global(MP_QSTR_sys, mp_import_name(MP_QSTR_sys, mp_const_none, MP_OBJ_NEW_SMALL_INT(0))); mp_repl_autocomplete("sys.", 4, &mp_plat_print, &str); len = mp_repl_autocomplete("sys.impl", 8, &mp_plat_print, &str); @@ -485,7 +492,7 @@ STATIC mp_obj_t extra_coverage(void) { } // setting the keyboard interrupt and raising it during mp_handle_pending - mp_keyboard_interrupt(); + mp_sched_keyboard_interrupt(); nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { mp_handle_pending(true); @@ -495,13 +502,13 @@ STATIC mp_obj_t extra_coverage(void) { } // setting the keyboard interrupt (twice) and cancelling it during mp_handle_pending - mp_keyboard_interrupt(); - mp_keyboard_interrupt(); + mp_sched_keyboard_interrupt(); + mp_sched_keyboard_interrupt(); mp_handle_pending(false); // setting keyboard interrupt and a pending event (intr should be handled first) mp_sched_schedule(MP_OBJ_FROM_PTR(&mp_builtin_print_obj), MP_OBJ_NEW_SMALL_INT(10)); - mp_keyboard_interrupt(); + mp_sched_keyboard_interrupt(); if (nlr_push(&nlr) == 0) { mp_handle_pending(true); nlr_pop(); diff --git a/ports/unix/main.c b/ports/unix/main.c index a63a377c3b..55bdc22082 100644 --- a/ports/unix/main.c +++ b/ports/unix/main.c @@ -450,7 +450,13 @@ MP_NOINLINE int main_(int argc, char **argv) { signal(SIGPIPE, SIG_IGN); #endif - mp_stack_set_limit(40000 * (sizeof(void *) / 4)); + // Define a reasonable stack limit to detect stack overflow. + mp_uint_t stack_limit = 40000 * (sizeof(void *) / 4); + #if defined(__arm__) && !defined(__thumb2__) + // ARM (non-Thumb) architectures require more stack. + stack_limit *= 2; + #endif + mp_stack_set_limit(stack_limit); pre_process_options(argc, argv); diff --git a/ports/unix/modffi.c b/ports/unix/modffi.c index d18b82dbc6..06fb4c5718 100644 --- a/ports/unix/modffi.c +++ b/ports/unix/modffi.c @@ -35,6 +35,7 @@ #include "py/runtime.h" #include "py/binary.h" #include "py/mperrno.h" +#include "py/objint.h" #include "supervisor/shared/translate.h" @@ -58,6 +59,18 @@ * but may be later. */ +// This union is large enough to hold any supported argument/return value. +typedef union _ffi_union_t { + ffi_arg ffi; + unsigned char B; + unsigned short int H; + unsigned int I; + unsigned long int L; + unsigned long long int Q; + float flt; + double dbl; +} ffi_union_t; + typedef struct _mp_obj_opaque_t { mp_obj_base_t base; void *val; @@ -153,10 +166,10 @@ STATIC ffi_type *get_ffi_type(mp_obj_t o_in) { mp_raise_TypeError(MP_ERROR_TEXT("unknown type")); } -STATIC mp_obj_t return_ffi_value(ffi_arg val, char type) { +STATIC mp_obj_t return_ffi_value(ffi_union_t *val, char type) { switch (type) { case 's': { - const char *s = (const char *)(intptr_t)val; + const char *s = (const char *)(intptr_t)val->ffi; if (!s) { return mp_const_none; } @@ -166,20 +179,30 @@ STATIC mp_obj_t return_ffi_value(ffi_arg val, char type) { return mp_const_none; #if MICROPY_PY_BUILTINS_FLOAT case 'f': { - union { ffi_arg ffi; - float flt; - } val_union = { .ffi = val }; - return mp_obj_new_float_from_f(val_union.flt); + return mp_obj_new_float_from_f(val->flt); } case 'd': { - double *p = (double *)&val; - return mp_obj_new_float_from_d(*p); + return mp_obj_new_float_from_d(val->dbl); } #endif + case 'b': + case 'h': + case 'i': + case 'l': + return mp_obj_new_int((signed)val->ffi); + case 'B': + case 'H': + case 'I': + case 'L': + return mp_obj_new_int_from_uint(val->ffi); + case 'q': + return mp_obj_new_int_from_ll(val->Q); + case 'Q': + return mp_obj_new_int_from_ull(val->Q); case 'O': - return (mp_obj_t)(intptr_t)val; + return (mp_obj_t)(intptr_t)val->ffi; default: - return mp_obj_new_int(val); + return mp_obj_new_int(val->ffi); } } @@ -364,34 +387,74 @@ STATIC void ffifunc_print(const mp_print_t *print, mp_obj_t self_in, mp_print_ki mp_printf(print, "", self->func); } +STATIC unsigned long long ffi_get_int_value(mp_obj_t o) { + if (mp_obj_is_small_int(o)) { + return MP_OBJ_SMALL_INT_VALUE(o); + } else { + unsigned long long res; + mp_obj_int_to_bytes_impl(o, MP_ENDIANNESS_BIG, sizeof(res), (byte *)&res); + return res; + } +} + +STATIC ffi_union_t ffi_int_obj_to_ffi_union(mp_obj_t o, const char argtype) { + ffi_union_t ret; + if ((argtype | 0x20) == 'q') { + ret.Q = ffi_get_int_value(o); + return ret; + } else { + mp_uint_t val = mp_obj_int_get_truncated(o); + switch (argtype) { + case 'b': + case 'B': + ret.B = val; + break; + case 'h': + case 'H': + ret.H = val; + break; + case 'i': + case 'I': + ret.I = val; + break; + case 'l': + case 'L': + ret.L = val; + break; + default: + ret.ffi = val; + break; + } + } + return ret; +} + STATIC mp_obj_t ffifunc_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { (void)n_kw; mp_obj_ffifunc_t *self = MP_OBJ_TO_PTR(self_in); assert(n_kw == 0); assert(n_args == self->cif.nargs); - ffi_arg values[n_args]; + ffi_union_t values[n_args]; void *valueptrs[n_args]; const char *argtype = self->argtypes; for (uint i = 0; i < n_args; i++, argtype++) { mp_obj_t a = args[i]; if (*argtype == 'O') { - values[i] = (ffi_arg)(intptr_t)a; + values[i].ffi = (ffi_arg)(intptr_t)a; #if MICROPY_PY_BUILTINS_FLOAT } else if (*argtype == 'f') { - float *p = (float *)&values[i]; - *p = mp_obj_get_float_to_f(a); + values[i].flt = mp_obj_get_float_to_f(a); } else if (*argtype == 'd') { - double *p = (double *)&values[i]; - *p = mp_obj_get_float_to_d(a); + values[i].dbl = mp_obj_get_float_to_d(a); #endif } else if (a == mp_const_none) { - values[i] = 0; + values[i].ffi = 0; } else if (mp_obj_is_int(a)) { - values[i] = mp_obj_int_get_truncated(a); + values[i] = ffi_int_obj_to_ffi_union(a, *argtype); } else if (mp_obj_is_str(a)) { const char *s = mp_obj_str_get_str(a); - values[i] = (ffi_arg)(intptr_t)s; + values[i].ffi = (ffi_arg)(intptr_t)s; } else if (((mp_obj_base_t *)MP_OBJ_TO_PTR(a))->type->buffer_p.get_buffer != NULL) { mp_obj_base_t *o = (mp_obj_base_t *)MP_OBJ_TO_PTR(a); mp_buffer_info_t bufinfo; @@ -399,32 +462,19 @@ STATIC mp_obj_t ffifunc_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const if (ret != 0) { goto error; } - values[i] = (ffi_arg)(intptr_t)bufinfo.buf; + values[i].ffi = (ffi_arg)(intptr_t)bufinfo.buf; } else if (mp_obj_is_type(a, &fficallback_type)) { mp_obj_fficallback_t *p = MP_OBJ_TO_PTR(a); - values[i] = (ffi_arg)(intptr_t)p->func; + values[i].ffi = (ffi_arg)(intptr_t)p->func; } else { goto error; } valueptrs[i] = &values[i]; } - // If ffi_arg is not big enough to hold a double, then we must pass along a - // pointer to a memory location of the correct size. - // TODO check if this needs to be done for other types which don't fit into - // ffi_arg. - #if MICROPY_PY_BUILTINS_FLOAT - if (sizeof(ffi_arg) == 4 && self->rettype == 'd') { - double retval; - ffi_call(&self->cif, self->func, &retval, valueptrs); - return mp_obj_new_float_from_d(retval); - } else - #endif - { - ffi_arg retval; - ffi_call(&self->cif, self->func, &retval, valueptrs); - return return_ffi_value(retval, self->rettype); - } + ffi_union_t retval; + ffi_call(&self->cif, self->func, &retval, valueptrs); + return return_ffi_value(&retval, self->rettype); error: mp_raise_TypeError(MP_ERROR_TEXT("Don't know how to pass object to native function")); diff --git a/ports/unix/mpthreadport.c b/ports/unix/mpthreadport.c index e5ca793ab3..fe4030f08d 100644 --- a/ports/unix/mpthreadport.c +++ b/ports/unix/mpthreadport.c @@ -126,7 +126,7 @@ void mp_thread_init(void) { thread->next = NULL; #if defined(__APPLE__) - snprintf(thread_signal_done_name, sizeof(thread_signal_done_name), "micropython_sem_%d", (int)thread->id); + snprintf(thread_signal_done_name, sizeof(thread_signal_done_name), "micropython_sem_%ld", (long)thread->id); thread_signal_done_p = sem_open(thread_signal_done_name, O_CREAT | O_EXCL, 0666, 0); #else sem_init(&thread_signal_done, 0, 0); diff --git a/ports/unix/unix_mphal.c b/ports/unix/unix_mphal.c index 89bb645bff..fceff68284 100644 --- a/ports/unix/unix_mphal.c +++ b/ports/unix/unix_mphal.c @@ -57,7 +57,7 @@ STATIC void sighandler(int signum) { // this is the second time we are called, so die straight away exit(1); } - mp_keyboard_interrupt(); + mp_sched_keyboard_interrupt(); #endif } } diff --git a/ports/zephyr/boards/nucleo_wb55rg.conf b/ports/zephyr/boards/nucleo_wb55rg.conf new file mode 100644 index 0000000000..baf0f28075 --- /dev/null +++ b/ports/zephyr/boards/nucleo_wb55rg.conf @@ -0,0 +1,6 @@ +CONFIG_CONSOLE_SUBSYS=n +CONFIG_NETWORKING=n +CONFIG_BT=y +CONFIG_BT_DEVICE_NAME_DYNAMIC=y +CONFIG_BT_PERIPHERAL=y +CONFIG_BT_CENTRAL=y diff --git a/ports/zephyr/modbluetooth_zephyr.c b/ports/zephyr/modbluetooth_zephyr.c new file mode 100644 index 0000000000..6b3a10d471 --- /dev/null +++ b/ports/zephyr/modbluetooth_zephyr.c @@ -0,0 +1,410 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Damien P. George + * Copyright (c) 2019-2020 Jim Mussared + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/runtime.h" +#include "py/mperrno.h" +#include "py/mphal.h" + +#if MICROPY_PY_BLUETOOTH + +#include +#include +#include "extmod/modbluetooth.h" + +#define DEBUG_printf(...) // printk("BLE: " __VA_ARGS__) + +#define BLE_HCI_SCAN_ITVL_MIN 0x10 +#define BLE_HCI_SCAN_ITVL_MAX 0xffff +#define BLE_HCI_SCAN_WINDOW_MIN 0x10 +#define BLE_HCI_SCAN_WINDOW_MAX 0xffff + +#define ERRNO_BLUETOOTH_NOT_ACTIVE MP_ENODEV + +enum { + MP_BLUETOOTH_ZEPHYR_BLE_STATE_OFF, + MP_BLUETOOTH_ZEPHYR_BLE_STATE_ACTIVE, + MP_BLUETOOTH_ZEPHYR_BLE_STATE_SUSPENDED, +}; + +enum { + MP_BLUETOOTH_ZEPHYR_GAP_SCAN_STATE_INACTIVE, + MP_BLUETOOTH_ZEPHYR_GAP_SCAN_STATE_DEACTIVATING, + MP_BLUETOOTH_ZEPHYR_GAP_SCAN_STATE_ACTIVE, +}; + +typedef struct _mp_bluetooth_zephyr_root_pointers_t { + // Characteristic (and descriptor) value storage. + mp_gatts_db_t gatts_db; +} mp_bluetooth_zephyr_root_pointers_t; + +STATIC int mp_bluetooth_zephyr_ble_state; + +#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE +STATIC int mp_bluetooth_zephyr_gap_scan_state; +STATIC struct k_timer mp_bluetooth_zephyr_gap_scan_timer; +STATIC struct bt_le_scan_cb mp_bluetooth_zephyr_gap_scan_cb_struct; +#endif + +STATIC int bt_err_to_errno(int err) { + // Zephyr uses errno codes directly, but they are negative. + return -err; +} + +// modbluetooth (and the layers above it) work in BE for addresses, Zephyr works in LE. +STATIC void reverse_addr_byte_order(uint8_t *addr_out, const bt_addr_le_t *addr_in) { + for (int i = 0; i < 6; ++i) { + addr_out[i] = addr_in->a.val[5 - i]; + } +} + +#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE +void gap_scan_cb_recv(const struct bt_le_scan_recv_info *info, struct net_buf_simple *buf) { + DEBUG_printf("gap_scan_cb_recv: adv_type=%d\n", info->adv_type); + + if (!mp_bluetooth_is_active()) { + return; + } + + if (mp_bluetooth_zephyr_gap_scan_state != MP_BLUETOOTH_ZEPHYR_GAP_SCAN_STATE_ACTIVE) { + return; + } + + uint8_t addr[6]; + reverse_addr_byte_order(addr, info->addr); + mp_bluetooth_gap_on_scan_result(info->addr->type, addr, info->adv_type, info->rssi, buf->data, buf->len); +} + +STATIC mp_obj_t gap_scan_stop(mp_obj_t unused) { + (void)unused; + mp_bluetooth_gap_scan_stop(); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(gap_scan_stop_obj, gap_scan_stop); + +void gap_scan_cb_timeout(struct k_timer *timer_id) { + DEBUG_printf("gap_scan_cb_timeout\n"); + // Cannot call bt_le_scan_stop from a timer callback because this callback may be + // preempting the BT stack. So schedule it to be called from the main thread. + while (!mp_sched_schedule(MP_OBJ_FROM_PTR(&gap_scan_stop_obj), mp_const_none)) { + k_yield(); + } + // Indicate scanning has stopped so that no more scan result events are generated + // (they may still come in until bt_le_scan_stop is called by gap_scan_stop). + mp_bluetooth_zephyr_gap_scan_state = MP_BLUETOOTH_ZEPHYR_GAP_SCAN_STATE_DEACTIVATING; +} +#endif + +int mp_bluetooth_init(void) { + DEBUG_printf("mp_bluetooth_init\n"); + + // Clean up if necessary. + mp_bluetooth_deinit(); + + // Allocate memory for state. + MP_STATE_PORT(bluetooth_zephyr_root_pointers) = m_new0(mp_bluetooth_zephyr_root_pointers_t, 1); + mp_bluetooth_gatts_db_create(&MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db); + + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE + mp_bluetooth_zephyr_gap_scan_state = MP_BLUETOOTH_ZEPHYR_GAP_SCAN_STATE_INACTIVE; + k_timer_init(&mp_bluetooth_zephyr_gap_scan_timer, gap_scan_cb_timeout, NULL); + mp_bluetooth_zephyr_gap_scan_cb_struct.recv = gap_scan_cb_recv; + mp_bluetooth_zephyr_gap_scan_cb_struct.timeout = NULL; // currently not implemented in Zephyr + bt_le_scan_cb_register(&mp_bluetooth_zephyr_gap_scan_cb_struct); + #endif + + if (mp_bluetooth_zephyr_ble_state == MP_BLUETOOTH_ZEPHYR_BLE_STATE_OFF) { + // bt_enable can only be called once. + int ret = bt_enable(NULL); + if (ret) { + return bt_err_to_errno(ret); + } + } + + mp_bluetooth_zephyr_ble_state = MP_BLUETOOTH_ZEPHYR_BLE_STATE_ACTIVE; + + DEBUG_printf("mp_bluetooth_init: ready\n"); + + return 0; +} + +void mp_bluetooth_deinit(void) { + DEBUG_printf("mp_bluetooth_deinit %d\n", mp_bluetooth_zephyr_ble_state); + if (mp_bluetooth_zephyr_ble_state == MP_BLUETOOTH_ZEPHYR_BLE_STATE_OFF + || mp_bluetooth_zephyr_ble_state == MP_BLUETOOTH_ZEPHYR_BLE_STATE_SUSPENDED) { + return; + } + + mp_bluetooth_gap_advertise_stop(); + + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE + mp_bluetooth_gap_scan_stop(); + bt_le_scan_cb_unregister(&mp_bluetooth_zephyr_gap_scan_cb_struct); + #endif + + // There is no way to turn off the BT stack in Zephyr, so just set the + // state as suspended so it can be correctly reactivated later. + mp_bluetooth_zephyr_ble_state = MP_BLUETOOTH_ZEPHYR_BLE_STATE_SUSPENDED; + + MP_STATE_PORT(bluetooth_zephyr_root_pointers) = NULL; +} + +bool mp_bluetooth_is_active(void) { + return mp_bluetooth_zephyr_ble_state == MP_BLUETOOTH_ZEPHYR_BLE_STATE_ACTIVE; +} + +void mp_bluetooth_get_current_address(uint8_t *addr_type, uint8_t *addr) { + if (!mp_bluetooth_is_active()) { + mp_raise_OSError(ERRNO_BLUETOOTH_NOT_ACTIVE); + } + bt_addr_le_t le_addr; + size_t count = 1; + bt_id_get(&le_addr, &count); + if (count == 0) { + mp_raise_OSError(EIO); + } + reverse_addr_byte_order(addr, &le_addr); + *addr_type = le_addr.type; +} + +void mp_bluetooth_set_address_mode(uint8_t addr_mode) { + // TODO: implement +} + +size_t mp_bluetooth_gap_get_device_name(const uint8_t **buf) { + const char *name = bt_get_name(); + *buf = (const uint8_t *)name; + return strlen(name); +} + +int mp_bluetooth_gap_set_device_name(const uint8_t *buf, size_t len) { + char tmp_buf[CONFIG_BT_DEVICE_NAME_MAX + 1]; + if (len + 1 > sizeof(tmp_buf)) { + return MP_EINVAL; + } + memcpy(tmp_buf, buf, len); + tmp_buf[len] = '\0'; + return bt_err_to_errno(bt_set_name(tmp_buf)); +} + +// Zephyr takes advertising/scan data as an array of (type, len, payload) packets, +// and this function constructs such an array from raw advertising/scan data. +STATIC void mp_bluetooth_prepare_bt_data(const uint8_t *data, size_t len, struct bt_data *bt_data, size_t *bt_len) { + size_t i = 0; + const uint8_t *d = data; + while (d < data + len && i < *bt_len) { + bt_data[i].type = d[1]; + bt_data[i].data_len = d[0] - 1; + bt_data[i].data = &d[2]; + i += 1; + d += 1 + d[0]; + } + *bt_len = i; +} + +int mp_bluetooth_gap_advertise_start(bool connectable, int32_t interval_us, const uint8_t *adv_data, size_t adv_data_len, const uint8_t *sr_data, size_t sr_data_len) { + if (!mp_bluetooth_is_active()) { + return ERRNO_BLUETOOTH_NOT_ACTIVE; + } + + mp_bluetooth_gap_advertise_stop(); + + struct bt_data bt_ad_data[8]; + size_t bt_ad_len = 0; + if (adv_data) { + bt_ad_len = MP_ARRAY_SIZE(bt_ad_data); + mp_bluetooth_prepare_bt_data(adv_data, adv_data_len, bt_ad_data, &bt_ad_len); + } + + struct bt_data bt_sd_data[8]; + size_t bt_sd_len = 0; + if (sr_data) { + bt_sd_len = MP_ARRAY_SIZE(bt_sd_data); + mp_bluetooth_prepare_bt_data(sr_data, sr_data_len, bt_sd_data, &bt_sd_len); + } + + struct bt_le_adv_param param = { + .id = 0, + .sid = 0, + .secondary_max_skip = 0, + .options = (connectable ? BT_LE_ADV_OPT_CONNECTABLE : 0) + | BT_LE_ADV_OPT_ONE_TIME + | BT_LE_ADV_OPT_USE_IDENTITY + | BT_LE_ADV_OPT_SCANNABLE, + .interval_min = interval_us / 625, + .interval_max = interval_us / 625 + 1, // min/max cannot be the same value + .peer = NULL, + }; + + return bt_err_to_errno(bt_le_adv_start(¶m, bt_ad_data, bt_ad_len, bt_sd_data, bt_sd_len)); +} + +void mp_bluetooth_gap_advertise_stop(void) { + // Note: bt_le_adv_stop returns 0 if adv is already stopped. + int ret = bt_le_adv_stop(); + if (ret != 0) { + mp_raise_OSError(bt_err_to_errno(ret)); + } +} + +int mp_bluetooth_gatts_register_service_begin(bool append) { + if (!mp_bluetooth_is_active()) { + return ERRNO_BLUETOOTH_NOT_ACTIVE; + } + + if (append) { + // Don't support append yet (modbluetooth.c doesn't support it yet anyway). + return MP_EOPNOTSUPP; + } + + // Reset the gatt characteristic value db. + mp_bluetooth_gatts_db_reset(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db); + + return MP_EOPNOTSUPP; +} + +int mp_bluetooth_gatts_register_service_end(void) { + return MP_EOPNOTSUPP; +} + +int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, mp_obj_bluetooth_uuid_t **characteristic_uuids, uint16_t *characteristic_flags, mp_obj_bluetooth_uuid_t **descriptor_uuids, uint16_t *descriptor_flags, uint8_t *num_descriptors, uint16_t *handles, size_t num_characteristics) { + return MP_EOPNOTSUPP; +} + +int mp_bluetooth_gap_disconnect(uint16_t conn_handle) { + if (!mp_bluetooth_is_active()) { + return ERRNO_BLUETOOTH_NOT_ACTIVE; + } + return MP_EOPNOTSUPP; +} + +int mp_bluetooth_gatts_read(uint16_t value_handle, uint8_t **value, size_t *value_len) { + if (!mp_bluetooth_is_active()) { + return ERRNO_BLUETOOTH_NOT_ACTIVE; + } + return mp_bluetooth_gatts_db_read(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, value_handle, value, value_len); +} + +int mp_bluetooth_gatts_write(uint16_t value_handle, const uint8_t *value, size_t value_len) { + if (!mp_bluetooth_is_active()) { + return ERRNO_BLUETOOTH_NOT_ACTIVE; + } + return mp_bluetooth_gatts_db_write(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, value_handle, value, value_len); +} + +int mp_bluetooth_gatts_notify(uint16_t conn_handle, uint16_t value_handle) { + if (!mp_bluetooth_is_active()) { + return ERRNO_BLUETOOTH_NOT_ACTIVE; + } + return MP_EOPNOTSUPP; +} + +int mp_bluetooth_gatts_notify_send(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t value_len) { + if (!mp_bluetooth_is_active()) { + return ERRNO_BLUETOOTH_NOT_ACTIVE; + } + return MP_EOPNOTSUPP; +} + +int mp_bluetooth_gatts_indicate(uint16_t conn_handle, uint16_t value_handle) { + if (!mp_bluetooth_is_active()) { + return ERRNO_BLUETOOTH_NOT_ACTIVE; + } + return MP_EOPNOTSUPP; +} + +int mp_bluetooth_gatts_set_buffer(uint16_t value_handle, size_t len, bool append) { + if (!mp_bluetooth_is_active()) { + return ERRNO_BLUETOOTH_NOT_ACTIVE; + } + return MP_EOPNOTSUPP; +} + +int mp_bluetooth_get_preferred_mtu(void) { + if (!mp_bluetooth_is_active()) { + mp_raise_OSError(ERRNO_BLUETOOTH_NOT_ACTIVE); + } + mp_raise_OSError(MP_EOPNOTSUPP); +} + +int mp_bluetooth_set_preferred_mtu(uint16_t mtu) { + if (!mp_bluetooth_is_active()) { + return ERRNO_BLUETOOTH_NOT_ACTIVE; + } + return MP_EOPNOTSUPP; +} + +#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE + +int mp_bluetooth_gap_scan_start(int32_t duration_ms, int32_t interval_us, int32_t window_us, bool active_scan) { + // Stop any ongoing GAP scan. + int ret = mp_bluetooth_gap_scan_stop(); + if (ret) { + return ret; + } + + struct bt_le_scan_param param = { + .type = active_scan ? BT_HCI_LE_SCAN_ACTIVE : BT_HCI_LE_SCAN_PASSIVE, + .options = BT_LE_SCAN_OPT_NONE, + .interval = MAX(BLE_HCI_SCAN_ITVL_MIN, MIN(BLE_HCI_SCAN_ITVL_MAX, interval_us / 625)), + .window = MAX(BLE_HCI_SCAN_WINDOW_MIN, MIN(BLE_HCI_SCAN_WINDOW_MAX, window_us / 625)), + }; + k_timer_start(&mp_bluetooth_zephyr_gap_scan_timer, K_MSEC(duration_ms), K_NO_WAIT); + mp_bluetooth_zephyr_gap_scan_state = MP_BLUETOOTH_ZEPHYR_GAP_SCAN_STATE_ACTIVE; + int err = bt_le_scan_start(¶m, NULL); + return bt_err_to_errno(err); +} + +int mp_bluetooth_gap_scan_stop(void) { + DEBUG_printf("mp_bluetooth_gap_scan_stop\n"); + if (!mp_bluetooth_is_active()) { + return ERRNO_BLUETOOTH_NOT_ACTIVE; + } + if (mp_bluetooth_zephyr_gap_scan_state == MP_BLUETOOTH_ZEPHYR_GAP_SCAN_STATE_INACTIVE) { + // Already stopped. + return 0; + } + mp_bluetooth_zephyr_gap_scan_state = MP_BLUETOOTH_ZEPHYR_GAP_SCAN_STATE_INACTIVE; + k_timer_stop(&mp_bluetooth_zephyr_gap_scan_timer); + int err = bt_le_scan_stop(); + if (err == 0) { + mp_bluetooth_gap_on_scan_complete(); + return 0; + } + return bt_err_to_errno(err); +} + +int mp_bluetooth_gap_peripheral_connect(uint8_t addr_type, const uint8_t *addr, int32_t duration_ms) { + DEBUG_printf("mp_bluetooth_gap_peripheral_connect\n"); + if (!mp_bluetooth_is_active()) { + return ERRNO_BLUETOOTH_NOT_ACTIVE; + } + return MP_EOPNOTSUPP; +} + +#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE + +#endif // MICROPY_PY_BLUETOOTH diff --git a/ports/zephyr/mphalport.c b/ports/zephyr/mphalport.c new file mode 100644 index 0000000000..4f00cbd26c --- /dev/null +++ b/ports/zephyr/mphalport.c @@ -0,0 +1,74 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/runtime.h" +#include "py/mphal.h" + +static struct k_poll_signal wait_signal; +static struct k_poll_event wait_events[2] = { + K_POLL_EVENT_INITIALIZER(K_POLL_TYPE_SIGNAL, + K_POLL_MODE_NOTIFY_ONLY, + &wait_signal), + K_POLL_EVENT_STATIC_INITIALIZER(K_POLL_TYPE_SEM_AVAILABLE, + K_POLL_MODE_NOTIFY_ONLY, + NULL, 0), +}; + +void mp_hal_init(void) { + k_poll_signal_init(&wait_signal); +} + +void mp_hal_signal_event(void) { + k_poll_signal_raise(&wait_signal, 0); +} + +void mp_hal_wait_sem(struct k_sem *sem, uint32_t timeout_ms) { + mp_uint_t t0 = mp_hal_ticks_ms(); + if (sem) { + k_poll_event_init(&wait_events[1], K_POLL_TYPE_SEM_AVAILABLE, K_POLL_MODE_NOTIFY_ONLY, sem); + } + for (;;) { + k_timeout_t wait; + if (timeout_ms == (uint32_t)-1) { + wait = K_FOREVER; + } else { + uint32_t dt = mp_hal_ticks_ms() - t0; + if (dt >= timeout_ms) { + return; + } + wait = K_MSEC(timeout_ms - dt); + } + k_poll(wait_events, sem ? 2 : 1, wait); + if (wait_events[0].state == K_POLL_STATE_SIGNALED) { + wait_events[0].signal->signaled = 0; + wait_events[0].state = K_POLL_STATE_NOT_READY; + mp_handle_pending(true); + } else if (sem && wait_events[1].state == K_POLL_STATE_SEM_AVAILABLE) { + wait_events[1].state = K_POLL_STATE_NOT_READY; + return; + } + } +} diff --git a/py/argcheck.c b/py/argcheck.c index 8d26779b91..eb5cd87f8f 100644 --- a/py/argcheck.c +++ b/py/argcheck.c @@ -40,7 +40,7 @@ void mp_arg_check_num_sig(size_t n_args, size_t n_kw, uint32_t sig) { size_t n_args_max = (sig >> 1) & 0xffff; if (n_kw && !takes_kw) { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_arg_error_terse_mismatch(); #else mp_raise_TypeError(MP_ERROR_TEXT("function doesn't take keyword arguments")); @@ -49,7 +49,7 @@ void mp_arg_check_num_sig(size_t n_args, size_t n_kw, uint32_t sig) { if (n_args_min == n_args_max) { if (n_args != n_args_min) { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_arg_error_terse_mismatch(); #else mp_raise_TypeError_varg(MP_ERROR_TEXT("function takes %d positional arguments but %d were given"), @@ -58,7 +58,7 @@ void mp_arg_check_num_sig(size_t n_args, size_t n_kw, uint32_t sig) { } } else { if (n_args < n_args_min) { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_arg_error_terse_mismatch(); #else mp_raise_TypeError_varg( @@ -66,7 +66,7 @@ void mp_arg_check_num_sig(size_t n_args, size_t n_kw, uint32_t sig) { n_args_min - n_args); #endif } else if (n_args > n_args_max) { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_arg_error_terse_mismatch(); #else mp_raise_TypeError_varg( @@ -106,7 +106,7 @@ void mp_arg_parse_all(size_t n_pos, const mp_obj_t *pos, mp_map_t *kws, size_t n } if (kw == NULL) { if (allowed[i].flags & MP_ARG_REQUIRED) { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_arg_error_terse_mismatch(); #else mp_raise_TypeError_varg(MP_ERROR_TEXT("'%q' argument required"), allowed[i].qst); @@ -130,7 +130,7 @@ void mp_arg_parse_all(size_t n_pos, const mp_obj_t *pos, mp_map_t *kws, size_t n } if (pos_found < n_pos) { extra_positional: - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_arg_error_terse_mismatch(); #else // TODO better error message @@ -138,7 +138,7 @@ void mp_arg_parse_all(size_t n_pos, const mp_obj_t *pos, mp_map_t *kws, size_t n #endif } if (kws != NULL && kws_found < kws->used) { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_arg_error_terse_mismatch(); #else // TODO better error message diff --git a/py/asmarm.c b/py/asmarm.c index 30ab546ae6..3b637d34ef 100644 --- a/py/asmarm.c +++ b/py/asmarm.c @@ -38,25 +38,6 @@ #define SIGNED_FIT24(x) (((x) & 0xff800000) == 0) || (((x) & 0xff000000) == 0xff000000) -void asm_arm_end_pass(asm_arm_t *as) { - if (as->base.pass == MP_ASM_PASS_EMIT) { - #if defined(__linux__) && defined(__GNUC__) - char *start = mp_asm_base_get_code(&as->base); - char *end = start + mp_asm_base_get_code_size(&as->base); - __builtin___clear_cache(start, end); - #elif defined(__arm__) - // flush I- and D-cache - asm volatile ( - "0:" - "mrc p15, 0, r15, c7, c10, 3\n" - "bne 0b\n" - "mov r0, #0\n" - "mcr p15, 0, r0, c7, c7, 0\n" - : : : "r0", "cc"); - #endif - } -} - // Insert word into instruction flow STATIC void emit(asm_arm_t *as, uint op) { uint8_t *c = mp_asm_base_get_cur_to_write_bytes(&as->base, 4); diff --git a/py/asmarm.h b/py/asmarm.h index 3efb8042d3..cab4de79c2 100644 --- a/py/asmarm.h +++ b/py/asmarm.h @@ -72,7 +72,9 @@ typedef struct _asm_arm_t { uint stack_adjust; } asm_arm_t; -void asm_arm_end_pass(asm_arm_t *as); +static inline void asm_arm_end_pass(asm_arm_t *as) { + (void)as; +} void asm_arm_entry(asm_arm_t *as, int num_locals); void asm_arm_exit(asm_arm_t *as); diff --git a/py/asmthumb.c b/py/asmthumb.c index ae35632fac..547e5bedc1 100644 --- a/py/asmthumb.c +++ b/py/asmthumb.c @@ -35,7 +35,6 @@ #include "py/mpstate.h" #include "py/persistentcode.h" -#include "py/mphal.h" #include "py/asmthumb.h" #define UNSIGNED_FIT5(x) ((uint32_t)(x) < 32) @@ -62,20 +61,6 @@ static inline byte *asm_thumb_get_cur_to_write_bytes(asm_thumb_t *as, int n) { return mp_asm_base_get_cur_to_write_bytes(&as->base, n); } -void asm_thumb_end_pass(asm_thumb_t *as) { - (void)as; - // could check labels are resolved... - - #if defined(__ICACHE_PRESENT) && __ICACHE_PRESENT == 1 - if (as->base.pass == MP_ASM_PASS_EMIT) { - // flush D-cache, so the code emitted is stored in memory - MP_HAL_CLEAN_DCACHE(as->base.code_base, as->base.code_size); - // invalidate I-cache - SCB_InvalidateICache(); - } - #endif -} - /* STATIC void asm_thumb_write_byte_1(asm_thumb_t *as, byte b1) { byte *c = asm_thumb_get_cur_to_write_bytes(as, 1); diff --git a/py/asmthumb.h b/py/asmthumb.h index acdcfed701..ea48f7ebf3 100644 --- a/py/asmthumb.h +++ b/py/asmthumb.h @@ -70,7 +70,9 @@ typedef struct _asm_thumb_t { uint32_t stack_adjust; } asm_thumb_t; -void asm_thumb_end_pass(asm_thumb_t *as); +static inline void asm_thumb_end_pass(asm_thumb_t *as) { + (void)as; +} void asm_thumb_entry(asm_thumb_t *as, int num_locals); void asm_thumb_exit(asm_thumb_t *as); diff --git a/py/asmx64.c b/py/asmx64.c index fd64eaf98b..62df5c6d4a 100644 --- a/py/asmx64.c +++ b/py/asmx64.c @@ -285,31 +285,28 @@ void asm_x64_mov_r64_to_mem64(asm_x64_t *as, int src_r64, int dest_r64, int dest } void asm_x64_mov_mem8_to_r64zx(asm_x64_t *as, int src_r64, int src_disp, int dest_r64) { - assert(src_r64 < 8); - if (dest_r64 < 8) { + if (src_r64 < 8 && dest_r64 < 8) { asm_x64_write_byte_2(as, 0x0f, OPCODE_MOVZX_RM8_TO_R64); } else { - asm_x64_write_byte_3(as, REX_PREFIX | REX_R, 0x0f, OPCODE_MOVZX_RM8_TO_R64); + asm_x64_write_byte_3(as, REX_PREFIX | REX_R_FROM_R64(dest_r64) | REX_B_FROM_R64(src_r64), 0x0f, OPCODE_MOVZX_RM8_TO_R64); } asm_x64_write_r64_disp(as, dest_r64, src_r64, src_disp); } void asm_x64_mov_mem16_to_r64zx(asm_x64_t *as, int src_r64, int src_disp, int dest_r64) { - assert(src_r64 < 8); - if (dest_r64 < 8) { + if (src_r64 < 8 && dest_r64 < 8) { asm_x64_write_byte_2(as, 0x0f, OPCODE_MOVZX_RM16_TO_R64); } else { - asm_x64_write_byte_3(as, REX_PREFIX | REX_R, 0x0f, OPCODE_MOVZX_RM16_TO_R64); + asm_x64_write_byte_3(as, REX_PREFIX | REX_R_FROM_R64(dest_r64) | REX_B_FROM_R64(src_r64), 0x0f, OPCODE_MOVZX_RM16_TO_R64); } asm_x64_write_r64_disp(as, dest_r64, src_r64, src_disp); } void asm_x64_mov_mem32_to_r64zx(asm_x64_t *as, int src_r64, int src_disp, int dest_r64) { - assert(src_r64 < 8); - if (dest_r64 < 8) { + if (src_r64 < 8 && dest_r64 < 8) { asm_x64_write_byte_1(as, OPCODE_MOV_RM64_TO_R64); } else { - asm_x64_write_byte_2(as, REX_PREFIX | REX_R, OPCODE_MOV_RM64_TO_R64); + asm_x64_write_byte_2(as, REX_PREFIX | REX_R_FROM_R64(dest_r64) | REX_B_FROM_R64(src_r64), OPCODE_MOV_RM64_TO_R64); } asm_x64_write_r64_disp(as, dest_r64, src_r64, src_disp); } diff --git a/py/bc.c b/py/bc.c index 963b3d482c..ccf503631d 100644 --- a/py/bc.c +++ b/py/bc.c @@ -77,7 +77,7 @@ const byte *mp_decode_uint_skip(const byte *ptr) { #endif STATIC NORETURN void fun_pos_args_mismatch(mp_obj_fun_bc_t *f, size_t expected, size_t given) { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE // generic message, used also for other argument issues (void)f; (void)expected; @@ -221,7 +221,7 @@ void mp_setup_code_state(mp_code_state_t *code_state, size_t n_args, size_t n_kw } // Didn't find name match with positional args if ((scope_flags & MP_SCOPE_FLAG_VARKEYWORDS) == 0) { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("unexpected keyword argument")); #else mp_raise_TypeError_varg( diff --git a/py/builtinimport.c b/py/builtinimport.c index f614b95102..d49ef2a9d5 100644 --- a/py/builtinimport.c +++ b/py/builtinimport.c @@ -332,7 +332,7 @@ mp_obj_t mp_builtin___import__(size_t n_args, const mp_obj_t *args) { // We must have some component left over to import from if (p == this_name) { - mp_raise_ValueError(MP_ERROR_TEXT("cannot perform relative import")); + mp_raise_ImportError(MP_ERROR_TEXT("cannot perform relative import")); } uint new_mod_l = (mod_len == 0 ? (size_t)(p - this_name) : (size_t)(p - this_name) + 1 + mod_len); @@ -421,7 +421,7 @@ mp_obj_t mp_builtin___import__(size_t n_args, const mp_obj_t *args) { mp_module_call_init(mod_name, module_obj); } else { // couldn't find the file, so fail - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_ImportError(MP_ERROR_TEXT("module not found")); #else mp_raise_msg_varg(&mp_type_ImportError, @@ -533,7 +533,7 @@ mp_obj_t mp_builtin___import__(size_t n_args, const mp_obj_t *args) { #endif // Couldn't find the module, so fail - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_msg(&mp_type_ImportError, MP_ERROR_TEXT("module not found")); #else mp_raise_msg_varg(&mp_type_ImportError, MP_ERROR_TEXT("no module named '%q'"), module_name_qstr); diff --git a/py/compile.c b/py/compile.c index 911019e207..bdedc874e3 100644 --- a/py/compile.c +++ b/py/compile.c @@ -1795,16 +1795,6 @@ STATIC void compile_yield_from(compiler_t *comp) { } #if MICROPY_PY_ASYNC_AWAIT -STATIC bool compile_require_async_context(compiler_t *comp, mp_parse_node_struct_t *pns) { - int scope_flags = comp->scope_cur->scope_flags; - if ((scope_flags & MP_SCOPE_FLAG_ASYNC) != 0) { - return true; - } - compile_syntax_error(comp, (mp_parse_node_t)pns, - MP_ERROR_TEXT("'await', 'async for' or 'async with' outside async function")); - return false; -} - STATIC void compile_await_object_method(compiler_t *comp, qstr method) { EMIT_ARG(load_method, method, false); EMIT_ARG(call_method, 0, 0, 0); @@ -1813,11 +1803,6 @@ STATIC void compile_await_object_method(compiler_t *comp, qstr method) { STATIC void compile_async_for_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { // comp->break_label |= MP_EMIT_BREAK_FROM_FOR; - - if (!compile_require_async_context(comp, pns)) { - return; - } - qstr context = MP_PARSE_NODE_LEAF_ARG(pns->nodes[1]); uint while_else_label = comp_next_label(comp); uint try_exception_label = comp_next_label(comp); @@ -1980,9 +1965,6 @@ STATIC void compile_async_with_stmt_helper(compiler_t *comp, size_t n, mp_parse_ } STATIC void compile_async_with_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { - if (!compile_require_async_context(comp, pns)) { - return; - } // get the nodes for the pre-bit of the with (the a as b, c as d, ... bit) mp_parse_node_t *nodes; size_t n = mp_parse_node_extract_list(&pns->nodes[0], PN_with_stmt_list, &nodes); @@ -2000,13 +1982,23 @@ STATIC void compile_async_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { compile_funcdef(comp, pns0); scope_t *fscope = (scope_t *)pns0->nodes[4]; fscope->scope_flags |= MP_SCOPE_FLAG_GENERATOR | MP_SCOPE_FLAG_ASYNC; - } else if (MP_PARSE_NODE_STRUCT_KIND(pns0) == PN_for_stmt) { - // async for - compile_async_for_stmt(comp, pns0); } else { - // async with - assert(MP_PARSE_NODE_STRUCT_KIND(pns0) == PN_with_stmt); - compile_async_with_stmt(comp, pns0); + // async for/with; first verify the scope is a generator + int scope_flags = comp->scope_cur->scope_flags; + if (!(scope_flags & MP_SCOPE_FLAG_GENERATOR)) { + compile_syntax_error(comp, (mp_parse_node_t)pns0, + MP_ERROR_TEXT("'await', 'async for' or 'async with' outside async function")); + return; + } + + if (MP_PARSE_NODE_STRUCT_KIND(pns0) == PN_for_stmt) { + // async for + compile_async_for_stmt(comp, pns0); + } else { + // async with + assert(MP_PARSE_NODE_STRUCT_KIND(pns0) == PN_with_stmt); + compile_async_with_stmt(comp, pns0); + } } } #endif @@ -2627,7 +2619,7 @@ STATIC void compile_atom_brace_helper(compiler_t *comp, mp_parse_node_struct_t * compile_node(comp, pn_i); if (is_dict) { if (!is_key_value) { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE compile_syntax_error(comp, (mp_parse_node_t)pns, MP_ERROR_TEXT("invalid syntax")); #else compile_syntax_error(comp, (mp_parse_node_t)pns, MP_ERROR_TEXT("expecting key:value for dict")); @@ -2637,7 +2629,7 @@ STATIC void compile_atom_brace_helper(compiler_t *comp, mp_parse_node_struct_t * EMIT(store_map); } else { if (is_key_value) { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE compile_syntax_error(comp, (mp_parse_node_t)pns, MP_ERROR_TEXT("invalid syntax")); #else compile_syntax_error(comp, (mp_parse_node_t)pns, MP_ERROR_TEXT("expecting just a value for set")); @@ -2799,7 +2791,6 @@ STATIC void compile_atom_expr_await(compiler_t *comp, mp_parse_node_struct_t *pn compile_syntax_error(comp, (mp_parse_node_t)pns, MP_ERROR_TEXT("'await' outside function")); return; } - compile_require_async_context(comp, pns); compile_atom_expr_normal(comp, pns); // If it's an awaitable thing, need to reach for the __await__ method for the coroutine. diff --git a/py/dynruntime.h b/py/dynruntime.h index 96d7d27b2f..0b0fa443bd 100644 --- a/py/dynruntime.h +++ b/py/dynruntime.h @@ -81,6 +81,7 @@ static inline void *m_realloc_dyn(void *ptr, size_t new_num_bytes) { #define mp_type_type (*mp_fun_table.type_type) #define mp_type_str (*mp_fun_table.type_str) +#define mp_type_tuple (*((mp_obj_base_t *)mp_const_empty_tuple)->type) #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))) @@ -121,6 +122,7 @@ static inline void *m_realloc_dyn(void *ptr, size_t new_num_bytes) { #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_get_array(o, len, items) (mp_obj_get_array_dyn((o), (len), (items))) #define mp_obj_list_append(list, item) (mp_fun_table.list_append((list), (item))) #define mp_obj_assert_native_inited(o) (mp_fun_table.assert_native_inited((o))) @@ -264,4 +266,23 @@ static inline void mp_raise_OSError_dyn(int er) { #define mp_obj_get_float(o) (mp_obj_get_float_to_d((o))) #endif +/******************************************************************************/ +// Inline function definitions. + +// *items may point inside a GC block +static inline void mp_obj_get_array_dyn(mp_obj_t o, size_t *len, mp_obj_t **items) { + const mp_obj_type_t *type = mp_obj_get_type(o); + if (type == &mp_type_tuple) { + mp_obj_tuple_t *t = MP_OBJ_TO_PTR(o); + *len = t->len; + *items = &t->items[0]; + } else if (type == &mp_type_list) { + mp_obj_list_t *l = MP_OBJ_TO_PTR(o); + *len = l->len; + *items = l->items; + } else { + mp_raise_TypeError("expected tuple/list"); + } +} + #endif // MICROPY_INCLUDED_PY_DYNRUNTIME_H diff --git a/py/emitglue.c b/py/emitglue.c index 5049a76869..22ad55db57 100644 --- a/py/emitglue.c +++ b/py/emitglue.c @@ -108,6 +108,31 @@ void mp_emit_glue_assign_native(mp_raw_code_t *rc, mp_raw_code_kind_t kind, void assert(kind == MP_CODE_NATIVE_PY || kind == MP_CODE_NATIVE_VIPER || kind == MP_CODE_NATIVE_ASM); + // Some architectures require flushing/invalidation of the I/D caches, + // so that the generated native code which was created in data RAM will + // be available for execution from instruction RAM. + #if MICROPY_EMIT_THUMB || MICROPY_EMIT_INLINE_THUMB + #if __ICACHE_PRESENT == 1 + // Flush D-cache, so the code emitted is stored in RAM. + MP_HAL_CLEAN_DCACHE(fun_data, fun_len); + // Invalidate I-cache, so the newly-created code is reloaded from RAM. + SCB_InvalidateICache(); + #endif + #elif MICROPY_EMIT_ARM + #if (defined(__linux__) && defined(__GNUC__)) || __ARM_ARCH == 7 + __builtin___clear_cache(fun_data, (uint8_t *)fun_data + fun_len); + #elif defined(__arm__) + // Flush I-cache and D-cache. + asm volatile ( + "0:" + "mrc p15, 0, r15, c7, c10, 3\n" // test and clean D-cache + "bne 0b\n" + "mov r0, #0\n" + "mcr p15, 0, r0, c7, c7, 0\n" // invalidate I-cache and D-cache + : : : "r0", "cc"); + #endif + #endif + rc->kind = kind; rc->scope_flags = scope_flags; rc->n_pos_args = n_pos_args; diff --git a/py/emitnative.c b/py/emitnative.c index 1427bc83c9..1fa8c631e4 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -1797,7 +1797,7 @@ STATIC void emit_native_store_subscr(emit_t *emit) { int reg_index = REG_ARG_2; int reg_value = REG_ARG_3; emit_pre_pop_reg_flexible(emit, &vtype_base, ®_base, reg_index, reg_value); - #if N_X86 + #if N_X64 || N_X86 // special case: x86 needs byte stores to be from lower 4 regs (REG_ARG_3 is EDX) emit_pre_pop_reg(emit, &vtype_value, reg_value); #else @@ -1884,7 +1884,7 @@ STATIC void emit_native_store_subscr(emit_t *emit) { EMIT_NATIVE_VIPER_TYPE_ERROR(emit, MP_ERROR_TEXT("can't store with '%q' index"), vtype_to_qstr(vtype_index)); } - #if N_X86 + #if N_X64 || N_X86 // special case: x86 needs byte stores to be from lower 4 regs (REG_ARG_3 is EDX) emit_pre_pop_reg(emit, &vtype_value, reg_value); #else diff --git a/py/gc.c b/py/gc.c index 03a10fc535..789d3036b3 100644 --- a/py/gc.c +++ b/py/gc.c @@ -167,7 +167,7 @@ void gc_init(void *start, void *end) { MP_STATE_MEM(gc_lowest_long_lived_ptr) = (void *)PTR_FROM_BLOCK(MP_STATE_MEM(gc_alloc_table_byte_len * BLOCKS_PER_ATB)); // unlock the GC - MP_STATE_MEM(gc_lock_depth) = 0; + MP_STATE_THREAD(gc_lock_depth) = 0; // allow auto collection MP_STATE_MEM(gc_auto_collect_enabled) = true; @@ -200,19 +200,20 @@ void gc_deinit(void) { } void gc_lock(void) { - GC_ENTER(); - MP_STATE_MEM(gc_lock_depth)++; - GC_EXIT(); + // This does not need to be atomic or have the GC mutex because: + // - each thread has its own gc_lock_depth so there are no races between threads; + // - a hard interrupt will only change gc_lock_depth during its execution, and + // upon return will restore the value of gc_lock_depth. + MP_STATE_THREAD(gc_lock_depth)++; } void gc_unlock(void) { - GC_ENTER(); - MP_STATE_MEM(gc_lock_depth)--; - GC_EXIT(); + // This does not need to be atomic, See comment above in gc_lock. + MP_STATE_THREAD(gc_lock_depth)--; } bool gc_is_locked(void) { - return MP_STATE_MEM(gc_lock_depth) != 0; + return MP_STATE_THREAD(gc_lock_depth) != 0; } #ifndef TRACE_MARK @@ -356,7 +357,7 @@ STATIC void gc_mark(void *ptr) { void gc_collect_start(void) { GC_ENTER(); - MP_STATE_MEM(gc_lock_depth)++; + MP_STATE_THREAD(gc_lock_depth)++; #if MICROPY_GC_ALLOC_THRESHOLD MP_STATE_MEM(gc_alloc_amount) = 0; #endif @@ -383,9 +384,19 @@ void gc_collect_ptr(void *ptr) { gc_mark(ptr); } +// Address sanitizer needs to know that the access to ptrs[i] must always be +// considered OK, even if it's a load from an address that would normally be +// prohibited (due to being undefined, in a red zone, etc). +#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) +__attribute__((no_sanitize_address)) +#endif +static void *gc_get_ptr(void **ptrs, int i) { + return ptrs[i]; +} + void gc_collect_root(void **ptrs, size_t len) { for (size_t i = 0; i < len; i++) { - void *ptr = ptrs[i]; + void *ptr = gc_get_ptr(ptrs, i); gc_mark(ptr); } } @@ -397,13 +408,13 @@ void gc_collect_end(void) { MP_STATE_MEM(gc_first_free_atb_index)[i] = 0; } MP_STATE_MEM(gc_last_free_atb_index) = MP_STATE_MEM(gc_alloc_table_byte_len) - 1; - MP_STATE_MEM(gc_lock_depth)--; + MP_STATE_THREAD(gc_lock_depth)--; GC_EXIT(); } void gc_sweep_all(void) { GC_ENTER(); - MP_STATE_MEM(gc_lock_depth)++; + MP_STATE_THREAD(gc_lock_depth)++; MP_STATE_MEM(gc_stack_overflow) = 0; gc_collect_end(); } @@ -488,18 +499,17 @@ void *gc_alloc(size_t n_bytes, unsigned int alloc_flags, bool long_lived) { return NULL; } + // check if GC is locked + if (MP_STATE_THREAD(gc_lock_depth) > 0) { + return NULL; + } + if (MP_STATE_MEM(gc_pool_start) == 0) { reset_into_safe_mode(GC_ALLOC_OUTSIDE_VM); } GC_ENTER(); - // check if GC is locked - if (MP_STATE_MEM(gc_lock_depth) > 0) { - GC_EXIT(); - return NULL; - } - size_t found_block = 0xffffffff; size_t end_block; size_t start_block; @@ -676,13 +686,13 @@ void *gc_alloc_with_finaliser(mp_uint_t n_bytes) { // force the freeing of a piece of memory // TODO: freeing here does not call finaliser void gc_free(void *ptr) { - GC_ENTER(); - if (MP_STATE_MEM(gc_lock_depth) > 0) { + if (MP_STATE_THREAD(gc_lock_depth) > 0) { // TODO how to deal with this error? - GC_EXIT(); return; } + GC_ENTER(); + DEBUG_printf("gc_free(%p)\n", ptr); if (ptr == NULL) { @@ -837,15 +847,14 @@ void *gc_realloc(void *ptr_in, size_t n_bytes, bool allow_move) { return NULL; } + if (MP_STATE_THREAD(gc_lock_depth) > 0) { + return NULL; + } + void *ptr = ptr_in; GC_ENTER(); - if (MP_STATE_MEM(gc_lock_depth) > 0) { - GC_EXIT(); - return NULL; - } - // get the GC block number corresponding to this pointer assert(VERIFY_PTR(ptr)); size_t block = BLOCK_FROM_PTR(ptr); diff --git a/py/mkenv.mk b/py/mkenv.mk index 89628759e9..ab17a98f67 100644 --- a/py/mkenv.mk +++ b/py/mkenv.mk @@ -20,6 +20,7 @@ ifeq ("$(origin V)", "command line") BUILD_VERBOSE=$(V) endif ifndef BUILD_VERBOSE +$(info Use make V=1, make V=2 or set BUILD_VERBOSE similarly in your environment to increase build verbosity.) BUILD_VERBOSE = 0 endif ifeq ($(BUILD_VERBOSE),0) @@ -32,10 +33,6 @@ else Q = STEPECHO = @echo endif -# Since this is a new feature, advertise it -ifeq ($(BUILD_VERBOSE),0) -$(info Use make V=1, make V=2 or set BUILD_VERBOSE similarly in your environment to increase build verbosity.) -endif # default settings; can be overridden in main Makefile diff --git a/py/mkrules.cmake b/py/mkrules.cmake index f20240c62b..7589255b20 100644 --- a/py/mkrules.cmake +++ b/py/mkrules.cmake @@ -125,9 +125,13 @@ if(MICROPY_FROZEN_MANIFEST) MICROPY_MODULE_FROZEN_MPY=\(1\) ) + if(NOT MICROPY_LIB_DIR) + set(MICROPY_LIB_DIR ${MICROPY_DIR}/../micropython-lib) + endif() + add_custom_command( OUTPUT ${MICROPY_FROZEN_CONTENT} - COMMAND ${Python3_EXECUTABLE} ${MICROPY_DIR}/tools/makemanifest.py -o ${MICROPY_FROZEN_CONTENT} -v "MPY_DIR=${MICROPY_DIR}" -v "PORT_DIR=${MICROPY_PORT_DIR}" -b "${CMAKE_BINARY_DIR}" -f${MICROPY_CROSS_FLAGS} ${MICROPY_FROZEN_MANIFEST} + COMMAND ${Python3_EXECUTABLE} ${MICROPY_DIR}/tools/makemanifest.py -o ${MICROPY_FROZEN_CONTENT} -v "MPY_DIR=${MICROPY_DIR}" -v "MPY_LIB_DIR=${MICROPY_LIB_DIR}" -v "PORT_DIR=${MICROPY_PORT_DIR}" -v "BOARD_DIR=${MICROPY_BOARD_DIR}" -b "${CMAKE_BINARY_DIR}" -f${MICROPY_CROSS_FLAGS} ${MICROPY_FROZEN_MANIFEST} DEPENDS MICROPY_FORCE_BUILD ${MICROPY_QSTRDEFS_GENERATED} VERBATIM diff --git a/py/modbuiltins.c b/py/modbuiltins.c index 0d53a07392..f5ab07c404 100644 --- a/py/modbuiltins.c +++ b/py/modbuiltins.c @@ -374,7 +374,7 @@ STATIC mp_obj_t mp_builtin_ord(mp_obj_t o_in) { } } - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("ord expects a character")); #else mp_raise_TypeError_varg( diff --git a/py/modmicropython.c b/py/modmicropython.c index 411cb0dc2a..8f61cd2979 100644 --- a/py/modmicropython.c +++ b/py/modmicropython.c @@ -132,13 +132,13 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_micropython_heap_lock_obj, mp_micropython_he STATIC mp_obj_t mp_micropython_heap_unlock(void) { gc_unlock(); - return MP_OBJ_NEW_SMALL_INT(MP_STATE_MEM(gc_lock_depth)); + return MP_OBJ_NEW_SMALL_INT(MP_STATE_THREAD(gc_lock_depth)); } STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_micropython_heap_unlock_obj, mp_micropython_heap_unlock); #if MICROPY_PY_MICROPYTHON_HEAP_LOCKED STATIC mp_obj_t mp_micropython_heap_locked(void) { - return MP_OBJ_NEW_SMALL_INT(MP_STATE_MEM(gc_lock_depth)); + return MP_OBJ_NEW_SMALL_INT(MP_STATE_THREAD(gc_lock_depth)); } STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_micropython_heap_locked_obj, mp_micropython_heap_locked); #endif diff --git a/py/modthread.c b/py/modthread.c index 5ef5930545..fd52b9dfb5 100644 --- a/py/modthread.c +++ b/py/modthread.c @@ -173,6 +173,9 @@ STATIC void *thread_entry(void *args_in) { mp_pystack_init(mini_pystack, &mini_pystack[128]); #endif + // The GC starts off unlocked on this thread. + ts.gc_lock_depth = 0; + // set locals and globals from the calling context mp_locals_set(args->dict_locals); mp_globals_set(args->dict_globals); diff --git a/py/mpconfig.h b/py/mpconfig.h index 6f6e211345..2cd2a0d356 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -570,6 +570,12 @@ #define MICROPY_VM_HOOK_RETURN #endif +// Hook for mp_sched_schedule when a function gets scheduled on sched_queue +// (this macro executes within an atomic section) +#ifndef MICROPY_SCHED_HOOK_SCHEDULED +#define MICROPY_SCHED_HOOK_SCHEDULED +#endif + // Whether to include the garbage collector #ifndef MICROPY_ENABLE_GC #define MICROPY_ENABLE_GC (0) @@ -692,6 +698,8 @@ typedef long long mp_longint_impl_t; #define MICROPY_ENABLE_DOC_STRING (0) #endif +// Exception messages are removed (requires disabling MICROPY_ROM_TEXT_COMPRESSION) +#define MICROPY_ERROR_REPORTING_NONE (0) // Exception messages are short static strings #define MICROPY_ERROR_REPORTING_TERSE (1) // Exception messages provide basic error details @@ -1518,8 +1526,12 @@ typedef double mp_float_t; /*****************************************************************************/ /* Hooks for a port to wrap functions with attributes */ -#ifndef MICROPY_WRAP_MP_KEYBOARD_INTERRUPT -#define MICROPY_WRAP_MP_KEYBOARD_INTERRUPT(f) f +#ifndef MICROPY_WRAP_MP_SCHED_EXCEPTION +#define MICROPY_WRAP_MP_SCHED_EXCEPTION(f) f +#endif + +#ifndef MICROPY_WRAP_MP_SCHED_KEYBOARD_INTERRUPT +#define MICROPY_WRAP_MP_SCHED_KEYBOARD_INTERRUPT(f) f #endif #ifndef MICROPY_WRAP_MP_SCHED_SCHEDULE diff --git a/py/mpstate.h b/py/mpstate.h index 808e6aba0c..423463109d 100644 --- a/py/mpstate.h +++ b/py/mpstate.h @@ -82,7 +82,6 @@ typedef struct _mp_state_mem_t { int gc_stack_overflow; MICROPY_GC_STACK_ENTRY_TYPE gc_stack[MICROPY_ALLOC_GC_STACK_SIZE]; - uint16_t gc_lock_depth; // This variable controls auto garbage collection. If set to false then the // GC won't automatically run when gc_alloc can't find enough blocks. But @@ -253,6 +252,9 @@ typedef struct _mp_state_thread_t { uint8_t *pystack_cur; #endif + // Locking of the GC is done per thread. + uint16_t gc_lock_depth; + //////////////////////////////////////////////////////////// // START ROOT POINTER SECTION // Everything that needs GC scanning must start here, and diff --git a/py/nlraarch64.c b/py/nlraarch64.c index 37a860540e..9b00a2856a 100644 --- a/py/nlraarch64.c +++ b/py/nlraarch64.c @@ -50,7 +50,7 @@ __asm( "stp x27, x28, [x0, #96]\n" "str x29, [x0, #112]\n" #if defined(__APPLE__) && defined(__MACH__) - "b _nlr_push_tail \n" // do the rest in C + "b _nlr_push_tail \n" // do the rest in C #else "b nlr_push_tail \n" // do the rest in C #endif diff --git a/py/nlrx64.c b/py/nlrx64.c index f74fbd3bd0..f7e92608d2 100644 --- a/py/nlrx64.c +++ b/py/nlrx64.c @@ -58,7 +58,7 @@ unsigned int nlr_push(nlr_buf_t *nlr) { #else __asm volatile ( - #if defined(__APPLE__) || defined(__MACH__) + #if defined(__APPLE__) && defined(__MACH__) "pop %rbp \n" // undo function's prelude #endif "movq (%rsp), %rax \n" // load return %rip @@ -70,7 +70,7 @@ unsigned int nlr_push(nlr_buf_t *nlr) { "movq %r13, 56(%rdi) \n" // store %r13 into nlr_buf "movq %r14, 64(%rdi) \n" // store %r14 into nlr_buf "movq %r15, 72(%rdi) \n" // store %r15 into nlr_buf - #if defined(__APPLE__) || defined(__MACH__) + #if defined(__APPLE__) && defined(__MACH__) "jmp _nlr_push_tail \n" // do the rest in C #else "jmp nlr_push_tail \n" // do the rest in C diff --git a/py/obj.c b/py/obj.c index 616f4f2ce4..9a81d65eca 100644 --- a/py/obj.c +++ b/py/obj.c @@ -391,7 +391,7 @@ mp_float_t mp_obj_get_float(mp_obj_t arg) { mp_float_t val; if (!mp_obj_get_float_maybe(arg, &val)) { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError_varg(MP_ERROR_TEXT("can't convert to %q"), MP_QSTR_float); #else mp_raise_TypeError_varg( @@ -431,7 +431,7 @@ bool mp_obj_get_complex_maybe(mp_obj_t arg, mp_float_t *real, mp_float_t *imag) void mp_obj_get_complex(mp_obj_t arg, mp_float_t *real, mp_float_t *imag) { if (!mp_obj_get_complex_maybe(arg, real, imag)) { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("can't convert to complex")); #else mp_raise_TypeError_varg( @@ -449,7 +449,7 @@ void mp_obj_get_array(mp_obj_t o, size_t *len, mp_obj_t **items) { } else if (mp_obj_is_type(o, &mp_type_list)) { mp_obj_list_get(o, len, items); } else { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("expected tuple/list")); #else mp_raise_TypeError_varg( @@ -463,7 +463,7 @@ void mp_obj_get_array_fixed_n(mp_obj_t o, size_t len, mp_obj_t **items) { size_t seq_len; mp_obj_get_array(o, &seq_len, items); if (seq_len != len) { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_ValueError(MP_ERROR_TEXT("tuple/list has wrong length")); #else mp_raise_ValueError_varg( @@ -478,7 +478,7 @@ size_t mp_get_index(const mp_obj_type_t *type, size_t len, mp_obj_t index, bool if (mp_obj_is_small_int(index)) { i = MP_OBJ_SMALL_INT_VALUE(index); } else if (!mp_obj_get_int_maybe(index, &i)) { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("indices must be integers")); #else mp_raise_TypeError_varg( @@ -498,7 +498,7 @@ size_t mp_get_index(const mp_obj_type_t *type, size_t len, mp_obj_t index, bool } } else { if (i < 0 || (mp_uint_t)i >= len) { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_IndexError(MP_ERROR_TEXT("index out of range")); #else mp_raise_msg_varg(&mp_type_IndexError, @@ -533,7 +533,7 @@ mp_obj_t mp_obj_id(mp_obj_t o_in) { mp_obj_t mp_obj_len(mp_obj_t o_in) { mp_obj_t len = mp_obj_len_maybe(o_in); if (len == MP_OBJ_NULL) { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("object has no len")); #else mp_raise_TypeError_varg( @@ -575,21 +575,21 @@ mp_obj_t mp_obj_subscr(mp_obj_t base, mp_obj_t index, mp_obj_t value) { } } if (value == MP_OBJ_NULL) { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("object doesn't support item deletion")); #else mp_raise_TypeError_varg( MP_ERROR_TEXT("'%s' object doesn't support item deletion"), mp_obj_get_type_str(base)); #endif } else if (value == MP_OBJ_SENTINEL) { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("object isn't subscriptable")); #else mp_raise_TypeError_varg( MP_ERROR_TEXT("'%s' object isn't subscriptable"), mp_obj_get_type_str(base)); #endif } else { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("object doesn't support item assignment")); #else mp_raise_TypeError_varg( diff --git a/py/obj.h b/py/obj.h index c9135546f2..75caf459d4 100644 --- a/py/obj.h +++ b/py/obj.h @@ -792,8 +792,13 @@ extern uint64_t float_to_uint64(float f); mp_obj_t mp_obj_new_exception(const mp_obj_type_t *exc_type); mp_obj_t mp_obj_new_exception_arg1(const mp_obj_type_t *exc_type, mp_obj_t arg); mp_obj_t mp_obj_new_exception_args(const mp_obj_type_t *exc_type, size_t n_args, const mp_obj_t *args); +#if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_NONE +#define mp_obj_new_exception_msg(exc_type, msg) mp_obj_new_exception(exc_type) +#define mp_obj_new_exception_msg_varg(exc_type, ...) mp_obj_new_exception(exc_type) +#else mp_obj_t mp_obj_new_exception_msg(const mp_obj_type_t *exc_type, const compressed_string_t *msg); mp_obj_t mp_obj_new_exception_msg_varg(const mp_obj_type_t *exc_type, const compressed_string_t *fmt, ...); // counts args by number of % symbols in fmt, excluding %%; can only handle void* sizes (ie no float/double!) +#endif #ifdef va_start mp_obj_t mp_obj_new_exception_msg_vlist(const mp_obj_type_t *exc_type, const compressed_string_t *fmt, va_list ap); // counts args by number of % symbols in fmt, excluding %%; can only handle void* sizes (ie no float/double!) #endif diff --git a/py/objarray.c b/py/objarray.c index 54abe4ed91..2d363392dd 100644 --- a/py/objarray.c +++ b/py/objarray.c @@ -211,11 +211,7 @@ STATIC mp_obj_t bytearray_make_new(const mp_obj_type_t *type_in, size_t n_args, mp_obj_t mp_obj_new_memoryview(byte typecode, size_t nitems, void *items) { mp_obj_array_t *self = m_new_obj(mp_obj_array_t); - self->base.type = &mp_type_memoryview; - self->typecode = typecode; - self->memview_offset = 0; - self->len = nitems; - self->items = items; + mp_obj_memoryview_init(self, typecode, 0, nitems, items); return MP_OBJ_FROM_PTR(self); } @@ -234,6 +230,14 @@ STATIC mp_obj_t memoryview_make_new(const mp_obj_type_t *type_in, size_t n_args, bufinfo.len / mp_binary_get_size('@', bufinfo.typecode, NULL), bufinfo.buf)); + // If the input object is a memoryview then need to point the items of the + // new memoryview to the start of the buffer so the GC can trace it. + if (mp_obj_get_type(args[0]) == &mp_type_memoryview) { + mp_obj_array_t *other = MP_OBJ_TO_PTR(args[0]); + self->memview_offset = other->memview_offset; + self->items = other->items; + } + // test if the object can be written to if (mp_get_buffer(args[0], &bufinfo, MP_BUFFER_RW)) { self->typecode |= MP_OBJ_ARRAY_TYPECODE_FLAG_RW; // indicate writable buffer @@ -288,6 +292,17 @@ STATIC mp_obj_t array_unary_op(mp_unary_op_t op, mp_obj_t o_in) { } } +STATIC int typecode_for_comparison(int typecode, bool *is_unsigned) { + if (typecode == BYTEARRAY_TYPECODE) { + typecode = 'B'; + } + if (typecode <= 'Z') { + typecode += 32; // to lowercase + *is_unsigned = true; + } + return typecode; +} + STATIC mp_obj_t array_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) { mp_obj_array_t *lhs = MP_OBJ_TO_PTR(lhs_in); switch (op) { @@ -376,14 +391,33 @@ STATIC mp_obj_t array_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs return mp_const_false; } - case MP_BINARY_OP_EQUAL: { + case MP_BINARY_OP_EQUAL: + case MP_BINARY_OP_LESS: + case MP_BINARY_OP_LESS_EQUAL: + case MP_BINARY_OP_MORE: + case MP_BINARY_OP_MORE_EQUAL: { mp_buffer_info_t lhs_bufinfo; mp_buffer_info_t rhs_bufinfo; array_get_buffer(lhs_in, &lhs_bufinfo, MP_BUFFER_READ); if (!mp_get_buffer(rhs_in, &rhs_bufinfo, MP_BUFFER_READ)) { return mp_const_false; } - return mp_obj_new_bool(mp_seq_cmp_bytes(op, lhs_bufinfo.buf, lhs_bufinfo.len, rhs_bufinfo.buf, rhs_bufinfo.len)); + // mp_seq_cmp_bytes is used so only compatible representations can be correctly compared. + // The type doesn't matter: array/bytearray/str/bytes all have the same buffer layout, so + // just check if the typecodes are compatible; for testing equality the types should have the + // same code except for signedness, and not be floating point because nan never equals nan. + // For > and < the types should be the same and unsigned. + // Note that typecode_for_comparison always returns lowercase letters to save code size. + // No need for (& TYPECODE_MASK) here: xxx_get_buffer already takes care of that. + bool is_unsigned = false; + const int lhs_code = typecode_for_comparison(lhs_bufinfo.typecode, &is_unsigned); + const int rhs_code = typecode_for_comparison(rhs_bufinfo.typecode, &is_unsigned); + if (lhs_code == rhs_code && lhs_code != 'f' && lhs_code != 'd' && (op == MP_BINARY_OP_EQUAL || is_unsigned)) { + return mp_obj_new_bool(mp_seq_cmp_bytes(op, lhs_bufinfo.buf, lhs_bufinfo.len, rhs_bufinfo.buf, rhs_bufinfo.len)); + } + // mp_obj_equal_not_equal treats returning MP_OBJ_NULL as 'fall back to pointer comparison' + // for MP_BINARY_OP_EQUAL but that is incompatible with CPython. + mp_raise_NotImplementedError(NULL); } default: diff --git a/py/objexcept.c b/py/objexcept.c index aaacdd5500..cf0abe6206 100644 --- a/py/objexcept.c +++ b/py/objexcept.c @@ -367,9 +367,11 @@ mp_obj_t mp_obj_new_exception_args(const mp_obj_type_t *exc_type, size_t n_args, return exc_type->make_new(exc_type, n_args, args, NULL); } +#if MICROPY_ERROR_REPORTING != MICROPY_ERROR_REPORTING_NONE mp_obj_t mp_obj_new_exception_msg(const mp_obj_type_t *exc_type, const compressed_string_t *msg) { return mp_obj_new_exception_msg_varg(exc_type, msg); } +#endif // The following struct and function implement a simple printer that conservatively // allocates memory and truncates the output data if no more memory can be obtained. diff --git a/py/objnamedtuple.c b/py/objnamedtuple.c index b1f1f0ba4d..1d7e8a00ec 100644 --- a/py/objnamedtuple.c +++ b/py/objnamedtuple.c @@ -108,7 +108,7 @@ mp_obj_t namedtuple_make_new(const mp_obj_type_t *type_in, size_t n_args, const n_kw = kw_args->used; } if (n_args + n_kw != num_fields) { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_arg_error_terse_mismatch(); #elif MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_NORMAL mp_raise_TypeError_varg( @@ -134,7 +134,7 @@ mp_obj_t namedtuple_make_new(const mp_obj_type_t *type_in, size_t n_args, const qstr kw = mp_obj_str_get_qstr(kw_args->table[i].key); size_t id = mp_obj_namedtuple_find_field(type, kw); if (id == (size_t)-1) { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_arg_error_terse_mismatch(); #else mp_raise_TypeError_varg( @@ -142,7 +142,7 @@ mp_obj_t namedtuple_make_new(const mp_obj_type_t *type_in, size_t n_args, const #endif } if (tuple->items[id] != MP_OBJ_NULL) { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_arg_error_terse_mismatch(); #else mp_raise_TypeError_varg( diff --git a/py/objstr.c b/py/objstr.c index 2dfb2bab29..f95624960d 100644 --- a/py/objstr.c +++ b/py/objstr.c @@ -968,7 +968,7 @@ STATIC mp_obj_t arg_as_int(mp_obj_t arg) { } #endif -#if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE +#if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE STATIC NORETURN void terse_str_format_value_error(void) { mp_raise_ValueError(MP_ERROR_TEXT("bad format string")); } @@ -989,7 +989,7 @@ STATIC vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *ar vstr_add_byte(&vstr, '}'); continue; } - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else mp_raise_ValueError(MP_ERROR_TEXT("single '}' encountered in format string")); @@ -1028,7 +1028,7 @@ STATIC vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *ar if (str < top && (*str == 'r' || *str == 's')) { conversion = *str++; } else { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #elif MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_NORMAL mp_raise_ValueError(MP_ERROR_TEXT("bad conversion specifier")); @@ -1066,14 +1066,14 @@ STATIC vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *ar } } if (str >= top) { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else mp_raise_ValueError(MP_ERROR_TEXT("unmatched '{' in format")); #endif } if (*str != '}') { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else mp_raise_ValueError(MP_ERROR_TEXT("expected ':' after format specifier")); @@ -1086,7 +1086,7 @@ STATIC vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *ar int index = 0; if (MP_LIKELY(unichar_isdigit(*field_name))) { if (*arg_i > 0) { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else mp_raise_ValueError( @@ -1116,7 +1116,7 @@ STATIC vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *ar } } else { if (*arg_i < 0) { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else mp_raise_ValueError( @@ -1209,7 +1209,7 @@ STATIC vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *ar type = *s++; } if (*s) { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else mp_raise_ValueError(MP_ERROR_TEXT("invalid format specifier")); @@ -1230,14 +1230,14 @@ STATIC vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *ar if (flags & (PF_FLAG_SHOW_SIGN | PF_FLAG_SPACE_SIGN)) { if (type == 's') { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else mp_raise_ValueError(MP_ERROR_TEXT("sign not allowed in string format specifier")); #endif } if (type == 'c') { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else mp_raise_ValueError( @@ -1301,7 +1301,7 @@ STATIC vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *ar break; default: - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else mp_raise_ValueError_varg( @@ -1373,7 +1373,7 @@ STATIC vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *ar #endif default: - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else mp_raise_ValueError_varg( @@ -1385,7 +1385,7 @@ STATIC vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *ar // arg doesn't look like a number if (align == '=') { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else mp_raise_ValueError( @@ -1409,7 +1409,7 @@ STATIC vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *ar } default: - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else mp_raise_ValueError_varg( @@ -1438,7 +1438,7 @@ STATIC mp_obj_t str_modulo_format(mp_obj_t pattern, size_t n_args, const mp_obj_ mp_check_self(mp_obj_is_str_or_bytes(pattern)); GET_STR_DATA_LEN(pattern, str, len); - #if MICROPY_ERROR_REPORTING != MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING > MICROPY_ERROR_REPORTING_TERSE const byte *start_str = str; #endif bool is_bytes = mp_obj_is_type(pattern, &mp_type_bytes); @@ -1470,7 +1470,7 @@ STATIC mp_obj_t str_modulo_format(mp_obj_t pattern, size_t n_args, const mp_obj_ const byte *key = ++str; while (*str != ')') { if (str >= top) { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else mp_raise_ValueError(MP_ERROR_TEXT("incomplete format key")); @@ -1534,7 +1534,7 @@ STATIC mp_obj_t str_modulo_format(mp_obj_t pattern, size_t n_args, const mp_obj_ if (str >= top) { incomplete_format: - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else mp_raise_ValueError(MP_ERROR_TEXT("incomplete format")); @@ -1620,7 +1620,7 @@ STATIC mp_obj_t str_modulo_format(mp_obj_t pattern, size_t n_args, const mp_obj_ break; default: - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE terse_str_format_value_error(); #else mp_raise_ValueError_varg( @@ -2165,7 +2165,7 @@ bool mp_obj_str_equal(mp_obj_t s1, mp_obj_t s2) { } STATIC NORETURN void bad_implicit_conversion(mp_obj_t self_in) { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("can't convert to str implicitly")); #else const qstr src_name = mp_obj_get_type_qstr(self_in); diff --git a/py/objtype.c b/py/objtype.c index 336fcc341b..cc3168a9e6 100644 --- a/py/objtype.c +++ b/py/objtype.c @@ -373,7 +373,7 @@ mp_obj_t mp_obj_instance_make_new(const mp_obj_type_t *self, size_t n_args, cons m_del(mp_obj_t, args2, 2 + n_args + 2 * n_kw); } if (init_ret != mp_const_none) { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("__init__() should return None")); #else mp_raise_TypeError_varg(MP_ERROR_TEXT("__init__() should return None, not '%q'"), @@ -889,7 +889,7 @@ mp_obj_t mp_obj_instance_call(mp_obj_t self_in, size_t n_args, size_t n_kw, cons mp_obj_t member[2] = {MP_OBJ_NULL, MP_OBJ_NULL}; mp_obj_t call = mp_obj_instance_get_call(self_in, member); if (call == MP_OBJ_NULL) { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("object not callable")); #else mp_raise_TypeError_varg(MP_ERROR_TEXT("'%q' object is not callable"), @@ -1029,7 +1029,7 @@ STATIC mp_obj_t type_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp mp_obj_type_t *self = MP_OBJ_TO_PTR(self_in); if (self->make_new == NULL) { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("cannot create instance")); #else mp_raise_TypeError_varg(MP_ERROR_TEXT("cannot create '%q' instances"), self->name); @@ -1171,7 +1171,7 @@ mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict) mp_obj_type_t *t = MP_OBJ_TO_PTR(bases_items[i]); // TODO: Verify with CPy, tested on function type if (t->make_new == NULL) { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("type is not an acceptable base type")); #else mp_raise_TypeError_varg( diff --git a/py/parsenum.c b/py/parsenum.c index b3b79cad09..bd41488204 100644 --- a/py/parsenum.c +++ b/py/parsenum.c @@ -147,7 +147,7 @@ overflow: value_error: { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_obj_t exc = mp_obj_new_exception_msg(&mp_type_ValueError, MP_ERROR_TEXT("invalid syntax for integer")); raise_exc(exc, lex); diff --git a/py/profile.c b/py/profile.c index 9cf8c4b7ba..e5fb35f0ed 100644 --- a/py/profile.c +++ b/py/profile.c @@ -298,9 +298,7 @@ STATIC mp_obj_t mp_prof_callback_invoke(mp_obj_t callback, prof_callback_args_t mp_prof_is_executing = false; if (MP_STATE_VM(mp_pending_exception) != MP_OBJ_NULL) { - mp_obj_t obj = MP_STATE_VM(mp_pending_exception); - MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL; - nlr_raise(obj); + mp_handle_pending(true); } return top; } diff --git a/py/repl.c b/py/repl.c index 7ceae573d4..60a48b58b8 100644 --- a/py/repl.c +++ b/py/repl.c @@ -153,7 +153,7 @@ STATIC bool test_qstr(mp_obj_t obj, qstr name) { } else { // try builtin module return mp_map_lookup((mp_map_t *)&mp_builtin_module_map, - MP_OBJ_NEW_QSTR(name), MP_MAP_LOOKUP) != NULL; + MP_OBJ_NEW_QSTR(name), MP_MAP_LOOKUP); } } @@ -167,13 +167,13 @@ STATIC const char *find_completions(const char *s_start, size_t s_len, for (qstr q = MP_QSTR_ + 1; q < nqstr; ++q) { size_t d_len; const char *d_str = (const char *)qstr_data(q, &d_len); + // special case; filter out words that begin with underscore + // unless there's already a partial match + if (s_len == 0 && d_str[0] == '_') { + continue; + } if (s_len <= d_len && strncmp(s_start, d_str, s_len) == 0) { if (test_qstr(obj, q)) { - // special case; filter out words that begin with underscore - // unless there's already a partial match - if (s_len == 0 && d_str[0] == '_') { - continue; - } if (match_str == NULL) { match_str = d_str; *match_len = d_len; @@ -297,7 +297,7 @@ size_t mp_repl_autocomplete(const char *str, size_t len, const mp_print_t *print if (q_first == 0) { // If there're no better alternatives, and if it's first word // in the line, try to complete "import". - if (s_start == org_str && s_len > 0) { + if (s_start == org_str && s_len > 0 && s_len < sizeof(import_str) - 1) { if (memcmp(s_start, import_str, s_len) == 0) { *compl_str = import_str + s_len; return sizeof(import_str) - 1 - s_len; diff --git a/py/runtime.c b/py/runtime.c index bff6eb2ed5..b981b6aae8 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -159,9 +159,6 @@ void mp_deinit(void) { #ifdef MICROPY_PORT_DEINIT_FUNC MICROPY_PORT_DEINIT_FUNC; #endif - - // mp_obj_dict_free(&dict_main); - // mp_map_deinit(&MP_STATE_VM(mp_loaded_modules_map)); } mp_obj_t mp_load_name(qstr qst) { @@ -193,7 +190,7 @@ mp_obj_t mp_load_global(qstr qst) { #endif elem = mp_map_lookup((mp_map_t *)&mp_module_builtins_globals.map, MP_OBJ_NEW_QSTR(qst), MP_MAP_LOOKUP); if (elem == NULL) { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_msg(&mp_type_NameError, MP_ERROR_TEXT("name not defined")); #else mp_raise_msg_varg(&mp_type_NameError, MP_ERROR_TEXT("name '%q' is not defined"), qst); @@ -293,20 +290,20 @@ mp_obj_t mp_unary_op(mp_unary_op_t op, mp_obj_t arg) { } // With MP_UNARY_OP_INT, mp_unary_op() becomes a fallback for mp_obj_get_int(). // In this case provide a more focused error message to not confuse, e.g. chr(1.0) - if (MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE) { - if (op == MP_UNARY_OP_INT) { - mp_raise_TypeError(MP_ERROR_TEXT("can't convert to int")); - } else { - mp_raise_TypeError(MP_ERROR_TEXT("unsupported type for operator")); - } + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE + if (op == MP_UNARY_OP_INT) { + mp_raise_TypeError(MP_ERROR_TEXT("can't convert to int")); } else { - if (op == MP_UNARY_OP_INT) { - mp_raise_TypeError_varg(MP_ERROR_TEXT("can't convert %q to int"), mp_obj_get_type_qstr(arg)); - } else { - mp_raise_TypeError_varg(MP_ERROR_TEXT("unsupported type for %q: '%q'"), - mp_unary_op_method_name[op], mp_obj_get_type_qstr(arg)); - } + mp_raise_TypeError(MP_ERROR_TEXT("unsupported type for operator")); } + #else + if (op == MP_UNARY_OP_INT) { + mp_raise_TypeError_varg(MP_ERROR_TEXT("can't convert %q to int"), mp_obj_get_type_qstr(arg)); + } else { + mp_raise_TypeError_varg(MP_ERROR_TEXT("unsupported type for %q: '%q'"), + mp_unary_op_method_name[op], mp_obj_get_type_qstr(arg)); + } + #endif } } @@ -612,7 +609,7 @@ generic_binary_op: } unsupported_op: - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("unsupported type for operator")); #else mp_raise_TypeError_varg( @@ -654,7 +651,7 @@ mp_obj_t mp_call_function_n_kw(mp_obj_t fun_in, size_t n_args, size_t n_kw, cons return type->call(fun_in, n_args, n_kw, args); } - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("object not callable")); #else mp_raise_TypeError_varg(MP_ERROR_TEXT("'%q' object is not callable"), mp_obj_get_type_qstr(fun_in)); @@ -882,14 +879,14 @@ void mp_unpack_sequence(mp_obj_t seq_in, size_t num, mp_obj_t *items) { return; too_short: - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_ValueError(MP_ERROR_TEXT("wrong number of values to unpack")); #else mp_raise_ValueError_varg(MP_ERROR_TEXT("need more than %d values to unpack"), (int)seq_len); #endif too_long: - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_ValueError(MP_ERROR_TEXT("wrong number of values to unpack")); #else mp_raise_ValueError_varg(MP_ERROR_TEXT("too many values to unpack (expected %d)"), @@ -951,7 +948,7 @@ void mp_unpack_ex(mp_obj_t seq_in, size_t num_in, mp_obj_t *items) { return; too_short: - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_ValueError(MP_ERROR_TEXT("wrong number of values to unpack")); #else mp_raise_ValueError_varg(MP_ERROR_TEXT("need more than %d values to unpack"), @@ -1138,7 +1135,7 @@ void mp_load_method(mp_obj_t base, qstr attr, mp_obj_t *dest) { if (dest[0] == MP_OBJ_NULL) { // no attribute/method called attr - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_AttributeError(MP_ERROR_TEXT("no such attribute")); #else // following CPython, we give a more detailed error message for type objects @@ -1212,7 +1209,7 @@ void mp_store_attr(mp_obj_t base, qstr attr, mp_obj_t value) { } #endif } - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_AttributeError(MP_ERROR_TEXT("no such attribute")); #else mp_raise_msg_varg(&mp_type_AttributeError, @@ -1257,7 +1254,7 @@ mp_obj_t mp_getiter(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf) { } // object not iterable - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("object not iterable")); #else mp_raise_TypeError_varg( @@ -1279,7 +1276,7 @@ mp_obj_t mp_iternext_allow_raise(mp_obj_t o_in) { // __next__ exists, call it and return its result return mp_call_method_n_kw(0, 0, dest); } else { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("object not an iterator")); #else mp_raise_TypeError_varg(MP_ERROR_TEXT("'%q' object is not an iterator"), @@ -1315,7 +1312,7 @@ mp_obj_t mp_iternext(mp_obj_t o_in) { } } } else { - #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("object not an iterator")); #else mp_raise_TypeError_varg(MP_ERROR_TEXT("'%q' object is not an iterator"), @@ -1565,6 +1562,26 @@ NORETURN void mp_raise_arg1(const mp_obj_type_t *exc_type, mp_obj_t arg) { nlr_raise(mp_obj_new_exception_arg1(exc_type, arg)); } +#if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_NONE + +NORETURN void mp_raise_type(const mp_obj_type_t *exc_type) { + nlr_raise(mp_obj_new_exception(exc_type)); +} + +NORETURN void mp_raise_ValueError_no_msg(void) { + mp_raise_type(&mp_type_ValueError); +} + +NORETURN void mp_raise_TypeError_no_msg(void) { + mp_raise_type(&mp_type_TypeError); +} + +NORETURN void mp_raise_NotImplementedError_no_msg(void) { + mp_raise_type(&mp_type_NotImplementedError); +} + +#else + NORETURN void mp_raise_msg(const mp_obj_type_t *exc_type, const compressed_string_t *msg) { if (msg == NULL) { nlr_raise(mp_obj_new_exception(exc_type)); @@ -1691,6 +1708,8 @@ NORETURN void mp_raise_MpyError(const compressed_string_t *msg) { mp_raise_msg(&mp_type_MpyError, msg); } +#endif + #if MICROPY_STACK_CHECK || MICROPY_ENABLE_PYSTACK NORETURN void mp_raise_recursion_depth(void) { mp_raise_RuntimeError(MP_ERROR_TEXT("maximum recursion depth exceeded")); diff --git a/py/runtime.h b/py/runtime.h index a8a3120eef..332718b54e 100644 --- a/py/runtime.h +++ b/py/runtime.h @@ -68,7 +68,8 @@ extern const byte mp_binary_op_method_name[]; void mp_init(void); void mp_deinit(void); -void mp_keyboard_interrupt(void); +void mp_sched_exception(mp_obj_t exc); +void mp_sched_keyboard_interrupt(void); void mp_handle_pending(bool raise_exc); void mp_handle_pending_tail(mp_uint_t atomic_state); @@ -162,6 +163,17 @@ mp_obj_t mp_import_name(qstr name, mp_obj_t fromlist, mp_obj_t level); mp_obj_t mp_import_from(mp_obj_t module, qstr name); void mp_import_all(mp_obj_t module); +#if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_NONE +NORETURN void mp_raise_type(const mp_obj_type_t *exc_type); +NORETURN void mp_raise_ValueError_no_msg(void); +NORETURN void mp_raise_TypeError_no_msg(void); +NORETURN void mp_raise_NotImplementedError_no_msg(void); +#define mp_raise_msg(exc_type, msg) mp_raise_type(exc_type) +#define mp_raise_msg_varg(exc_type, ...) mp_raise_type(exc_type) +#define mp_raise_ValueError(msg) mp_raise_ValueError_no_msg() +#define mp_raise_TypeError(msg) mp_raise_TypeError_no_msg() +#define mp_raise_NotImplementedError(msg) mp_raise_NotImplementedError_no_msg() +#else #define mp_raise_type(exc_type) mp_raise_msg(exc_type, NULL) #if !(defined(MICROPY_ENABLE_DYNRUNTIME) && MICROPY_ENABLE_DYNRUNTIME) NORETURN void mp_raise_arg1(const mp_obj_type_t *exc_type, mp_obj_t arg); @@ -191,6 +203,7 @@ NORETURN void mp_raise_NotImplementedError_varg(const compressed_string_t *fmt, NORETURN void mp_raise_OverflowError_varg(const compressed_string_t *fmt, ...); NORETURN void mp_raise_MpyError(const compressed_string_t *msg); NORETURN void mp_raise_recursion_depth(void); +#endif #if MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG #undef mp_check_self diff --git a/py/scheduler.c b/py/scheduler.c index 8329010417..f11317dc1d 100644 --- a/py/scheduler.c +++ b/py/scheduler.c @@ -28,17 +28,21 @@ #include "py/runtime.h" -#if MICROPY_KBD_EXCEPTION -// This function may be called asynchronously at any time so only do the bare minimum. -void MICROPY_WRAP_MP_KEYBOARD_INTERRUPT(mp_keyboard_interrupt)(void) { - MP_STATE_VM(mp_kbd_exception).traceback_data = NULL; - MP_STATE_VM(mp_pending_exception) = MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_kbd_exception)); +void MICROPY_WRAP_MP_SCHED_EXCEPTION(mp_sched_exception)(mp_obj_t exc) { + MP_STATE_VM(mp_pending_exception) = exc; #if MICROPY_ENABLE_SCHEDULER if (MP_STATE_VM(sched_state) == MP_SCHED_IDLE) { MP_STATE_VM(sched_state) = MP_SCHED_PENDING; } #endif } + +#if MICROPY_KBD_EXCEPTION +// This function may be called asynchronously at any time so only do the bare minimum. +void MICROPY_WRAP_MP_SCHED_KEYBOARD_INTERRUPT(mp_sched_keyboard_interrupt)(void) { + MP_STATE_VM(mp_kbd_exception).traceback_data = NULL; + mp_sched_exception(MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_kbd_exception))); +} #endif #if MICROPY_ENABLE_SCHEDULER @@ -130,6 +134,7 @@ bool MICROPY_WRAP_MP_SCHED_SCHEDULE(mp_sched_schedule)(mp_obj_t function, mp_obj uint8_t iput = IDX_MASK(MP_STATE_VM(sched_idx) + MP_STATE_VM(sched_len)++); MP_STATE_VM(sched_queue)[iput].func = function; MP_STATE_VM(sched_queue)[iput].arg = arg; + MICROPY_SCHED_HOOK_SCHEDULED; ret = true; } else { // schedule queue is full diff --git a/py/stackctrl.h b/py/stackctrl.h index 6b003f6c28..a3dfadb05a 100644 --- a/py/stackctrl.h +++ b/py/stackctrl.h @@ -40,7 +40,7 @@ void mp_stack_check(void); #else -#define mp_stack_set_limit(limit) +#define mp_stack_set_limit(limit) (void)(limit) #define MP_STACK_CHECK() #endif diff --git a/tests/basics/array1.py b/tests/basics/array1.py index 5b3f475786..f21ad4bd75 100644 --- a/tests/basics/array1.py +++ b/tests/basics/array1.py @@ -41,14 +41,23 @@ except ValueError: # equality (CPython requires both sides are array) print(bytes(array.array('b', [0x61, 0x62, 0x63])) == b'abc') print(array.array('b', [0x61, 0x62, 0x63]) == b'abc') +print(array.array('B', [0x61, 0x62, 0x63]) == b'abc') print(array.array('b', [0x61, 0x62, 0x63]) != b'abc') print(array.array('b', [0x61, 0x62, 0x63]) == b'xyz') print(array.array('b', [0x61, 0x62, 0x63]) != b'xyz') print(b'abc' == array.array('b', [0x61, 0x62, 0x63])) +print(b'abc' == array.array('B', [0x61, 0x62, 0x63])) print(b'abc' != array.array('b', [0x61, 0x62, 0x63])) print(b'xyz' == array.array('b', [0x61, 0x62, 0x63])) print(b'xyz' != array.array('b', [0x61, 0x62, 0x63])) +compatible_typecodes = [] +for t in ["b", "h", "i", "l", "q"]: + compatible_typecodes.append((t, t)) + compatible_typecodes.append((t, t.upper())) +for a, b in compatible_typecodes: + print(array.array(a, [1, 2]) == array.array(b, [1, 2])) + class X(array.array): pass @@ -57,3 +66,24 @@ print(X('b', [0x61, 0x62, 0x63]) == b'abc') print(X('b', [0x61, 0x62, 0x63]) != b'abc') print(X('b', [0x61, 0x62, 0x63]) == array.array('b', [0x61, 0x62, 0x63])) print(X('b', [0x61, 0x62, 0x63]) != array.array('b', [0x61, 0x62, 0x63])) + +# other comparisons +for typecode in ["B", "H", "I", "L", "Q"]: + a = array.array(typecode, [1, 1]) + print(a < a) + print(a <= a) + print(a > a) + print(a >= a) + + al = array.array(typecode, [1, 0]) + ab = array.array(typecode, [1, 2]) + + print(a < al) + print(a <= al) + print(a > al) + print(a >= al) + + print(a < ab) + print(a <= ab) + print(a > ab) + print(a >= ab) diff --git a/tests/basics/array_micropython.py b/tests/basics/array_micropython.py index 6b3dc7a93b..44dc1d83d8 100644 --- a/tests/basics/array_micropython.py +++ b/tests/basics/array_micropython.py @@ -17,3 +17,15 @@ print(a[0]) a = array.array('P') a.append(1) print(a[0]) + +# comparison between mismatching binary layouts is not implemented +typecodes = ["b", "h", "i", "l", "q", "P", "O", "S", "f", "d"] +for a in typecodes: + for b in typecodes: + if a == b and a not in ["f", "d"]: + continue + try: + array.array(a) == array.array(b) + print('FAIL') + except NotImplementedError: + pass diff --git a/tests/basics/async_syntaxerror.py b/tests/basics/async_syntaxerror.py new file mode 100644 index 0000000000..ddd2c4b59e --- /dev/null +++ b/tests/basics/async_syntaxerror.py @@ -0,0 +1,19 @@ +# test syntax errors using async + +try: + exec +except NameError: + print("SKIP") + raise SystemExit + + +def test_syntax(code): + try: + exec(code) + print("no SyntaxError") + except SyntaxError: + print("SyntaxError") + + +test_syntax("async for x in (): x") +test_syntax("async with x: x") diff --git a/tests/basics/async_syntaxerror.py.exp b/tests/basics/async_syntaxerror.py.exp new file mode 100644 index 0000000000..5275689b41 --- /dev/null +++ b/tests/basics/async_syntaxerror.py.exp @@ -0,0 +1,2 @@ +SyntaxError +SyntaxError diff --git a/tests/basics/bytearray1.py b/tests/basics/bytearray1.py index b598500264..d12292e879 100644 --- a/tests/basics/bytearray1.py +++ b/tests/basics/bytearray1.py @@ -27,6 +27,26 @@ print(bytearray([1]) == b"1") print(b"1" == bytearray([1])) print(bytearray() == bytearray()) +b1 = bytearray([1, 2, 3]) +b2 = bytearray([1, 2, 3]) +b3 = bytearray([1, 3]) +print(b1 == b2) +print(b2 != b3) +print(b1 <= b2) +print(b1 <= b3) +print(b1 < b3) +print(b1 >= b2) +print(b3 >= b2) +print(b3 > b2) +print(b1 != b2) +print(b2 == b3) +print(b1 > b2) +print(b1 > b3) +print(b1 >= b3) +print(b1 < b2) +print(b3 < b2) +print(b3 <= b2) + # comparison with other type should return False print(bytearray() == 1) diff --git a/tests/basics/exception1.py b/tests/basics/exception1.py index d83764cb93..a0ca8a74e2 100644 --- a/tests/basics/exception1.py +++ b/tests/basics/exception1.py @@ -1,3 +1,5 @@ +# test basic properties of exceptions + print(repr(IndexError())) print(str(IndexError())) @@ -12,3 +14,6 @@ s = StopIteration() print(s.value) s = StopIteration(1, 2, 3) print(s.value) + +print(OSError().errno) +print(OSError(1, "msg").errno) diff --git a/tests/basics/is_isnot.py b/tests/basics/is_isnot.py index 990190aa41..55459cb0a6 100644 --- a/tests/basics/is_isnot.py +++ b/tests/basics/is_isnot.py @@ -1,14 +1,4 @@ -print(1 is 1) -print(1 is 2) -print(1 is not 1) -print(1 is not 2) - - print([1, 2] is [1, 2]) a = [1, 2] b = a print(b is a) - -# TODO: strings require special "is" handling, postponed -# until qstr refactor. -#print("a" is "a") diff --git a/tests/basics/is_isnot_literal.py b/tests/basics/is_isnot_literal.py new file mode 100644 index 0000000000..1626fa949f --- /dev/null +++ b/tests/basics/is_isnot_literal.py @@ -0,0 +1,13 @@ +# test "is" and "is not" with literal arguments +# these raise a SyntaxWarning in CPython because the results are +# implementation dependent; see https://bugs.python.org/issue34850 + +print(1 is 1) +print(1 is 2) +print(1 is not 1) +print(1 is not 2) + +print("a" is "a") +print("a" is "b") +print("a" is not "a") +print("a" is not "b") diff --git a/tests/basics/is_isnot_literal.py.exp b/tests/basics/is_isnot_literal.py.exp new file mode 100644 index 0000000000..fc4b8f8129 --- /dev/null +++ b/tests/basics/is_isnot_literal.py.exp @@ -0,0 +1,8 @@ +True +False +False +True +True +False +False +True diff --git a/tests/basics/memoryview_gc.py b/tests/basics/memoryview_gc.py index d366cbbb15..5cd6124ada 100644 --- a/tests/basics/memoryview_gc.py +++ b/tests/basics/memoryview_gc.py @@ -21,3 +21,13 @@ for i in range(100000): # check that the memoryview is still what we want print(list(m)) + +# check that creating a memoryview of a memoryview retains the underlying data +m = None +gc.collect() # cleanup from previous test +m = memoryview(memoryview(bytearray(i for i in range(50)))[5:-5]) +print(sum(m), list(m[:10])) +gc.collect() +for i in range(10): + list(range(10)) # allocate memory to overwrite any reclaimed heap +print(sum(m), list(m[:10])) diff --git a/tests/basics/op_error.py b/tests/basics/op_error.py index 63c35db3f5..98a3ab025a 100644 --- a/tests/basics/op_error.py +++ b/tests/basics/op_error.py @@ -29,18 +29,10 @@ except TypeError: print('TypeError') # unsupported subscription -try: - 1[0] -except TypeError: - print('TypeError') try: 1[0] = 1 except TypeError: print('TypeError') -try: - ''[''] -except TypeError: - print('TypeError') try: 'a'[0] = 1 except TypeError: @@ -50,12 +42,6 @@ try: except TypeError: print('TypeError') -# not callable -try: - 1() -except TypeError: - print('TypeError') - # not an iterator try: next(1) diff --git a/tests/basics/op_error_literal.py b/tests/basics/op_error_literal.py new file mode 100644 index 0000000000..00244ee2b2 --- /dev/null +++ b/tests/basics/op_error_literal.py @@ -0,0 +1,18 @@ +# test errors from bad operations with literals +# these raise a SyntaxWarning in CPython; see https://bugs.python.org/issue15248 + +# unsupported subscription +try: + 1[0] +except TypeError: + print("TypeError") +try: + ""[""] +except TypeError: + print("TypeError") + +# not callable +try: + 1() +except TypeError: + print("TypeError") diff --git a/tests/basics/op_error_literal.py.exp b/tests/basics/op_error_literal.py.exp new file mode 100644 index 0000000000..a8ea4fb10b --- /dev/null +++ b/tests/basics/op_error_literal.py.exp @@ -0,0 +1,3 @@ +TypeError +TypeError +TypeError diff --git a/tests/basics/subclass_native3.py b/tests/basics/subclass_native3.py index 6745b77bb2..ac5aabfed7 100644 --- a/tests/basics/subclass_native3.py +++ b/tests/basics/subclass_native3.py @@ -1,6 +1,10 @@ +# test subclassing a native exception + + class MyExc(Exception): pass + e = MyExc(100, "Some error") print(e) print(repr(e)) @@ -20,3 +24,19 @@ try: raise MyExc("Some error2") except: print("Caught user exception") + + +class MyStopIteration(StopIteration): + pass + + +print(MyStopIteration().value) +print(MyStopIteration(1).value) + + +class MyOSError(OSError): + pass + + +print(MyOSError().errno) +print(MyOSError(1, "msg").errno) diff --git a/tests/cpydiff/core_function_moduleattr.py b/tests/cpydiff/core_function_moduleattr.py new file mode 100644 index 0000000000..71747c1f40 --- /dev/null +++ b/tests/cpydiff/core_function_moduleattr.py @@ -0,0 +1,13 @@ +""" +categories: Core,Functions +description: Function objects do not have the ``__module__`` attribute +cause: MicroPython is optimized for reduced code size and RAM usage. +workaround: Use ``sys.modules[function.__globals__['__name__']]`` for non-builtin modules. +""" + + +def f(): + pass + + +print(f.__module__) diff --git a/tests/cpydiff/module_array_comparison.py b/tests/cpydiff/module_array_comparison.py new file mode 100644 index 0000000000..a442af3f5b --- /dev/null +++ b/tests/cpydiff/module_array_comparison.py @@ -0,0 +1,9 @@ +""" +categories: Modules,array +description: Comparison between different typecodes not supported +cause: Code size +workaround: Compare individual elements +""" +import array + +array.array("b", [1, 2]) == array.array("i", [1, 2]) diff --git a/tests/cpydiff/module_array_constructor.py b/tests/cpydiff/module_array_constructor.py new file mode 100644 index 0000000000..08cf2ef2d1 --- /dev/null +++ b/tests/cpydiff/module_array_constructor.py @@ -0,0 +1,10 @@ +""" +categories: Modules,array +description: Overflow checking is not implemented +cause: MicroPython implements implicit truncation in order to reduce code size and execution time +workaround: If CPython compatibility is needed then mask the value explicitly +""" +import array + +a = array.array("b", [257]) +print(a) diff --git a/tests/cpydiff/modules_random_getrandbits.py b/tests/cpydiff/modules_random_getrandbits.py new file mode 100644 index 0000000000..523e3a329d --- /dev/null +++ b/tests/cpydiff/modules_random_getrandbits.py @@ -0,0 +1,12 @@ +""" +categories: Modules,random +description: ``getrandbits`` method can only return a maximum of 32 bits at a time. +cause: PRNG's internal state is only 32bits so it can only return a maximum of 32 bits of data at a time. +workaround: If you need a number that has more than 32 bits then utilize the random module from micropython-lib. +""" + +import random + + +x = random.getrandbits(64) +print("{}".format(x)) diff --git a/tests/cpydiff/modules_random_randint.py b/tests/cpydiff/modules_random_randint.py new file mode 100644 index 0000000000..b05908a157 --- /dev/null +++ b/tests/cpydiff/modules_random_randint.py @@ -0,0 +1,12 @@ +""" +categories: Modules,random +description: ``randint`` method can only return an integer that is at most the native word size. +cause: PRNG is only able to generate 32 bits of state at a time. The result is then cast into a native sized int instead of a full int object. +workaround: If you need integers larger than native wordsize use the random module from micropython-lib. +""" + +import random + + +x = random.randint(2 ** 128 - 1, 2 ** 128) +print("x={}".format(x)) diff --git a/tests/cpydiff/types_exception_attrs.py b/tests/cpydiff/types_exception_attrs.py new file mode 100644 index 0000000000..ad72b62a61 --- /dev/null +++ b/tests/cpydiff/types_exception_attrs.py @@ -0,0 +1,9 @@ +""" +categories: Types,Exception +description: All exceptions have readable ``value`` and ``errno`` attributes, not just ``StopIteration`` and ``OSError``. +cause: MicroPython is optimised to reduce code size. +workaround: Only use ``value`` on ``StopIteration`` exceptions, and ``errno`` on ``OSError`` exceptions. Do not use or rely on these attributes on other exceptions. +""" +e = Exception(1) +print(e.value) +print(e.errno) diff --git a/tests/cpydiff/types_int_bit_length.py b/tests/cpydiff/types_int_bit_length.py new file mode 100644 index 0000000000..2e907745a9 --- /dev/null +++ b/tests/cpydiff/types_int_bit_length.py @@ -0,0 +1,9 @@ +""" +categories: Types,int +description: ``bit_length`` method doesn't exist. +cause: bit_length method is not implemented. +workaround: Avoid using this method on MicroPython. +""" + +x = 255 +print("{} is {} bits long.".format(x, x.bit_length())) diff --git a/tests/extmod/btree1.py b/tests/extmod/btree1.py index 4890d92b42..1f4853eaa3 100644 --- a/tests/extmod/btree1.py +++ b/tests/extmod/btree1.py @@ -65,7 +65,7 @@ print(db.seq(1, b"qux")) try: db.seq(b"foo1") except OSError as e: - print(e.args[0] == uerrno.EINVAL) + print(e.errno == uerrno.EINVAL) print(list(db.keys())) print(list(db.values())) diff --git a/tests/extmod/btree_error.py b/tests/extmod/btree_error.py index 00e07ec8c7..b64769e884 100644 --- a/tests/extmod/btree_error.py +++ b/tests/extmod/btree_error.py @@ -27,7 +27,7 @@ class Device(uio.IOBase): try: db = btree.open(Device(), pagesize=511) except OSError as er: - print("OSError", er.args[0] == uerrno.EINVAL) + print("OSError", er.errno == uerrno.EINVAL) # Valid pagesize, device returns error on read; errno comes from Device.readinto try: diff --git a/tests/extmod/btree_gc.py b/tests/extmod/btree_gc.py index 153f4e7d78..1845aa0640 100644 --- a/tests/extmod/btree_gc.py +++ b/tests/extmod/btree_gc.py @@ -21,3 +21,6 @@ for i in range(N): db[b"thekey" + str(i)] = b"thelongvalue" + str(i) print(db[b"thekey" + str(i)]) gc.collect() + +# Reclaim memory allocated by the db object. +db.close() diff --git a/tests/extmod/framebuf16.py b/tests/extmod/framebuf16.py index e658f1345a..cd7f5ec015 100644 --- a/tests/extmod/framebuf16.py +++ b/tests/extmod/framebuf16.py @@ -1,9 +1,14 @@ try: - import framebuf + import framebuf, usys except ImportError: print("SKIP") raise SystemExit +# This test and its .exp file is based on a little-endian architecture. +if usys.byteorder != "little": + print("SKIP") + raise SystemExit + def printbuf(): print("--8<--") diff --git a/tests/extmod/framebuf_subclass.py b/tests/extmod/framebuf_subclass.py index aad5d2a1e9..a9e3c5efc7 100644 --- a/tests/extmod/framebuf_subclass.py +++ b/tests/extmod/framebuf_subclass.py @@ -1,11 +1,16 @@ # test subclassing framebuf.FrameBuffer try: - import framebuf + import framebuf, usys except ImportError: print("SKIP") raise SystemExit +# This test and its .exp file is based on a little-endian architecture. +if usys.byteorder != "little": + print("SKIP") + raise SystemExit + class FB(framebuf.FrameBuffer): def __init__(self, n): diff --git a/tests/extmod/uasyncio_cancel_wait_on_finished.py b/tests/extmod/uasyncio_cancel_wait_on_finished.py new file mode 100644 index 0000000000..66b36dd60d --- /dev/null +++ b/tests/extmod/uasyncio_cancel_wait_on_finished.py @@ -0,0 +1,41 @@ +# Test cancelling a task that is waiting on a task that just finishes. + +try: + import uasyncio as asyncio +except ImportError: + try: + import asyncio + except ImportError: + print("SKIP") + raise SystemExit + + +async def sleep_task(): + print("sleep_task sleep") + await asyncio.sleep(0) + print("sleep_task wake") + + +async def wait_task(t): + print("wait_task wait") + await t + print("wait_task wake") + + +async def main(): + waiting_task = asyncio.create_task(wait_task(asyncio.create_task(sleep_task()))) + + print("main sleep") + await asyncio.sleep(0) + print("main sleep") + await asyncio.sleep(0) + + waiting_task.cancel() + print("main wait") + try: + await waiting_task + except asyncio.CancelledError as er: + print(repr(er)) + + +asyncio.run(main()) diff --git a/tests/extmod/uasyncio_cancel_wait_on_finished.py.exp b/tests/extmod/uasyncio_cancel_wait_on_finished.py.exp new file mode 100644 index 0000000000..60e871bfe5 --- /dev/null +++ b/tests/extmod/uasyncio_cancel_wait_on_finished.py.exp @@ -0,0 +1,7 @@ +main sleep +sleep_task sleep +wait_task wait +main sleep +sleep_task wake +main wait +CancelledError() diff --git a/tests/extmod/uctypes_le_float.py b/tests/extmod/uctypes_le_float.py index 89e9a9e0ab..5255632c91 100644 --- a/tests/extmod/uctypes_le_float.py +++ b/tests/extmod/uctypes_le_float.py @@ -22,3 +22,19 @@ print("%.4f" % S.f64) S.uf64 = 12.34 print("%.4f" % S.uf64) + +# array of float/double +desc = { + "af32": (uctypes.ARRAY | 0, uctypes.FLOAT32 | 2), + "af64": (uctypes.ARRAY | 0, uctypes.FLOAT64 | 2), +} +data = bytearray(16) +S = uctypes.struct(uctypes.addressof(data), desc, uctypes.LITTLE_ENDIAN) + +S.af32[0] = 1 +S.af32[1] = 2 +print("%.4f %.4f" % (S.af32[0], S.af32[1]), data) + +S.af64[0] = 1 +S.af64[1] = 2 +print("%.4f %.4f" % (S.af64[0], S.af64[1]), data) diff --git a/tests/extmod/uctypes_le_float.py.exp b/tests/extmod/uctypes_le_float.py.exp index a35a1da2dc..e93fcb0d97 100644 --- a/tests/extmod/uctypes_le_float.py.exp +++ b/tests/extmod/uctypes_le_float.py.exp @@ -1,3 +1,5 @@ 12.3400 12.3400 12.3400 +1.0000 2.0000 bytearray(b'\x00\x00\x80?\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x00\x00') +1.0000 2.0000 bytearray(b'\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00@') diff --git a/tests/extmod/uctypes_sizeof_float.py b/tests/extmod/uctypes_sizeof_float.py index 351632d76b..f1a4c88e2d 100644 --- a/tests/extmod/uctypes_sizeof_float.py +++ b/tests/extmod/uctypes_sizeof_float.py @@ -6,3 +6,5 @@ except ImportError: print(uctypes.sizeof({"f": uctypes.FLOAT32})) print(uctypes.sizeof({"f": uctypes.FLOAT64})) +print(uctypes.sizeof({"f": (uctypes.ARRAY | 0, uctypes.FLOAT32 | 2)})) +print(uctypes.sizeof({"f": (uctypes.ARRAY | 0, uctypes.FLOAT64 | 2)})) diff --git a/tests/extmod/uctypes_sizeof_float.py.exp b/tests/extmod/uctypes_sizeof_float.py.exp index de78180725..82776b54ab 100644 --- a/tests/extmod/uctypes_sizeof_float.py.exp +++ b/tests/extmod/uctypes_sizeof_float.py.exp @@ -1,2 +1,4 @@ 4 8 +8 +16 diff --git a/tests/extmod/uhashlib_final.py b/tests/extmod/uhashlib_final.py new file mode 100644 index 0000000000..f562cc1780 --- /dev/null +++ b/tests/extmod/uhashlib_final.py @@ -0,0 +1,35 @@ +try: + import uhashlib +except ImportError: + print("SKIP") + raise SystemExit + + +for algo_name in ("md5", "sha1", "sha256"): + algo = getattr(uhashlib, algo_name, None) + if not algo: + continue + + # Running .digest() several times in row is not supported. + h = algo(b"123") + h.digest() + try: + h.digest() + print("fail") + except ValueError: + # Expected path, don't print anything so test output is the + # same even if the algorithm is not implemented on the port. + pass + + # Partial digests are not supported. + h = algo(b"123") + h.digest() + try: + h.update(b"456") + print("fail") + except ValueError: + # Expected path, don't print anything so test output is the + # same even if the algorithm is not implemented on the port. + pass + +print("done") diff --git a/tests/extmod/uhashlib_final.py.exp b/tests/extmod/uhashlib_final.py.exp new file mode 100644 index 0000000000..19f86f493a --- /dev/null +++ b/tests/extmod/uhashlib_final.py.exp @@ -0,0 +1 @@ +done diff --git a/tests/extmod/urandom_basic.py b/tests/extmod/urandom_basic.py index 180197398f..f7f5a6d691 100644 --- a/tests/extmod/urandom_basic.py +++ b/tests/extmod/urandom_basic.py @@ -22,8 +22,11 @@ r = random.getrandbits(16) random.seed(1) print(random.getrandbits(16) == r) -# check that it throws an error for zero bits +# check that zero bits works +print(random.getrandbits(0)) + +# check that it throws an error for negative bits try: - random.getrandbits(0) + random.getrandbits(-1) except ValueError: print("ValueError") diff --git a/tests/extmod/urandom_basic.py.exp b/tests/extmod/urandom_basic.py.exp new file mode 100644 index 0000000000..d629828d79 --- /dev/null +++ b/tests/extmod/urandom_basic.py.exp @@ -0,0 +1,4 @@ +True +True +0 +ValueError diff --git a/tests/extmod/uselect_poll_basic.py b/tests/extmod/uselect_poll_basic.py index 07328365b3..97fbd6fd15 100644 --- a/tests/extmod/uselect_poll_basic.py +++ b/tests/extmod/uselect_poll_basic.py @@ -33,7 +33,7 @@ poller.unregister(s) try: poller.modify(s, select.POLLIN) except OSError as e: - assert e.args[0] == errno.ENOENT + assert e.errno == errno.ENOENT # poll after closing the socket, should return POLLNVAL poller.register(s) diff --git a/tests/extmod/usocket_tcp_basic.py b/tests/extmod/usocket_tcp_basic.py index 368dfe3c98..c2fe8cd14c 100644 --- a/tests/extmod/usocket_tcp_basic.py +++ b/tests/extmod/usocket_tcp_basic.py @@ -14,4 +14,4 @@ s = socket.socket() try: s.recv(1) except OSError as er: - print("ENOTCONN:", er.args[0] == errno.ENOTCONN) + print("ENOTCONN:", er.errno == errno.ENOTCONN) diff --git a/tests/extmod/usocket_udp_nonblock.py b/tests/extmod/usocket_udp_nonblock.py index 7dc0e562a3..bc560de142 100644 --- a/tests/extmod/usocket_udp_nonblock.py +++ b/tests/extmod/usocket_udp_nonblock.py @@ -17,4 +17,4 @@ s.settimeout(0) try: s.recv(1) except OSError as er: - print("EAGAIN:", er.args[0] == errno.EAGAIN) + print("EAGAIN:", er.errno == errno.EAGAIN) diff --git a/tests/extmod/vfs_fat_fileio1.py b/tests/extmod/vfs_fat_fileio1.py index 18a9690d41..10e92d6940 100644 --- a/tests/extmod/vfs_fat_fileio1.py +++ b/tests/extmod/vfs_fat_fileio1.py @@ -59,22 +59,22 @@ f.close() # allowed try: f.write("world!") except OSError as e: - print(e.args[0] == uerrno.EINVAL) + print(e.errno == uerrno.EINVAL) try: f.read() except OSError as e: - print(e.args[0] == uerrno.EINVAL) + print(e.errno == uerrno.EINVAL) try: f.flush() except OSError as e: - print(e.args[0] == uerrno.EINVAL) + print(e.errno == uerrno.EINVAL) try: open("foo_file.txt", "x") except OSError as e: - print(e.args[0] == uerrno.EEXIST) + print(e.errno == uerrno.EEXIST) with open("foo_file.txt", "a") as f: f.write("world!") @@ -106,7 +106,7 @@ vfs.mkdir("foo_dir") try: vfs.rmdir("foo_file.txt") except OSError as e: - print(e.args[0] == 20) # uerrno.ENOTDIR + print(e.errno == 20) # uerrno.ENOTDIR vfs.remove("foo_file.txt") print(list(vfs.ilistdir())) diff --git a/tests/extmod/vfs_fat_fileio2.py b/tests/extmod/vfs_fat_fileio2.py index e83cfd9204..297d75a481 100644 --- a/tests/extmod/vfs_fat_fileio2.py +++ b/tests/extmod/vfs_fat_fileio2.py @@ -53,22 +53,22 @@ uos.chdir("/ramdisk") try: vfs.mkdir("foo_dir") except OSError as e: - print(e.args[0] == uerrno.EEXIST) + print(e.errno == uerrno.EEXIST) try: vfs.remove("foo_dir") except OSError as e: - print(e.args[0] == uerrno.EISDIR) + print(e.errno == uerrno.EISDIR) try: vfs.remove("no_file.txt") except OSError as e: - print(e.args[0] == uerrno.ENOENT) + print(e.errno == uerrno.ENOENT) try: vfs.rename("foo_dir", "/null/file") except OSError as e: - print(e.args[0] == uerrno.ENOENT) + print(e.errno == uerrno.ENOENT) # file in dir with open("foo_dir/file-in-dir.txt", "w+t") as f: @@ -84,7 +84,7 @@ with open("foo_dir/sub_file.txt", "w") as f: try: vfs.rmdir("foo_dir") except OSError as e: - print(e.args[0] == uerrno.EACCES) + print(e.errno == uerrno.EACCES) # trim full path vfs.rename("foo_dir/file-in-dir.txt", "foo_dir/file.txt") @@ -113,5 +113,5 @@ try: f = open("large_file.txt", "wb") f.write(bytearray(bsize * free)) except OSError as e: - print("ENOSPC:", e.args[0] == 28) # uerrno.ENOSPC + print("ENOSPC:", e.errno == 28) # uerrno.ENOSPC f.close() diff --git a/tests/extmod/vfs_fat_more.py b/tests/extmod/vfs_fat_more.py index 8eba6a08db..1b7b04ee63 100644 --- a/tests/extmod/vfs_fat_more.py +++ b/tests/extmod/vfs_fat_more.py @@ -92,7 +92,7 @@ for exist in ("", "/", "dir", "/dir", "dir/subdir"): try: uos.mkdir(exist) except OSError as er: - print("mkdir OSError", er.args[0] == 17) # EEXIST + print("mkdir OSError", er.errno == 17) # EEXIST uos.chdir("/") print(uos.stat("test5.txt")[:-3]) diff --git a/tests/extmod/vfs_fat_ramdisk.py b/tests/extmod/vfs_fat_ramdisk.py index 439becc60c..4b79391102 100644 --- a/tests/extmod/vfs_fat_ramdisk.py +++ b/tests/extmod/vfs_fat_ramdisk.py @@ -61,7 +61,7 @@ print("getcwd:", vfs.getcwd()) try: vfs.stat("no_file.txt") except OSError as e: - print(e.args[0] == uerrno.ENOENT) + print(e.errno == uerrno.ENOENT) with vfs.open("foo_file.txt", "w") as f: f.write("hello!") @@ -84,7 +84,7 @@ with vfs.open("sub_file.txt", "w") as f: try: vfs.chdir("sub_file.txt") except OSError as e: - print(e.args[0] == uerrno.ENOENT) + print(e.errno == uerrno.ENOENT) vfs.chdir("..") print("getcwd:", vfs.getcwd()) @@ -98,4 +98,4 @@ print(list(vfs.ilistdir(b""))) try: vfs.ilistdir(b"no_exist") except OSError as e: - print("ENOENT:", e.args[0] == uerrno.ENOENT) + print("ENOENT:", e.errno == uerrno.ENOENT) diff --git a/tests/float/bytearray_construct.py b/tests/float/bytearray_construct_endian.py similarity index 85% rename from tests/float/bytearray_construct.py rename to tests/float/bytearray_construct_endian.py index 257d37d1be..47f2b793c0 100644 --- a/tests/float/bytearray_construct.py +++ b/tests/float/bytearray_construct_endian.py @@ -9,4 +9,4 @@ except ImportError: print("SKIP") raise SystemExit -print(bytearray(array("f", [1, 2.3]))) +print(bytearray(array("f", [1, 2.5]))) diff --git a/tests/float/bytes_construct.py b/tests/float/bytes_construct_endian.py similarity index 63% rename from tests/float/bytes_construct.py rename to tests/float/bytes_construct_endian.py index 0806087b0e..4e15acc8bc 100644 --- a/tests/float/bytes_construct.py +++ b/tests/float/bytes_construct_endian.py @@ -1,4 +1,4 @@ -# test construction of bytearray from array with float type +# test construction of bytes from array with float type try: from uarray import array @@ -9,4 +9,4 @@ except ImportError: print("SKIP") raise SystemExit -print(bytes(array("f", [1, 2.3]))) +print(bytes(array("f", [1, 2.5]))) diff --git a/tests/float/float_array.py b/tests/float/float_array.py index 3c2189869b..219b6b86ae 100644 --- a/tests/float/float_array.py +++ b/tests/float/float_array.py @@ -22,4 +22,4 @@ def test(a): test(array("f")) test(array("d")) -print("{:.4f}".format(array("f", b"\xcc\xcc\xcc=")[0])) +print("{:.4f}".format(array("f", bytes(array("I", [0x3DCCCCCC])))[0])) diff --git a/tests/import/import_pkg7.py.exp b/tests/import/import_pkg7.py.exp new file mode 100644 index 0000000000..8f21a615f6 --- /dev/null +++ b/tests/import/import_pkg7.py.exp @@ -0,0 +1,8 @@ +pkg __name__: pkg7 +pkg __name__: pkg7.subpkg1 +pkg __name__: pkg7.subpkg1.subpkg2 +mod1 +mod2 +mod1.foo +mod2.bar +ImportError diff --git a/tests/import/pkg7/subpkg1/subpkg2/mod3.py b/tests/import/pkg7/subpkg1/subpkg2/mod3.py index 0aa916d208..b0f4279fcf 100644 --- a/tests/import/pkg7/subpkg1/subpkg2/mod3.py +++ b/tests/import/pkg7/subpkg1/subpkg2/mod3.py @@ -7,5 +7,5 @@ print(bar) # attempted relative import beyond top-level package try: from .... import mod1 -except ValueError: - print("ValueError") +except ImportError: + print("ImportError") diff --git a/tests/micropython/viper_misc2.py b/tests/micropython/viper_misc2.py new file mode 100644 index 0000000000..8f0be487d6 --- /dev/null +++ b/tests/micropython/viper_misc2.py @@ -0,0 +1,21 @@ +# Miscellaneous viper tests + +# Test correct use of registers in load and store +@micropython.viper +def expand(dest: ptr8, source: ptr8, length: int): + n = 0 + for x in range(0, length, 2): + c = source[x] + d = source[x + 1] + dest[n] = (c & 0xE0) | ((c & 0x1C) >> 1) + n += 1 + dest[n] = ((c & 3) << 6) | ((d & 0xE0) >> 4) + n += 1 + dest[n] = ((d & 0x1C) << 3) | ((d & 3) << 2) + n += 1 + + +source = b"\xaa\xaa\xff\xff" +dest = bytearray(len(source) // 2 * 3) +expand(dest, source, len(source)) +print(dest) diff --git a/tests/micropython/viper_misc2.py.exp b/tests/micropython/viper_misc2.py.exp new file mode 100644 index 0000000000..eff2f5e41d --- /dev/null +++ b/tests/micropython/viper_misc2.py.exp @@ -0,0 +1 @@ +bytearray(b'\xa4\x8aH\xee\xce\xec') diff --git a/tests/multi_bluetooth/perf_gatt_char_write.py b/tests/multi_bluetooth/perf_gatt_char_write.py new file mode 100644 index 0000000000..00607f0090 --- /dev/null +++ b/tests/multi_bluetooth/perf_gatt_char_write.py @@ -0,0 +1,151 @@ +# Write characteristic from central to peripheral and time data rate. + +from micropython import const +import time, machine, bluetooth + +TIMEOUT_MS = 2000 + +_IRQ_CENTRAL_CONNECT = const(1) +_IRQ_CENTRAL_DISCONNECT = const(2) +_IRQ_GATTS_WRITE = const(3) +_IRQ_PERIPHERAL_CONNECT = const(7) +_IRQ_PERIPHERAL_DISCONNECT = const(8) +_IRQ_GATTC_CHARACTERISTIC_RESULT = const(11) +_IRQ_GATTC_CHARACTERISTIC_DONE = const(12) +_IRQ_GATTC_WRITE_DONE = const(17) +_IRQ_MTU_EXCHANGED = const(21) + +# How long to run the test for. +_NUM_NOTIFICATIONS = const(40) +_MTU_SIZE = const(131) +_CHAR_SIZE = const(_MTU_SIZE - 3) + +SERVICE_UUID = bluetooth.UUID("A5A5A5A5-FFFF-9999-1111-5A5A5A5A5A5A") +CHAR_UUID = bluetooth.UUID("00000000-1111-2222-3333-444444444444") +CHAR = (CHAR_UUID, bluetooth.FLAG_WRITE | bluetooth.FLAG_WRITE_NO_RESPONSE) +SERVICE = (SERVICE_UUID, (CHAR,)) +SERVICES = (SERVICE,) + +packet_sequence = 0 +waiting_events = {} + + +def irq(event, data): + if event == _IRQ_CENTRAL_CONNECT: + waiting_events[event] = data[0] + elif event == _IRQ_PERIPHERAL_CONNECT: + waiting_events[event] = data[0] + elif event == _IRQ_GATTS_WRITE: + global packet_sequence + conn_handle, attr_handle = data + data = ble.gatts_read(attr_handle) + if not (data[0] == packet_sequence and data[-1] == (256 - packet_sequence) & 0xFF): + print("_IRQ_GATTS_WRITE data invalid:", packet_sequence, data) + elif packet_sequence % 10 == 0: + print("_IRQ_GATTS_WRITE", packet_sequence) + packet_sequence += 1 + elif event == _IRQ_GATTC_CHARACTERISTIC_RESULT: + # conn_handle, def_handle, value_handle, properties, uuid = data + if data[-1] == CHAR_UUID: + waiting_events[event] = data[2] + else: + return + elif event == _IRQ_MTU_EXCHANGED: + # ATT MTU exchange complete (either initiated by us or the remote device). + conn_handle, mtu = data + print("_IRQ_MTU_EXCHANGED:", mtu) + + if event not in waiting_events: + waiting_events[event] = None + + +def wait_for_event(event, timeout_ms): + t0 = time.ticks_ms() + while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: + if event in waiting_events: + return waiting_events.pop(event) + machine.idle() + raise ValueError("Timeout waiting for {}".format(event)) + + +# Acting in peripheral role. +def instance0(): + multitest.globals(BDADDR=ble.config("mac")) + ((char_handle,),) = ble.gatts_register_services(SERVICES) + ble.gatts_set_buffer(char_handle, _CHAR_SIZE) + print("gap_advertise") + ble.gap_advertise(20_000, b"\x02\x01\x06\x04\xffMPY") + multitest.next() + try: + # Wait for central to connect to us. + conn_handle = wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS) + # Wait for central to disconnect us. + wait_for_event(_IRQ_CENTRAL_DISCONNECT, 30000) + print("final packet_sequence:", packet_sequence) + finally: + ble.active(0) + + +# Acting in central role. +def instance1(): + global packet_sequence + ((char_handle,),) = ble.gatts_register_services(SERVICES) + multitest.next() + try: + # Connect to peripheral and then disconnect. + print("gap_connect") + ble.config(mtu=_MTU_SIZE) + ble.gap_connect(*BDADDR) + conn_handle = wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS) + ble.gattc_exchange_mtu(conn_handle) + + # Discover characteristics. + ble.gattc_discover_characteristics(conn_handle, 1, 65535) + value_handle = wait_for_event(_IRQ_GATTC_CHARACTERISTIC_RESULT, TIMEOUT_MS) + wait_for_event(_IRQ_GATTC_CHARACTERISTIC_DONE, TIMEOUT_MS) + + # Send data! + data = bytearray(ord("A") + (i % 64) for i in range(_CHAR_SIZE)) + for mode in (0, 1): + ticks_start = time.ticks_ms() + for i in range(_NUM_NOTIFICATIONS): + data[0] = packet_sequence + data[-1] = 256 - packet_sequence + if packet_sequence % 10 == 0: + print("gattc_write", packet_sequence) + if mode == 0: + while True: + try: + ble.gattc_write(conn_handle, value_handle, data, mode) + break + except OSError: + pass + else: + ble.gattc_write(conn_handle, value_handle, data, mode) + wait_for_event(_IRQ_GATTC_WRITE_DONE, TIMEOUT_MS) + packet_sequence += 1 + + ticks_end = time.ticks_ms() + ticks_total = time.ticks_diff(ticks_end, ticks_start) + + print( + "Did {} writes in {} ms. {} ms/write, {} bytes/sec".format( + _NUM_NOTIFICATIONS, + ticks_total, + ticks_total / _NUM_NOTIFICATIONS, + _NUM_NOTIFICATIONS * len(data) * 1000 // ticks_total, + ) + ) + + time.sleep_ms(100) + + # DIsconnect the peripheral. + print("gap_disconnect:", ble.gap_disconnect(conn_handle)) + wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, 20000) + finally: + ble.active(0) + + +ble = bluetooth.BLE() +ble.active(1) +ble.irq(irq) diff --git a/tests/multi_bluetooth/perf_gatt_char_write.py.exp b/tests/multi_bluetooth/perf_gatt_char_write.py.exp new file mode 100644 index 0000000000..4dda5e04f0 --- /dev/null +++ b/tests/multi_bluetooth/perf_gatt_char_write.py.exp @@ -0,0 +1,24 @@ +--- instance0 --- +gap_advertise +_IRQ_MTU_EXCHANGED: 131 +_IRQ_GATTS_WRITE 0 +_IRQ_GATTS_WRITE 10 +_IRQ_GATTS_WRITE 20 +_IRQ_GATTS_WRITE 30 +_IRQ_GATTS_WRITE 40 +_IRQ_GATTS_WRITE 50 +_IRQ_GATTS_WRITE 60 +_IRQ_GATTS_WRITE 70 +final packet_sequence: 80 +--- instance1 --- +gap_connect +_IRQ_MTU_EXCHANGED: 131 +gattc_write 0 +gattc_write 10 +gattc_write 20 +gattc_write 30 +gattc_write 40 +gattc_write 50 +gattc_write 60 +gattc_write 70 +gap_disconnect: True diff --git a/tests/multi_net/uasyncio_tcp_readinto.py b/tests/multi_net/uasyncio_tcp_readinto.py new file mode 100644 index 0000000000..631997652a --- /dev/null +++ b/tests/multi_net/uasyncio_tcp_readinto.py @@ -0,0 +1,80 @@ +# Test uasyncio stream readinto() method using TCP server/client + +try: + import uasyncio as asyncio +except ImportError: + try: + import asyncio + except ImportError: + print("SKIP") + raise SystemExit + +try: + import uarray as array +except ImportError: + try: + import array + except ImportError: + print("SKIP") + raise SystemExit + +PORT = 8000 + + +async def handle_connection(reader, writer): + writer.write(b"ab") + await writer.drain() + + writer.write(b"c") + await writer.drain() + + print("close") + writer.close() + await writer.wait_closed() + + print("done") + ev.set() + + +async def tcp_server(): + global ev + ev = asyncio.Event() + server = await asyncio.start_server(handle_connection, "0.0.0.0", PORT) + print("server running") + multitest.next() + async with server: + await asyncio.wait_for(ev.wait(), 10) + + +async def tcp_client(): + reader, writer = await asyncio.open_connection(IP, PORT) + + ba = bytearray(2) + n = await reader.readinto(ba) + print(n) + print(ba[:n]) + + a = array.array("b", [0, 0]) + n = await reader.readinto(a) + print(n) + print(a[:n]) + + try: + n = await reader.readinto(5) + except TypeError as er: + print("TypeError") + + try: + n = await reader.readinto() + except TypeError as er: + print("TypeError") + + +def instance0(): + multitest.globals(IP=multitest.get_network_ip()) + asyncio.run(tcp_server()) + + +def instance1(): + multitest.next() + asyncio.run(tcp_client()) diff --git a/tests/multi_net/uasyncio_tcp_readinto.py.exp b/tests/multi_net/uasyncio_tcp_readinto.py.exp new file mode 100644 index 0000000000..2d40ba1e63 --- /dev/null +++ b/tests/multi_net/uasyncio_tcp_readinto.py.exp @@ -0,0 +1,11 @@ +--- instance0 --- +server running +close +done +--- instance1 --- +2 +bytearray(b'ab') +1 +array('b', [99]) +TypeError +TypeError diff --git a/tests/run-multitests.py b/tests/run-multitests.py index cdb2730eda..b4afc1e526 100755 --- a/tests/run-multitests.py +++ b/tests/run-multitests.py @@ -32,8 +32,10 @@ import sys class multitest: @staticmethod def flush(): - if hasattr(sys.stdout, "flush"): + try: sys.stdout.flush() + except AttributeError: + pass @staticmethod def skip(): print("SKIP") @@ -160,7 +162,17 @@ class PyInstanceSubProcess(PyInstance): class PyInstancePyboard(PyInstance): + @staticmethod + def map_device_shortcut(device): + if device[0] == "a" and device[1:].isdigit(): + return "/dev/ttyACM" + device[1:] + elif device[0] == "u" and device[1:].isdigit(): + return "/dev/ttyUSB" + device[1:] + else: + return device + def __init__(self, device): + device = self.map_device_shortcut(device) self.device = device self.pyb = pyboard.Pyboard(device) self.pyb.enter_raw_repl() @@ -233,6 +245,7 @@ def trace_instance_output(instance_idx, line): if cmd_args.trace_output: t_ms = round((time.time() - trace_t0) * 1000) print("{:6} i{} :".format(t_ms, instance_idx), line) + sys.stdout.flush() def run_test_on_instances(test_file, num_instances, instances): diff --git a/tests/run-perfbench.py b/tests/run-perfbench.py index 8b71ae64ce..bcdbe69abb 100755 --- a/tests/run-perfbench.py +++ b/tests/run-perfbench.py @@ -85,7 +85,7 @@ def run_benchmark_on_target(target, script): def run_benchmarks(target, param_n, param_m, n_average, test_list): skip_complex = run_feature_test(target, "complex") != "complex" - skip_native = run_feature_test(target, "native_check") != "" + skip_native = run_feature_test(target, "native_check") != "native" for test_file in sorted(test_list): print(test_file + ": ", end="") diff --git a/tests/thread/stress_aes.py b/tests/thread/stress_aes.py index 2b974d5e51..673337563d 100644 --- a/tests/thread/stress_aes.py +++ b/tests/thread/stress_aes.py @@ -237,7 +237,7 @@ class LockedCounter: count = LockedCounter() -def thread_entry(): +def thread_entry(n_loop): global count aes = AES(256) @@ -246,7 +246,7 @@ def thread_entry(): data = bytearray(128) # from now on we don't use the heap - for loop in range(5): + for loop in range(n_loop): # encrypt aes.set_key(key) aes.set_iv(iv) @@ -267,8 +267,20 @@ def thread_entry(): if __name__ == "__main__": - n_thread = 20 + import sys + + if sys.platform == "rp2": + n_thread = 1 + n_loop = 2 + elif sys.platform in ("esp32", "pyboard"): + n_thread = 2 + n_loop = 2 + else: + n_thread = 20 + n_loop = 5 for i in range(n_thread): - _thread.start_new_thread(thread_entry, ()) + _thread.start_new_thread(thread_entry, (n_loop,)) + thread_entry(n_loop) while count.value < n_thread: time.sleep(1) + print("done") diff --git a/tests/thread/stress_create.py b/tests/thread/stress_create.py index eda768fa7b..877424cdf5 100644 --- a/tests/thread/stress_create.py +++ b/tests/thread/stress_create.py @@ -1,9 +1,13 @@ # stress test for creating many threads try: - import utime as time + import utime + + sleep_ms = utime.sleep_ms except ImportError: import time + + sleep_ms = lambda t: time.sleep(t / 1000) import _thread @@ -16,9 +20,11 @@ while thread_num < 500: try: _thread.start_new_thread(thread_entry, (thread_num,)) thread_num += 1 - except MemoryError: - pass + except (MemoryError, OSError) as er: + # Cannot create a new thead at this stage, yield for a bit to + # let existing threads run to completion and free up resources. + sleep_ms(50) # wait for the last threads to terminate -time.sleep(1) +sleep_ms(500) print("done") diff --git a/tests/thread/thread_exc1.py b/tests/thread/thread_exc1.py index 3c3a1e8be9..6dcfda121e 100644 --- a/tests/thread/thread_exc1.py +++ b/tests/thread/thread_exc1.py @@ -27,7 +27,12 @@ n_finished = 0 # spawn threads for i in range(n_thread): - _thread.start_new_thread(thread_entry, ()) + while True: + try: + _thread.start_new_thread(thread_entry, ()) + break + except OSError: + pass # busy wait for threads to finish while n_finished < n_thread: diff --git a/tests/thread/thread_exit1.py b/tests/thread/thread_exit1.py index 6179716d14..a6b00ca1f7 100644 --- a/tests/thread/thread_exit1.py +++ b/tests/thread/thread_exit1.py @@ -15,8 +15,13 @@ def thread_entry(): _thread.exit() -_thread.start_new_thread(thread_entry, ()) -_thread.start_new_thread(thread_entry, ()) +for i in range(2): + while True: + try: + _thread.start_new_thread(thread_entry, ()) + break + except OSError: + pass # wait for threads to finish time.sleep(1) diff --git a/tests/thread/thread_exit2.py b/tests/thread/thread_exit2.py index ba9852b5c2..2381fa5ebc 100644 --- a/tests/thread/thread_exit2.py +++ b/tests/thread/thread_exit2.py @@ -15,8 +15,13 @@ def thread_entry(): raise SystemExit -_thread.start_new_thread(thread_entry, ()) -_thread.start_new_thread(thread_entry, ()) +for i in range(2): + while True: + try: + _thread.start_new_thread(thread_entry, ()) + break + except OSError: + pass # wait for threads to finish time.sleep(1) diff --git a/tests/thread/thread_heap_lock.py b/tests/thread/thread_heap_lock.py new file mode 100644 index 0000000000..2837e0f366 --- /dev/null +++ b/tests/thread/thread_heap_lock.py @@ -0,0 +1,26 @@ +# test interaction of micropython.heap_lock with threads + +import _thread, micropython + +lock1 = _thread.allocate_lock() +lock2 = _thread.allocate_lock() + + +def thread_entry(): + lock1.acquire() + print([1, 2, 3]) + lock2.release() + + +lock1.acquire() +lock2.acquire() + +_thread.start_new_thread(thread_entry, ()) + +micropython.heap_lock() +lock1.release() +lock2.acquire() +micropython.heap_unlock() + +lock1.release() +lock2.release() diff --git a/tests/thread/thread_heap_lock.py.exp b/tests/thread/thread_heap_lock.py.exp new file mode 100644 index 0000000000..b5d8bb58d9 --- /dev/null +++ b/tests/thread/thread_heap_lock.py.exp @@ -0,0 +1 @@ +[1, 2, 3] diff --git a/tests/thread/thread_lock5.py b/tests/thread/thread_lock5.py new file mode 100644 index 0000000000..830b5efa82 --- /dev/null +++ b/tests/thread/thread_lock5.py @@ -0,0 +1,16 @@ +# test _thread lock objects where a lock is acquired/released by a different thread + +import _thread + + +def thread_entry(): + print("thread about to release lock") + lock.release() + + +lock = _thread.allocate_lock() +lock.acquire() +_thread.start_new_thread(thread_entry, ()) +lock.acquire() +print("main has lock") +lock.release() diff --git a/tests/thread/thread_stacksize1.py b/tests/thread/thread_stacksize1.py index 7473c578ed..2c70b90b5b 100644 --- a/tests/thread/thread_stacksize1.py +++ b/tests/thread/thread_stacksize1.py @@ -41,7 +41,12 @@ n_finished = 0 # set stack size and spawn a few threads _thread.stack_size(sz) for i in range(n_thread): - _thread.start_new_thread(thread_entry, ()) + while True: + try: + _thread.start_new_thread(thread_entry, ()) + break + except OSError: + pass # reset stack size to default (for subsequent scripts on baremetal) _thread.stack_size() diff --git a/tests/thread/thread_start1.py b/tests/thread/thread_start1.py index f08ff05a8e..6f08a135e4 100644 --- a/tests/thread/thread_start1.py +++ b/tests/thread/thread_start1.py @@ -20,8 +20,13 @@ def thread_entry(n): foo() -_thread.start_new_thread(thread_entry, (10,)) -_thread.start_new_thread(thread_entry, (20,)) +for i in range(2): + while True: + try: + _thread.start_new_thread(thread_entry, ((i + 1) * 10,)) + break + except OSError: + pass # wait for threads to finish time.sleep(1) diff --git a/tests/unix/extra_coverage.py.exp b/tests/unix/extra_coverage.py.exp index f7dd6172ed..9a451ebd65 100644 --- a/tests/unix/extra_coverage.py.exp +++ b/tests/unix/extra_coverage.py.exp @@ -27,6 +27,19 @@ RuntimeError: RuntimeError: # repl ame__ +mport + +builtins micropython _thread array +btree cexample cmath collections +cppexample ffi framebuf gc +hashlib math sys termios +ubinascii uctypes uerrno uheapq +uio ujson ulab uos +urandom ure uselect ustruct +utime utimeq uzlib +ime + +utime utimeq argv atexit byteorder exc_info exit getsizeof implementation maxsize diff --git a/tests/unix/ffi_float.py b/tests/unix/ffi_float.py index d039398965..03bd9f7f17 100644 --- a/tests/unix/ffi_float.py +++ b/tests/unix/ffi_float.py @@ -35,6 +35,15 @@ print("%.6f" % strtod("1.23", None)) # test passing double and float args libm = ffi_open(("libm.so", "libm.so.6", "libc.so.0", "libc.so.6", "libc.dylib")) tgamma = libm.func("d", "tgamma", "d") -for fun in (tgamma,): +for fun_name in ("tgamma",): + fun = globals()[fun_name] for val in (0.5, 1, 1.0, 1.5, 4, 4.0): - print("%.6f" % fun(val)) + print(fun_name, "%.5f" % fun(val)) + +# test passing 2x float/double args +powf = libm.func("f", "powf", "ff") +pow = libm.func("d", "pow", "dd") +for fun_name in ("powf", "pow"): + fun = globals()[fun_name] + for args in ((0, 1), (1, 0), (2, 0.5), (3, 4)): + print(fun_name, "%.5f" % fun(*args)) diff --git a/tests/unix/ffi_float.py.exp b/tests/unix/ffi_float.py.exp index b9d7da2bdb..3d90914315 100644 --- a/tests/unix/ffi_float.py.exp +++ b/tests/unix/ffi_float.py.exp @@ -1,8 +1,16 @@ 1.230000 1.230000 -1.772454 -1.000000 -1.000000 -0.886227 -6.000000 -6.000000 +tgamma 1.77245 +tgamma 1.00000 +tgamma 1.00000 +tgamma 0.88623 +tgamma 6.00000 +tgamma 6.00000 +powf 0.00000 +powf 1.00000 +powf 1.41421 +powf 81.00000 +pow 0.00000 +pow 1.00000 +pow 1.41421 +pow 81.00000 diff --git a/tests/unix/ffi_lib.c b/tests/unix/ffi_lib.c new file mode 100644 index 0000000000..35340536ae --- /dev/null +++ b/tests/unix/ffi_lib.c @@ -0,0 +1,33 @@ +#include + +int8_t f8i(int8_t x) { + return x ^ 1; +} + +uint8_t f8u(uint8_t x) { + return x ^ 1; +} + +int16_t f16i(int16_t x) { + return x ^ 1; +} + +uint16_t f16u(uint16_t x) { + return x ^ 1; +} + +int32_t f32i(int32_t x) { + return x ^ 1; +} + +uint32_t f32u(uint32_t x) { + return x ^ 1; +} + +int64_t f64i(int64_t x) { + return x ^ 1; +} + +uint64_t f64u(uint64_t x) { + return x ^ 1; +} diff --git a/tests/unix/ffi_types.py.exp b/tests/unix/ffi_types.py.exp new file mode 100644 index 0000000000..d6324477d6 --- /dev/null +++ b/tests/unix/ffi_types.py.exp @@ -0,0 +1,136 @@ +f8i(0) = 1 +f8i(7f) = 7e +f8i(80) = -7f +f8i(ff) = -2 +f8i(100) = 1 +f8i(7fff) = -2 +f8i(8000) = 1 +f8i(ffff) = -2 +f8i(10000) = 1 +f8i(7fffffff) = -2 +f8i(80000000) = 1 +f8i(ffffffff) = -2 +f8i(100000000) = 1 +f8i(7fffffffffffffff) = -2 +f8i(8000000000000000) = 1 +f8i(ffffffffffffffff) = -2 +f8i(10000000000000000) = 1 +f8u(0) = 1 +f8u(7f) = 7e +f8u(80) = 81 +f8u(ff) = fe +f8u(100) = 1 +f8u(7fff) = fe +f8u(8000) = 1 +f8u(ffff) = fe +f8u(10000) = 1 +f8u(7fffffff) = fe +f8u(80000000) = 1 +f8u(ffffffff) = fe +f8u(100000000) = 1 +f8u(7fffffffffffffff) = fe +f8u(8000000000000000) = 1 +f8u(ffffffffffffffff) = fe +f8u(10000000000000000) = 1 +f16i(0) = 1 +f16i(7f) = 7e +f16i(80) = 81 +f16i(ff) = fe +f16i(100) = 101 +f16i(7fff) = 7ffe +f16i(8000) = -7fff +f16i(ffff) = -2 +f16i(10000) = 1 +f16i(7fffffff) = -2 +f16i(80000000) = 1 +f16i(ffffffff) = -2 +f16i(100000000) = 1 +f16i(7fffffffffffffff) = -2 +f16i(8000000000000000) = 1 +f16i(ffffffffffffffff) = -2 +f16i(10000000000000000) = 1 +f16u(0) = 1 +f16u(7f) = 7e +f16u(80) = 81 +f16u(ff) = fe +f16u(100) = 101 +f16u(7fff) = 7ffe +f16u(8000) = 8001 +f16u(ffff) = fffe +f16u(10000) = 1 +f16u(7fffffff) = fffe +f16u(80000000) = 1 +f16u(ffffffff) = fffe +f16u(100000000) = 1 +f16u(7fffffffffffffff) = fffe +f16u(8000000000000000) = 1 +f16u(ffffffffffffffff) = fffe +f16u(10000000000000000) = 1 +f32i(0) = 1 +f32i(7f) = 7e +f32i(80) = 81 +f32i(ff) = fe +f32i(100) = 101 +f32i(7fff) = 7ffe +f32i(8000) = 8001 +f32i(ffff) = fffe +f32i(10000) = 10001 +f32i(7fffffff) = 7ffffffe +f32i(80000000) = -7fffffff +f32i(ffffffff) = -2 +f32i(100000000) = 1 +f32i(7fffffffffffffff) = -2 +f32i(8000000000000000) = 1 +f32i(ffffffffffffffff) = -2 +f32i(10000000000000000) = 1 +f32u(0) = 1 +f32u(7f) = 7e +f32u(80) = 81 +f32u(ff) = fe +f32u(100) = 101 +f32u(7fff) = 7ffe +f32u(8000) = 8001 +f32u(ffff) = fffe +f32u(10000) = 10001 +f32u(7fffffff) = 7ffffffe +f32u(80000000) = 80000001 +f32u(ffffffff) = fffffffe +f32u(100000000) = 1 +f32u(7fffffffffffffff) = fffffffe +f32u(8000000000000000) = 1 +f32u(ffffffffffffffff) = fffffffe +f32u(10000000000000000) = 1 +f64i(0) = 1 +f64i(7f) = 7e +f64i(80) = 81 +f64i(ff) = fe +f64i(100) = 101 +f64i(7fff) = 7ffe +f64i(8000) = 8001 +f64i(ffff) = fffe +f64i(10000) = 10001 +f64i(7fffffff) = 7ffffffe +f64i(80000000) = 80000001 +f64i(ffffffff) = fffffffe +f64i(100000000) = 100000001 +f64i(7fffffffffffffff) = 7ffffffffffffffe +f64i(8000000000000000) = -7fffffffffffffff +f64i(ffffffffffffffff) = -2 +f64i(10000000000000000) = 1 +f64u(0) = 1 +f64u(7f) = 7e +f64u(80) = 81 +f64u(ff) = fe +f64u(100) = 101 +f64u(7fff) = 7ffe +f64u(8000) = 8001 +f64u(ffff) = fffe +f64u(10000) = 10001 +f64u(7fffffff) = 7ffffffe +f64u(80000000) = 80000001 +f64u(ffffffff) = fffffffe +f64u(100000000) = 100000001 +f64u(7fffffffffffffff) = 7ffffffffffffffe +f64u(8000000000000000) = 8000000000000001 +f64u(ffffffffffffffff) = fffffffffffffffe +f64u(10000000000000000) = 1 diff --git a/tools/ci.sh b/tools/ci.sh index c018b55002..3039573322 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -109,7 +109,7 @@ function ci_esp32_build { make ${MAKEOPTS} -C ports/esp32 submodules make ${MAKEOPTS} -C ports/esp32 make ${MAKEOPTS} -C ports/esp32 clean - make ${MAKEOPTS} -C ports/esp32 USER_C_MODULES=../../../examples/usercmodule/micropython.cmake + make ${MAKEOPTS} -C ports/esp32 USER_C_MODULES=../../../examples/usercmodule/micropython.cmake FROZEN_MANIFEST=$(pwd)/ports/esp32/boards/manifest.py if [ -d $IDF_PATH/components/esp32s2 ]; then make ${MAKEOPTS} -C ports/esp32 BOARD=GENERIC_S2 fi @@ -138,6 +138,20 @@ function ci_esp8266_build { make ${MAKEOPTS} -C ports/esp8266 BOARD=GENERIC_1M } +######################################################################################## +# ports/mimxrt + +function ci_mimxrt_setup { + ci_gcc_arm_setup +} + +function ci_mimxrt_build { + make ${MAKEOPTS} -C mpy-cross + make ${MAKEOPTS} -C ports/mimxrt submodules + make ${MAKEOPTS} -C ports/mimxrt BOARD=MIMXRT1020_EVK + make ${MAKEOPTS} -C ports/mimxrt BOARD=TEENSY40 +} + ######################################################################################## # ports/nrf @@ -181,6 +195,8 @@ function ci_qemu_arm_build { make ${MAKEOPTS} -C ports/qemu-arm CFLAGS_EXTRA=-DMP_ENDIANNESS_BIG=1 make ${MAKEOPTS} -C ports/qemu-arm clean make ${MAKEOPTS} -C ports/qemu-arm -f Makefile.test test + make ${MAKEOPTS} -C ports/qemu-arm -f Makefile.test clean + make ${MAKEOPTS} -C ports/qemu-arm -f Makefile.test BOARD=sabrelite test } ######################################################################################## @@ -243,6 +259,10 @@ function ci_stm32_nucleo_build { BUILD_WB55=ports/stm32/build-NUCLEO_WB55 python3 ports/stm32/mboot/mboot_pack_dfu.py -k $BOARD_WB55/mboot_keys.h unpack-dfu $BUILD_WB55/firmware.pack.dfu $BUILD_WB55/firmware.unpack.dfu diff $BUILD_WB55/firmware.unpack.dfu $BUILD_WB55/firmware.dfu + # Test unpack-dfu command works without a secret key + tail -n +2 $BOARD_WB55/mboot_keys.h > $BOARD_WB55/mboot_keys_no_sk.h + python3 ports/stm32/mboot/mboot_pack_dfu.py -k $BOARD_WB55/mboot_keys_no_sk.h unpack-dfu $BUILD_WB55/firmware.pack.dfu $BUILD_WB55/firmware.unpack_no_sk.dfu + diff $BUILD_WB55/firmware.unpack.dfu $BUILD_WB55/firmware.unpack_no_sk.dfu } ######################################################################################## @@ -273,6 +293,19 @@ CI_UNIX_OPTS_SYS_SETTRACE_STACKLESS=( CFLAGS_EXTRA="-DMICROPY_STACKLESS=1 -DMICROPY_STACKLESS_STRICT=1 -DMICROPY_PY_SYS_SETTRACE=1" ) +CI_UNIX_OPTS_QEMU_MIPS=( + CROSS_COMPILE=mips-linux-gnu- + VARIANT=coverage + MICROPY_STANDALONE=1 + LDFLAGS_EXTRA="-static" +) + +CI_UNIX_OPTS_QEMU_ARM=( + CROSS_COMPILE=arm-linux-gnueabi- + VARIANT=coverage + MICROPY_STANDALONE=1 +) + function ci_unix_build_helper { make ${MAKEOPTS} -C mpy-cross make ${MAKEOPTS} -C ports/unix "$@" submodules @@ -280,6 +313,10 @@ function ci_unix_build_helper { make ${MAKEOPTS} -C ports/unix "$@" } +function ci_unix_build_ffi_lib_helper { + $1 $2 -shared -o tests/unix/ffi_lib.so tests/unix/ffi_lib.c +} + function ci_unix_run_tests_helper { make -C ports/unix "$@" test } @@ -326,6 +363,7 @@ function ci_unix_minimal_run_tests { function ci_unix_standard_build { ci_unix_build_helper VARIANT=standard + ci_unix_build_ffi_lib_helper gcc } function ci_unix_standard_run_tests { @@ -346,6 +384,7 @@ function ci_unix_coverage_setup { function ci_unix_coverage_build { ci_unix_build_helper VARIANT=coverage + ci_unix_build_ffi_lib_helper gcc } function ci_unix_coverage_run_tests { @@ -370,6 +409,7 @@ function ci_unix_32bit_setup { function ci_unix_coverage_32bit_build { ci_unix_build_helper VARIANT=coverage MICROPY_FORCE_32BIT=1 + ci_unix_build_ffi_lib_helper gcc -m32 } function ci_unix_coverage_32bit_run_tests { @@ -383,6 +423,7 @@ function ci_unix_coverage_32bit_run_native_mpy_tests { function ci_unix_nanbox_build { # Use Python 2 to check that it can run the build scripts ci_unix_build_helper PYTHON=python2 VARIANT=nanbox + ci_unix_build_ffi_lib_helper gcc -m32 } function ci_unix_nanbox_run_tests { @@ -391,6 +432,7 @@ function ci_unix_nanbox_run_tests { function ci_unix_float_build { ci_unix_build_helper VARIANT=standard CFLAGS_EXTRA="-DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_FLOAT" + ci_unix_build_ffi_lib_helper gcc } function ci_unix_float_run_tests { @@ -459,6 +501,46 @@ function ci_unix_macos_run_tests { (cd tests && ./run-tests.py --exclude 'uasyncio_(basic|heaplock|lock|wait_task)' --exclude 'import_pkg7.py' --exclude 'urandom_basic.py') } +function ci_unix_qemu_mips_setup { + sudo apt-get update + sudo apt-get install gcc-mips-linux-gnu g++-mips-linux-gnu + sudo apt-get install qemu-user + qemu-mips --version +} + +function ci_unix_qemu_mips_build { + # qemu-mips on GitHub Actions will seg-fault if not linked statically + ci_unix_build_helper "${CI_UNIX_OPTS_QEMU_MIPS[@]}" +} + +function ci_unix_qemu_mips_run_tests { + # Issues with MIPS tests: + # - (i)listdir does not work, it always returns the empty list (it's an issue with the underlying C call) + # - ffi tests do not work + file ./ports/unix/micropython-coverage + (cd tests && MICROPY_MICROPYTHON=../ports/unix/micropython-coverage ./run-tests.py --exclude 'vfs_posix.py' --exclude 'ffi_(callback|float|float2).py') +} + +function ci_unix_qemu_arm_setup { + sudo apt-get update + sudo apt-get install gcc-arm-linux-gnueabi g++-arm-linux-gnueabi + sudo apt-get install qemu-user + qemu-arm --version +} + +function ci_unix_qemu_arm_build { + ci_unix_build_helper "${CI_UNIX_OPTS_QEMU_ARM[@]}" + ci_unix_build_ffi_lib_helper arm-linux-gnueabi-gcc +} + +function ci_unix_qemu_arm_run_tests { + # Issues with ARM tests: + # - (i)listdir does not work, it always returns the empty list (it's an issue with the underlying C call) + export QEMU_LD_PREFIX=/usr/arm-linux-gnueabi + file ./ports/unix/micropython-coverage + (cd tests && MICROPY_MICROPYTHON=../ports/unix/micropython-coverage ./run-tests.py --exclude 'vfs_posix.py') +} + ######################################################################################## # ports/windows @@ -475,19 +557,19 @@ function ci_windows_build { # ports/zephyr function ci_zephyr_setup { - docker pull zephyrprojectrtos/ci:v0.11.13 + docker pull zephyrprojectrtos/ci:v0.17.3 docker run --name zephyr-ci -d -it \ -v "$(pwd)":/micropython \ - -e ZEPHYR_SDK_INSTALL_DIR=/opt/sdk/zephyr-sdk-0.12.2 \ + -e ZEPHYR_SDK_INSTALL_DIR=/opt/toolchains/zephyr-sdk-0.12.4 \ -e ZEPHYR_TOOLCHAIN_VARIANT=zephyr \ -e ZEPHYR_BASE=/zephyrproject/zephyr \ -w /micropython/ports/zephyr \ - zephyrprojectrtos/ci:v0.11.13 + zephyrprojectrtos/ci:v0.17.3 docker ps -a } function ci_zephyr_install { - docker exec zephyr-ci west init --mr v2.5.0 /zephyrproject + docker exec zephyr-ci west init --mr v2.6.0 /zephyrproject docker exec -w /zephyrproject zephyr-ci west update docker exec -w /zephyrproject zephyr-ci west zephyr-export } diff --git a/tools/makemanifest.py b/tools/makemanifest.py index 117d1536e8..51de01dd8a 100644 --- a/tools/makemanifest.py +++ b/tools/makemanifest.py @@ -199,7 +199,7 @@ def mkdir(filename): def freeze_internal(kind, path, script, opt): path = convert_path(path) if not os.path.isdir(path): - raise FreezeError("freeze path must be a directory") + raise FreezeError("freeze path must be a directory: {}".format(path)) if script is None and kind == KIND_AS_STR: if any(f[0] == KIND_AS_STR for f in manifest_list): raise FreezeError("can only freeze one str directory") diff --git a/tools/metrics.py b/tools/metrics.py index 98291e25a9..c857d0733e 100755 --- a/tools/metrics.py +++ b/tools/metrics.py @@ -67,6 +67,7 @@ port_data = { "8": PortData("esp8266", "esp8266", "build-GENERIC/firmware.elf"), "3": PortData("esp32", "esp32", "build-GENERIC/micropython.elf"), "r": PortData("nrf", "nrf", "build-pca10040/firmware.elf"), + "p": PortData("rp2", "rp2", "build-PICO/firmware.elf"), "d": PortData("samd", "samd", "build-ADAFRUIT_ITSYBITSY_M4_EXPRESS/firmware.elf"), } diff --git a/tools/mpremote/LICENSE b/tools/mpremote/LICENSE new file mode 100644 index 0000000000..5ec904cca1 --- /dev/null +++ b/tools/mpremote/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2021 Damien P. George + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/tools/mpremote/README.md b/tools/mpremote/README.md new file mode 100644 index 0000000000..a6aaa1755d --- /dev/null +++ b/tools/mpremote/README.md @@ -0,0 +1,71 @@ +# mpremote -- MicroPython remote control + +This CLI tool provides an integrated set of utilities to remotely interact with +and automate a MicroPython device over a serial connection. + +The simplest way to use this tool is: + + mpremote + +This will automatically connect to the device and provide an interactive REPL. + +The full list of supported commands are: + + mpremote connect -- connect to given device + device may be: list, auto, id:x, port:x + or any valid device name/path + mpremote disconnect -- disconnect current device + mpremote mount -- mount local directory on device + mpremote eval -- evaluate and print the string + mpremote exec -- execute the string + mpremote run -- run the given local script + mpremote fs -- execute filesystem commands on the device + command may be: cat, ls, cp, rm, mkdir, rmdir + use ":" as a prefix to specify a file on the device + mpremote repl -- enter REPL + options: + --capture + --inject-code + --inject-file + +Multiple commands can be specified and they will be run sequentially. Connection +and disconnection will be done automatically at the start and end of the execution +of the tool, if such commands are not explicitly given. Automatic connection will +search for the first available serial device. If no action is specified then the +REPL will be entered. + +Shortcuts can be defined using the macro system. Built-in shortcuts are: + +- a0, a1, a2, a3: connect to `/dev/ttyACM?` +- u0, u1, u2, u3: connect to `/dev/ttyUSB?` +- c0, c1, c2, c3: connect to `COM?` +- cat, ls, cp, rm, mkdir, rmdir, df: filesystem commands +- reset: reset the device +- bootloader: make the device enter its bootloader + +Any user configuration, including user-defined shortcuts, can be placed in +.config/mpremote/config.py. For example: + + # Custom macro commands + commands = { + "c33": "connect id:334D335C3138", + "bl": "bootloader", + "double x=4": "eval x*2", + } + +Examples: + + mpremote + mpremote a1 + mpremote connect /dev/ttyUSB0 repl + mpremote ls + mpremote a1 ls + mpremote exec "import micropython; micropython.mem_info()" + mpremote eval 1/2 eval 3/4 + mpremote mount . + mpremote mount . exec "import local_script" + mpremote ls + mpremote cat boot.py + mpremote cp :main.py . + mpremote cp main.py : + mpremote cp -r dir/ : diff --git a/tools/mpremote/mpremote.py b/tools/mpremote/mpremote.py new file mode 100755 index 0000000000..a91ff67b15 --- /dev/null +++ b/tools/mpremote/mpremote.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 + +import sys +from mpremote import main + +sys.exit(main.main()) diff --git a/tools/mpremote/mpremote/__init__.py b/tools/mpremote/mpremote/__init__.py new file mode 100644 index 0000000000..1bb8bf6d7f --- /dev/null +++ b/tools/mpremote/mpremote/__init__.py @@ -0,0 +1 @@ +# empty diff --git a/tools/mpremote/mpremote/console.py b/tools/mpremote/mpremote/console.py new file mode 100644 index 0000000000..2652c7393b --- /dev/null +++ b/tools/mpremote/mpremote/console.py @@ -0,0 +1,171 @@ +import sys, time + +try: + import select, termios +except ImportError: + termios = None + select = None + import msvcrt, signal + + +class ConsolePosix: + def __init__(self): + self.infd = sys.stdin.fileno() + self.infile = sys.stdin.buffer.raw + self.outfile = sys.stdout.buffer.raw + self.orig_attr = termios.tcgetattr(self.infd) + + def enter(self): + # attr is: [iflag, oflag, cflag, lflag, ispeed, ospeed, cc] + attr = termios.tcgetattr(self.infd) + attr[0] &= ~( + termios.BRKINT | termios.ICRNL | termios.INPCK | termios.ISTRIP | termios.IXON + ) + attr[1] = 0 + attr[2] = attr[2] & ~(termios.CSIZE | termios.PARENB) | termios.CS8 + attr[3] = 0 + attr[6][termios.VMIN] = 1 + attr[6][termios.VTIME] = 0 + termios.tcsetattr(self.infd, termios.TCSANOW, attr) + + def exit(self): + termios.tcsetattr(self.infd, termios.TCSANOW, self.orig_attr) + + def waitchar(self, pyb_serial): + # TODO pyb_serial might not have fd + select.select([self.infd, pyb_serial.fd], [], []) + + def readchar(self): + res = select.select([self.infd], [], [], 0) + if res[0]: + return self.infile.read(1) + else: + return None + + def write(self, buf): + self.outfile.write(buf) + + +class ConsoleWindows: + KEY_MAP = { + b"H": b"A", # UP + b"P": b"B", # DOWN + b"M": b"C", # RIGHT + b"K": b"D", # LEFT + b"G": b"H", # POS1 + b"O": b"F", # END + b"Q": b"6~", # PGDN + b"I": b"5~", # PGUP + b"s": b"1;5D", # CTRL-LEFT, + b"t": b"1;5C", # CTRL-RIGHT, + b"\x8d": b"1;5A", # CTRL-UP, + b"\x91": b"1;5B", # CTRL-DOWN, + b"w": b"1;5H", # CTRL-POS1 + b"u": b"1;5F", # CTRL-END + b"\x98": b"1;3A", # ALT-UP, + b"\xa0": b"1;3B", # ALT-DOWN, + b"\x9d": b"1;3C", # ALT-RIGHT, + b"\x9b": b"1;3D", # ALT-LEFT, + b"\x97": b"1;3H", # ALT-POS1, + b"\x9f": b"1;3F", # ALT-END, + b"S": b"3~", # DEL, + b"\x93": b"3;5~", # CTRL-DEL + b"R": b"2~", # INS + b"\x92": b"2;5~", # CTRL-INS + b"\x94": b"Z", # Ctrl-Tab = BACKTAB, + } + + def __init__(self): + self.ctrl_c = 0 + + def _sigint_handler(self, signo, frame): + self.ctrl_c += 1 + + def enter(self): + signal.signal(signal.SIGINT, self._sigint_handler) + + def exit(self): + signal.signal(signal.SIGINT, signal.SIG_DFL) + + def inWaiting(self): + return 1 if self.ctrl_c or msvcrt.kbhit() else 0 + + def waitchar(self, pyb_serial): + while not (self.inWaiting() or pyb_serial.inWaiting()): + time.sleep(0.01) + + def readchar(self): + if self.ctrl_c: + self.ctrl_c -= 1 + return b"\x03" + if msvcrt.kbhit(): + ch = msvcrt.getch() + while ch in b"\x00\xe0": # arrow or function key prefix? + if not msvcrt.kbhit(): + return None + ch = msvcrt.getch() # second call returns the actual key code + try: + ch = b"\x1b[" + self.KEY_MAP[ch] + except KeyError: + return None + return ch + + def write(self, buf): + buf = buf.decode() if isinstance(buf, bytes) else buf + sys.stdout.write(buf) + sys.stdout.flush() + # for b in buf: + # if isinstance(b, bytes): + # msvcrt.putch(b) + # else: + # msvcrt.putwch(b) + + +if termios: + Console = ConsolePosix + VT_ENABLED = True +else: + Console = ConsoleWindows + + # Windows VT mode ( >= win10 only) + # https://bugs.python.org/msg291732 + import ctypes, os + from ctypes import wintypes + + kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) + + ERROR_INVALID_PARAMETER = 0x0057 + ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 + + def _check_bool(result, func, args): + if not result: + raise ctypes.WinError(ctypes.get_last_error()) + return args + + LPDWORD = ctypes.POINTER(wintypes.DWORD) + kernel32.GetConsoleMode.errcheck = _check_bool + kernel32.GetConsoleMode.argtypes = (wintypes.HANDLE, LPDWORD) + kernel32.SetConsoleMode.errcheck = _check_bool + kernel32.SetConsoleMode.argtypes = (wintypes.HANDLE, wintypes.DWORD) + + def set_conout_mode(new_mode, mask=0xFFFFFFFF): + # don't assume StandardOutput is a console. + # open CONOUT$ instead + fdout = os.open("CONOUT$", os.O_RDWR) + try: + hout = msvcrt.get_osfhandle(fdout) + old_mode = wintypes.DWORD() + kernel32.GetConsoleMode(hout, ctypes.byref(old_mode)) + mode = (new_mode & mask) | (old_mode.value & ~mask) + kernel32.SetConsoleMode(hout, mode) + return old_mode.value + finally: + os.close(fdout) + + # def enable_vt_mode(): + mode = mask = ENABLE_VIRTUAL_TERMINAL_PROCESSING + try: + set_conout_mode(mode, mask) + VT_ENABLED = True + except WindowsError as e: + VT_ENABLED = False diff --git a/tools/mpremote/mpremote/main.py b/tools/mpremote/mpremote/main.py new file mode 100644 index 0000000000..b7c46a1f11 --- /dev/null +++ b/tools/mpremote/mpremote/main.py @@ -0,0 +1,455 @@ +""" +MicroPython Remote - Interaction and automation tool for MicroPython +MIT license; Copyright (c) 2019-2021 Damien P. George + +This program provides a set of utilities to interact with and automate a +MicroPython device over a serial connection. Commands supported are: + + mpremote -- auto-detect, connect and enter REPL + mpremote -- connect to given device + mpremote connect -- connect to given device + mpremote disconnect -- disconnect current device + mpremote mount -- mount local directory on device + mpremote eval -- evaluate and print the string + mpremote exec -- execute the string + mpremote run