.. _pyboard_tutorial_assembler:

Inline assembler
================

Here you will learn how to write inline assembler in MicroPython.

**Note**: this is an advanced tutorial, intended for those who already
know a bit about microcontrollers and assembly language.

MicroPython includes an inline assembler.  It allows you to write
assembly routines as a Python function, and you can call them as you would
a normal Python function.

Returning a value
-----------------

Inline assembler functions are denoted by a special function decorator.
Let's start with the simplest example::

    @micropython.asm_thumb
    def fun():
        movw(r0, 42)

You can enter this in a script or at the REPL.  This function takes no
arguments and returns the number 42.  ``r0`` is a register, and the value
in this register when the function returns is the value that is returned.
MicroPython always interprets the ``r0`` as an integer, and converts it to an
integer object for the caller.

If you run ``print(fun())`` you will see it print out 42.

Accessing peripherals
---------------------

For something a bit more complicated, let's turn on an LED::

    @micropython.asm_thumb
    def led_on():
        movwt(r0, stm.GPIOA)
        movw(r1, 1 << 13)
        strh(r1, [r0, stm.GPIO_BSRRL])

This code uses a few new concepts:

  - ``stm`` is a module which provides a set of constants for easy
    access to the registers of the pyboard's microcontroller.  Try
    running ``import stm`` and then ``help(stm)`` at the REPL.  It will
    give you a list of all the available constants.

  - ``stm.GPIOA`` is the address in memory of the GPIOA peripheral.
    On the pyboard, the red LED is on port A, pin PA13.

  - ``movwt`` moves a 32-bit number into a register.  It is a convenience
    function that turns into 2 thumb instructions: ``movw`` followed by ``movt``.
    The ``movt`` also shifts the immediate value right by 16 bits.

  - ``strh`` stores a half-word (16 bits).  The instruction above stores
    the lower 16-bits of ``r1`` into the memory location ``r0 + stm.GPIO_BSRRL``.
    This has the effect of setting high all those pins on port A for which
    the corresponding bit in ``r0`` is set.  In our example above, the 13th
    bit in ``r0`` is set, so PA13 is pulled high.  This turns on the red LED.

Accepting arguments
-------------------

Inline assembler functions can accept up to 4 arguments.  If they are
used, they must be named ``r0``, ``r1``, ``r2`` and ``r3`` to reflect the registers
and the calling conventions.

Here is a function that adds its arguments::

    @micropython.asm_thumb
    def asm_add(r0, r1):
        add(r0, r0, r1)

This performs the computation ``r0 = r0 + r1``.  Since the result is put
in ``r0``, that is what is returned.  Try ``asm_add(1, 2)``, it should return
3.

Loops
-----

We can assign labels with ``label(my_label)``, and branch to them using
``b(my_label)``, or a conditional branch like ``bgt(my_label)``.

The following example flashes the green LED.  It flashes it ``r0`` times. ::

    @micropython.asm_thumb
    def flash_led(r0):
        # get the GPIOA address in r1
        movwt(r1, stm.GPIOA)

        # get the bit mask for PA14 (the pin LED #2 is on)
        movw(r2, 1 << 14)

        b(loop_entry)

        label(loop1)

        # turn LED on
        strh(r2, [r1, stm.GPIO_BSRRL])

        # delay for a bit
        movwt(r4, 5599900)
        label(delay_on)
        sub(r4, r4, 1)
        cmp(r4, 0)
        bgt(delay_on)

        # turn LED off
        strh(r2, [r1, stm.GPIO_BSRRH])

        # delay for a bit
        movwt(r4, 5599900)
        label(delay_off)
        sub(r4, r4, 1)
        cmp(r4, 0)
        bgt(delay_off)

        # loop r0 times
        sub(r0, r0, 1)
        label(loop_entry)
        cmp(r0, 0)
        bgt(loop1)

Further reading
---------------

For further information about supported instructions of the inline assembler,
see the :ref:`reference documentation <asm_thumb2_index>`.