summaryrefslogtreecommitdiffstatshomepage
path: root/docs/reference/constrained.rst
diff options
context:
space:
mode:
authorPeter Hinch <peter@hinch.me.uk>2016-09-06 07:52:00 +0100
committerDamien George <damien.p.george@gmail.com>2016-10-11 15:27:20 +1100
commit9e1dec181806d7cd34b5dfec8cdce4c41b39ab53 (patch)
tree7259083563f9378aba6d95a093594b59c3a8f090 /docs/reference/constrained.rst
parent39968aaaff575800218b47488f85eed7081cd0cb (diff)
downloadmicropython-9e1dec181806d7cd34b5dfec8cdce4c41b39ab53.tar.gz
micropython-9e1dec181806d7cd34b5dfec8cdce4c41b39ab53.zip
docs/reference: Add constrained.rst doc.
It contains detailed information about writing scripts to run efficiently on microcontrollers (and other constrained systems).
Diffstat (limited to 'docs/reference/constrained.rst')
-rw-r--r--docs/reference/constrained.rst456
1 files changed, 456 insertions, 0 deletions
diff --git a/docs/reference/constrained.rst b/docs/reference/constrained.rst
new file mode 100644
index 0000000000..7c1b6a3eb2
--- /dev/null
+++ b/docs/reference/constrained.rst
@@ -0,0 +1,456 @@
+.. _constrained:
+
+MicroPython on Microcontrollers
+===============================
+
+MicroPython is designed to be capable of running on microcontrollers. These
+have hardware limitations which may be unfamiliar to programmers more familiar
+with conventional computers. In particular the amount of RAM and nonvolatile
+"disk" (flash memory) storage is limited. This tutorial offers ways to make
+the most of the limited resources. Because MicroPython runs on controllers
+based on a variety of architectures, the methods presented are generic: in some
+cases it will be necessary to obtain detailed information from platform specific
+documentation.
+
+Flash Memory
+------------
+
+On the Pyboard the simple way to address the limited capacity is to fit a micro
+SD card. In some cases this is impractical, either because the device does not
+have an SD card slot or for reasons of cost or power consumption; hence the
+on-chip flash must be used. The firmware including the MicroPython subsystem is
+stored in the onboard flash. The remaining capacity is available for use. For
+reasons connected with the physical architecture of the flash memory part of
+this capacity may be inaccessible as a filesystem. In such cases this space may
+be employed by incorporating user modules into a firmware build which is then
+flashed to the device.
+
+There are two ways to achieve this: frozen modules and frozen bytecode. Frozen
+modules store the Python source with the firmware. Frozen bytecode uses the
+cross compiler to convert the source to bytecode which is then stored with the
+firmware. In either case the module may be accessed with an import statement:
+
+.. code::
+
+ import mymodule
+
+The procedure for producing frozen modules and bytecode is platform dependent;
+instructions for building the firmware can be found in the README files in the
+relevant part of the source tree.
+
+In general terms the steps are as follows:
+
+* Clone the MicroPython `repository <https://github.com/micropython/micropython>`_.
+* Acquire the (platform specific) toolchain to build the firmware.
+* Build the cross compiler.
+* Place the modules to be frozen in a specified directory (dependent on whether
+ the module is to be frozen as source or as bytecode).
+* Build the firmware. A specific command may be required to build frozen
+ code of either type - see the platform documentation.
+* Flash the firmware to the device.
+
+RAM
+---
+
+When reducing RAM usage there are two phases to consider: compilation and
+execution. In addition to memory consumption, there is also an issue known as
+heap fragmentation. In general terms it is best to minimise the repeated
+creation and destruction of objects. The reason for this is covered in the
+section covering the `heap`_.
+
+Compilation Phase
+~~~~~~~~~~~~~~~~~
+
+When a module is imported, MicroPython compiles the code to bytecode which is
+then executed by the MicroPython virtual machine (VM). The bytecode is stored
+in RAM. The compiler itself requires RAM, but this becomes available for use
+when the compilation has completed.
+
+If a number of modules have already been imported the situation can arise where
+there is insufficient RAM to run the compiler. In this case the import
+statement will produce a memory exception.
+
+If a module instantiates global objects on import it will consume RAM at the
+time of import, which is then unavailable for the compiler to use on subsequent
+imports. In general it is best to avoid code which runs on import; a better
+approach is to have initialisation code which is run by the application after
+all modules have been imported. This maximises the RAM available to the
+compiler.
+
+If RAM is still insufficient to compile all modules one solution is to
+precompile modules. MicroPython has a cross compiler capable of compiling Python
+modules to bytecode (see the README in the mpy-cross directory). The resulting
+bytecode file has a .mpy extension; it may be copied to the filesystem and
+imported in the usual way. Alternatively some or all modules may be implemented
+as frozen bytecode: on most platforms this saves even more RAM as the bytecode
+is run directly from flash rather than being stored in RAM.
+
+Execution Phase
+~~~~~~~~~~~~~~~
+
+There are a number of coding techniques for reducing RAM usage.
+
+**Constants**
+
+MicroPython provides a ``const`` keyword which may be used as follows:
+
+.. code::
+
+ from micropython import const
+ ROWS = const(33)
+ _COLS = const(0x10)
+ a = ROWS
+ b = _COLS
+
+In both instances where the constant is assigned to a variable the compiler
+will avoid coding a lookup to the name of the constant by substituting its
+literal value. This saves bytecode and hence RAM. However the ``ROWS`` value
+will occupy at least two machine words, one each for the key and value in the
+globals dictionary. The presence in the dictionary is necessary because another
+module might import or use it. This RAM can be saved by prepending the name
+with an underscore as in ``_COLS``: this symbol is not visible outside the
+module so will not occupy RAM.
+
+The argument to ``const()`` may be anything which, at compile time, evaluates
+to an integer e.g. ``0x100`` or ``1 << 8``. It can even include other const
+symbols that have already been defined, e.g. ``1 << BIT``.
+
+**Constant data structures**
+
+Where there is a substantial volume of constant data and the platform supports
+execution from Flash, RAM may be saved as follows. The data should be located in
+Python modules and frozen as bytecode. The data must be defined as ``bytes``
+objects. The compiler 'knows' that ``bytes`` objects are immutable and ensures
+that the objects remain in flash memory rather than being copied to RAM. The
+``ustruct`` module can assist in converting between ``bytes`` types and other
+Python built-in types.
+
+When considering the implications of frozen bytecode, note that in Python
+strings, floats, bytes, integers and complex numbers are immutable. Accordingly
+these will be frozen into flash. Thus, in the line
+
+.. code::
+
+ mystring = "The quick brown fox"
+
+the actual string "The quick brown fox" will reside in flash. At runtime a
+reference to the string is assigned to the *variable* ``mystring``. The reference
+occupies a single machine word. In principle a long integer could be used to
+store constant data:
+
+.. code::
+
+ bar = 0xDEADBEEF0000DEADBEEF
+
+As in the string example, at runtime a reference to the arbitrarily large
+integer is assigned to the variable ``bar``. That reference occupies a
+single machine word.
+
+It might be expected that tuples of integers could be employed for the purpose
+of storing constant data with minimal RAM use. With the current compiler this
+is ineffective (the code works, but RAM is not saved).
+
+.. code::
+
+ foo = (1, 2, 3, 4, 5, 6, 100000)
+
+At runtime the tuple will be located in RAM. This may be subject to future
+improvement.
+
+**Needless object creation**
+
+There are a number of situations where objects may unwittingly be created and
+destroyed. This can reduce the usability of RAM through fragmentation. The
+following sections discuss instances of this.
+
+**String concatenation**
+
+Consider the following code fragments which aim to produce constant strings:
+
+.. code::
+
+ var = "foo" + "bar"
+ var1 = "foo" "bar"
+ var2 = """\
+ foo\
+ bar"""
+
+Each produces the same outcome, however the first needlessly creates two string
+objects at runtime, allocates more RAM for concatenation before producing the
+third. The others perform the concatenation at compile time which is more
+efficient, reducing fragmentation.
+
+Where strings must be dynamically created before being fed to a stream such as
+a file it will save RAM if this is done in a piecemeal fashion. Rather than
+creating a large string object, create a substring and feed it to the stream
+before dealing with the next.
+
+The best way to create dynamic strings is by means of the string ``format``
+method:
+
+.. code::
+
+ var = "Temperature {:5.2f} Pressure {:06d}\n".format(temp, press)
+
+**Buffers**
+
+When accessing devices such as instances of UART, I2C and SPI interfaces, using
+pre-allocated buffers avoids the creation of needless objects. Consider these
+two loops:
+
+.. code::
+
+ while True:
+ var = spi.read(100)
+ # process data
+
+ buf = bytearray(100)
+ while True:
+ spi.readinto(buf)
+ # process data in buf
+
+The first creates a buffer on each pass whereas the second re-uses a pre-allocated
+buffer; this is both faster and more efficient in terms of memory fragmentation.
+
+**Bytes are smaller than ints**
+
+On most platforms an integer consumes four bytes. Consider the two calls to the
+function ``foo()``:
+
+.. code::
+
+ def foo(bar):
+ for x in bar:
+ print(x)
+ foo((1, 2, 0xff))
+ foo(b'\1\2\xff')
+
+In the first call a tuple of integers is created in RAM. The second efficiently
+creates a ``bytes`` object consuming the minimum amount of RAM. If the module
+were frozen as bytecode, the ``bytes`` object would reside in flash.
+
+**Strings Versus Bytes**
+
+Python3 introduced Unicode support. This introduced a distinction between a
+string and an array of bytes. MicroPython ensures that Unicode strings take no
+additional space so long as all characters in the string are ASCII (i.e. have
+a value < 126). If values in the full 8-bit range are required ``bytes`` and
+``bytearray`` objects can be used to ensure that no additional space will be
+required. Note that most string methods (e.g. ``strip()``) apply also to ``bytes``
+instances so the process of eliminating Unicode can be painless.
+
+.. code::
+
+ s = 'the quick brown fox' # A string instance
+ b = b'the quick brown fox' # a bytes instance
+
+Where it is necessary to convert between strings and bytes the string ``encode``
+and the bytes ``decode`` methods can be used. Note that both strings and bytes
+are immutable. Any operation which takes as input such an object and produces
+another implies at least one RAM allocation to produce the result. In the
+second line below a new bytes object is allocated. This would also occur if ``foo``
+were a string.
+
+.. code::
+
+ foo = b' empty whitespace'
+ foo = foo.lstrip()
+
+**Runtime compiler execution**
+
+The Python keywords ``eval`` and ``exec`` invoke the compiler at runtime, which
+requires significant amounts of RAM. Note that the ``pickle`` library employs
+``exec``. It may be more RAM efficient to use the ``json`` library for object
+serialisation.
+
+**Storing strings in flash**
+
+Python strings are immutable hence have the potential to be stored in read only
+memory. The compiler can place in flash strings defined in Python code. As with
+frozen modules it is necessary to have a copy of the source tree on the PC and
+the toolchain to build the firmware. The procedure will work even if the
+modules have not been fully debugged, so long as they can be imported and run.
+
+After importing the modules, execute:
+
+.. code::
+
+ micropython.qstr_info(1)
+
+Then copy and paste all the Q(xxx) lines into a text editor. Check for and
+remove lines which are obviously invalid. Open the file qstrdefsport.h which
+will be found in stmhal (or the equivalent directory for the architecture in
+use). Copy and paste the corrected lines at the end of the file. Save the file,
+rebuild and flash the firmware. The outcome can be checked by importing the
+modules and again issuing:
+
+.. code::
+
+ micropython.qstr_info(1)
+
+The Q(xxx) lines should be gone.
+
+.. _heap:
+
+The Heap
+--------
+
+When a running program instantiates an object the necessary RAM is allocated
+from a fixed size pool known as the heap. When the object goes out of scope (in
+other words becomes inaccessible to code) the redundant object is known as
+"garbage". A process known as "garbage collection" (GC) reclaims that memory,
+returning it to the free heap. This process runs automatically, however it can
+be invoked directly by issuing ``gc.collect()``.
+
+The discourse on this is somewhat involved. For a 'quick fix' issue the
+following periodically:
+
+.. code::
+
+ gc.collect()
+ gc.threshold(gc.mem_free() // 4 + gc.mem_alloc())
+
+Fragmentation
+~~~~~~~~~~~~~
+
+Say a program creates an object ``foo``, then an object ``bar``. Subsequently
+``foo`` goes out of scope but ``bar`` remains. The RAM used by ``foo`` will be
+reclaimed by GC. However if ``bar`` was allocated to a higher address, the
+RAM reclaimed from ``foo`` will only be of use for objects no bigger than
+``foo``. In a complex or long running program the heap can become fragmented:
+despite there being a substantial amount of RAM available, there is insufficient
+contiguous space to allocate a particular object, and the program fails with a
+memory error.
+
+The techniques outlined above aim to minimise this. Where large permanent buffers
+or other objects are required it is best to instantiate these early in the
+process of program execution before fragmentation can occur. Further improvements
+may be made by monitoring the state of the heap and by controlling GC; these are
+outlined below.
+
+Reporting
+~~~~~~~~~
+
+A number of library functions are available to report on memory allocation and
+to control GC. These are to be found in the ``gc`` and ``micropython`` modules.
+The following example may be pasted at the REPL (``ctrl e`` to enter paste mode,
+``ctrl d`` to run it).
+
+.. code::
+
+ import gc
+ import micropython
+ gc.collect()
+ micropython.mem_info()
+ print('-----------------------------')
+ print('Initial free: {} allocated: {}'.format(gc.mem_free(), gc.mem_alloc()))
+ def func():
+ a = bytearray(10000)
+ gc.collect()
+ print('Func definition: {} allocated: {}'.format(gc.mem_free(), gc.mem_alloc()))
+ func()
+ print('Func run free: {} allocated: {}'.format(gc.mem_free(), gc.mem_alloc()))
+ gc.collect()
+ print('Garbage collect free: {} allocated: {}'.format(gc.mem_free(), gc.mem_alloc()))
+ print('-----------------------------')
+ micropython.mem_info(1)
+
+Methods employed above:
+
+* ``gc.collect()`` Force a garbage collection. See footnote.
+* ``micropython.mem_info()`` Print a summary of RAM utilisation.
+* ``gc.mem_free()`` Return the free heap size in bytes.
+* ``gc.mem_alloc()`` Return the number of bytes currently allocated.
+* ``micropython.mem_info(1)`` Print a table of heap utilisation (detailed below).
+
+The numbers produced are dependent on the platform, but it can be seen that
+declaring the function uses a small amount of RAM in the form of bytecode
+emitted by the compiler (the RAM used by the compiler has been reclaimed).
+Running the function uses over 10KiB, but on return ``a`` is garbage because it
+is out of scope and cannot be referenced. The final ``gc.collect()`` recovers
+that memory.
+
+The final output produced by ``micropython.mem_info(1)`` will vary in detail but
+may be interpreted as follows:
+
+====== =================
+Symbol Meaning
+====== =================
+ . free block
+ h head block
+ = tail block
+ m marked head block
+ T tuple
+ L list
+ D dict
+ F float
+ B byte code
+ M module
+====== =================
+
+Each letter represents a single block of memory, a block being 16 bytes. So each
+line of the heap dump represents 0x400 bytes or 1KiB of RAM.
+
+Control of Garbage Collection
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A GC can be demanded at any time by issuing ``gc.collect()``. It is advantageous
+to do this at intervals, firstly to pre-empt fragmentation and secondly for
+performance. A GC can take several milliseconds but is quicker when there is
+little work to do (about 1ms on the Pyboard). An explicit call can minimise that
+delay while ensuring it occurs at points in the program when it is acceptable.
+
+Automatic GC is provoked under the following circumstances. When an attempt at
+allocation fails, a GC is performed and the allocation re-tried. Only if this
+fails is an exception raised. Secondly an automatic GC will be triggered if the
+amount of free RAM falls below a threshold. This threshold can be adapted as
+execution progresses:
+
+.. code::
+
+ gc.collect()
+ gc.threshold(gc.mem_free() // 4 + gc.mem_alloc())
+
+This will provoke a GC when more than 25% of the currently free heap becomes
+occupied.
+
+In general modules should instantiate data objects at runtime using constructors
+or other initialisation functions. The reason is that if this occurs on
+initialisation the compiler may be starved of RAM when subsequent modules are
+imported. If modules do instantiate data on import then ``gc.collect()`` issued
+after the import will ameliorate the problem.
+
+String Operations
+-----------------
+
+MicroPython handles strings in an efficient manner and understanding this can
+help in designing applications to run on microcontrollers. When a module
+is compiled, strings which occur multiple times are stored once only, a process
+known as string interning. In MicroPython an interned string is known as a ``qstr``.
+In a module imported normally that single instance will be located in RAM, but
+as described above, in modules frozen as bytecode it will be located in flash.
+
+String comparisons are also performed efficiently using hashing rather than
+character by character. The penalty for using strings rather than integers may
+hence be small both in terms of performance and RAM usage - a fact which may
+come as a surprise to C programmers.
+
+Postscript
+----------
+
+MicroPython passes, returns and (by default) copies objects by reference. A
+reference occupies a single machine word so these processes are efficient in
+RAM usage and speed.
+
+Where variables are required whose size is neither a byte nor a machine word
+there are standard libraries which can assist in storing these efficiently and
+in performing conversions. See the ``array``, ``ustruct`` and ``uctypes``
+modules.
+
+Footnote: gc.collect() return value
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+On Unix and Windows platforms the ``gc.collect()`` method returns an integer
+which signifies the number of distinct memory regions that were reclaimed in the
+collection (more precisely, the number of heads that were turned into frees). For
+efficiency reasons bare metal ports do not return this value.