| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244 |
- Hints and tips
- ==============
- The following are some examples of the use of the inline assembler and some
- information on how to work around its limitations. In this document the term
- "assembler function" refers to a function declared in Python with the
- ``@micropython.asm_thumb`` decorator, whereas "subroutine" refers to assembler
- code called from within an assembler function.
- Code branches and subroutines
- -----------------------------
- It is important to appreciate that labels are local to an assembler function.
- There is currently no way for a subroutine defined in one function to be called
- from another.
- To call a subroutine the instruction ``bl(LABEL)`` is issued. This transfers
- control to the instruction following the ``label(LABEL)`` directive and stores
- the return address in the link register (``lr`` or ``r14``). To return the
- instruction ``bx(lr)`` is issued which causes execution to continue with
- the instruction following the subroutine call. This mechanism implies that, if
- a subroutine is to call another, it must save the link register prior to
- the call and restore it before terminating.
- The following rather contrived example illustrates a function call. Note that
- it's necessary at the start to branch around all subroutine calls: subroutines
- end execution with ``bx(lr)`` while the outer function simply "drops off the end"
- in the style of Python functions.
- ::
- @micropython.asm_thumb
- def quad(r0):
- b(START)
- label(DOUBLE)
- add(r0, r0, r0)
- bx(lr)
- label(START)
- bl(DOUBLE)
- bl(DOUBLE)
- print(quad(10))
- The following code example demonstrates a nested (recursive) call: the classic
- Fibonacci sequence. Here, prior to a recursive call, the link register is saved
- along with other registers which the program logic requires to be preserved.
- ::
- @micropython.asm_thumb
- def fib(r0):
- b(START)
- label(DOFIB)
- push({r1, r2, lr})
- cmp(r0, 1)
- ble(FIBDONE)
- sub(r0, 1)
- mov(r2, r0) # r2 = n -1
- bl(DOFIB)
- mov(r1, r0) # r1 = fib(n -1)
- sub(r0, r2, 1)
- bl(DOFIB) # r0 = fib(n -2)
- add(r0, r0, r1)
- label(FIBDONE)
- pop({r1, r2, lr})
- bx(lr)
- label(START)
- bl(DOFIB)
- for n in range(10):
- print(fib(n))
- Argument passing and return
- ---------------------------
- The tutorial details the fact that assembler functions can support from zero to
- three arguments, which must (if used) be named ``r0``, ``r1`` and ``r2``. When
- the code executes the registers will be initialised to those values.
- The data types which can be passed in this way are integers and memory
- addresses. With current firmware all possible 32 bit values may be passed and
- returned. If the return value may have the most significant bit set a Python
- type hint should be employed to enable MicroPython to determine whether the
- value should be interpreted as a signed or unsigned integer: types are
- ``int`` or ``uint``.
- ::
- @micropython.asm_thumb
- def uadd(r0, r1) -> uint:
- add(r0, r0, r1)
- ``hex(uadd(0x40000000,0x40000000))`` will return 0x80000000, demonstrating the
- passing and return of integers where bits 30 and 31 differ.
- The limitations on the number of arguments and return values can be overcome by means
- of the ``array`` module which enables any number of values of any type to be accessed.
- Multiple arguments
- ~~~~~~~~~~~~~~~~~~
- If a Python array of integers is passed as an argument to an assembler
- function, the function will receive the address of a contiguous set of integers.
- Thus multiple arguments can be passed as elements of a single array. Similarly a
- function can return multiple values by assigning them to array elements.
- Assembler functions have no means of determining the length of an array:
- this will need to be passed to the function.
- This use of arrays can be extended to enable more than three arrays to be used.
- This is done using indirection: the ``uctypes`` module supports ``addressof()``
- which will return the address of an array passed as its argument. Thus you can
- populate an integer array with the addresses of other arrays:
- ::
- from uctypes import addressof
- @micropython.asm_thumb
- def getindirect(r0):
- ldr(r0, [r0, 0]) # Address of array loaded from passed array
- ldr(r0, [r0, 4]) # Return element 1 of indirect array (24)
- def testindirect():
- a = array.array('i',[23, 24])
- b = array.array('i',[0,0])
- b[0] = addressof(a)
- print(getindirect(b))
- Non-integer data types
- ~~~~~~~~~~~~~~~~~~~~~~
- These may be handled by means of arrays of the appropriate data type. For
- example, single precision floating point data may be processed as follows.
- This code example takes an array of floats and replaces its contents with
- their squares.
- ::
- from array import array
- @micropython.asm_thumb
- def square(r0, r1):
- label(LOOP)
- vldr(s0, [r0, 0])
- vmul(s0, s0, s0)
- vstr(s0, [r0, 0])
- add(r0, 4)
- sub(r1, 1)
- bgt(LOOP)
- a = array('f', (x for x in range(10)))
- square(a, len(a))
- print(a)
- The uctypes module supports the use of data structures beyond simple
- arrays. It enables a Python data structure to be mapped onto a bytearray
- instance which may then be passed to the assembler function.
- Named constants
- ---------------
- Assembler code may be made more readable and maintainable by using named
- constants rather than littering code with numbers. This may be achieved
- thus:
- ::
- MYDATA = const(33)
- @micropython.asm_thumb
- def foo():
- mov(r0, MYDATA)
- The const() construct causes MicroPython to replace the variable name
- with its value at compile time. If constants are declared in an outer
- Python scope they can be shared between multiple assembler functions and
- with Python code.
- Assembler code as class methods
- -------------------------------
- MicroPython passes the address of the object instance as the first argument
- to class methods. This is normally of little use to an assembler function.
- It can be avoided by declaring the function as a static method thus:
- ::
- class foo:
- @staticmethod
- @micropython.asm_thumb
- def bar(r0):
- add(r0, r0, r0)
- Use of unsupported instructions
- -------------------------------
- These can be coded using the data statement as shown below. While
- ``push()`` and ``pop()`` are supported the example below illustrates the
- principle. The necessary machine code may be found in the ARM v7-M
- Architecture Reference Manual. Note that the first argument of data
- calls such as
- ::
- data(2, 0xe92d, 0x0f00) # push r8,r9,r10,r11
- indicates that each subsequent argument is a two byte quantity.
- Overcoming MicroPython's integer restriction
- --------------------------------------------
- The Pyboard chip includes a CRC generator. Its use presents a problem in
- MicroPython because the returned values cover the full gamut of 32 bit
- quantities whereas small integers in MicroPython cannot have differing values
- in bits 30 and 31. This limitation is overcome with the following code, which
- uses assembler to put the result into an array and Python code to
- coerce the result into an arbitrary precision unsigned integer.
- ::
- from array import array
- import stm
- def enable_crc():
- stm.mem32[stm.RCC + stm.RCC_AHB1ENR] |= 0x1000
- def reset_crc():
- stm.mem32[stm.CRC+stm.CRC_CR] = 1
- @micropython.asm_thumb
- def getval(r0, r1):
- movwt(r3, stm.CRC + stm.CRC_DR)
- str(r1, [r3, 0])
- ldr(r2, [r3, 0])
- str(r2, [r0, 0])
- def getcrc(value):
- a = array('i', [0])
- getval(a, value)
- return a[0] & 0xffffffff # coerce to arbitrary precision
- enable_crc()
- reset_crc()
- for x in range(20):
- print(hex(getcrc(0)))
|