Commit 343b5c10 authored by Paul Sokolovsky's avatar Paul Sokolovsky
Browse files

docs/uctypes: Improve documentation.

Seealso and Limitations sectiosn added, better formatting and grammar.
parent 79b40d11
:mod:`uctypes` -- access C structures :mod:`uctypes` -- access binary data in a structured way
===================================== ========================================================
.. module:: uctypes .. module:: uctypes
:synopsis: access C structures :synopsis: access binary data in a structured way
This module implements "foreign data interface" for MicroPython. The idea This module implements "foreign data interface" for MicroPython. The idea
behind it is similar to CPython's ``ctypes`` modules, but actual API is behind it is similar to CPython's ``ctypes`` modules, but the actual API is
different, streamlined and optimized for small size. different, streamlined and optimized for small size. The basic idea of the
module is to define data structure layout with about the same power as the
C language allows, and the access it using familiar dot-syntax to reference
sub-fields.
.. seealso::
Module :mod:`ustruct`
Standard Python way to access binary data structures (doesn't scale
well to large and complex structures).
Defining structure layout Defining structure layout
------------------------- -------------------------
Structure layout is defined by a "descriptor" - a Python dictionary which Structure layout is defined by a "descriptor" - a Python dictionary which
encodes field names as keys and other properties required to access them as encodes field names as keys and other properties required to access them as
an associated values. Currently, uctypes requires explicit specification of associated values. Currently, uctypes requires explicit specification of
offsets for each field. Offset are given in bytes from structure start. offsets for each field. Offset are given in bytes from a structure start.
Following are encoding examples for various field types: Following are encoding examples for various field types:
Scalar types:: * Scalar types::
"field_name": uctypes.UINT32 | 0 "field_name": uctypes.UINT32 | 0
in other words, value is scalar type identifier ORed with field offset in other words, value is scalar type identifier ORed with field offset
(in bytes) from the start of the structure. (in bytes) from the start of the structure.
Recursive structures:: * Recursive structures::
"sub": (2, { "sub": (2, {
"b0": uctypes.UINT8 | 0, "b0": uctypes.UINT8 | 0,
"b1": uctypes.UINT8 | 1, "b1": uctypes.UINT8 | 1,
}) })
i.e. value is a 2-tuple, first element of which is offset, and second is i.e. value is a 2-tuple, first element of which is offset, and second is
a structure descriptor dictionary (note: offsets in recursive descriptors a structure descriptor dictionary (note: offsets in recursive descriptors
are relative to a structure it defines). are relative to a structure it defines).
Arrays of primitive types:: * Arrays of primitive types::
"arr": (uctypes.ARRAY | 0, uctypes.UINT8 | 2), "arr": (uctypes.ARRAY | 0, uctypes.UINT8 | 2),
i.e. value is a 2-tuple, first element of which is ARRAY flag ORed i.e. value is a 2-tuple, first element of which is ARRAY flag ORed
with offset, and second is scalar element type ORed number of elements with offset, and second is scalar element type ORed number of elements
in array. in array.
Arrays of aggregate types:: * Arrays of aggregate types::
"arr2": (uctypes.ARRAY | 0, 2, {"b": uctypes.UINT8 | 0}), "arr2": (uctypes.ARRAY | 0, 2, {"b": uctypes.UINT8 | 0}),
i.e. value is a 3-tuple, first element of which is ARRAY flag ORed i.e. value is a 3-tuple, first element of which is ARRAY flag ORed
with offset, second is a number of elements in array, and third is with offset, second is a number of elements in array, and third is
descriptor of element type. descriptor of element type.
Pointer to a primitive type:: * Pointer to a primitive type::
"ptr": (uctypes.PTR | 0, uctypes.UINT8), "ptr": (uctypes.PTR | 0, uctypes.UINT8),
i.e. value is a 2-tuple, first element of which is PTR flag ORed i.e. value is a 2-tuple, first element of which is PTR flag ORed
with offset, and second is scalar element type. with offset, and second is scalar element type.
Pointer to aggregate type:: * Pointer to an aggregate type::
"ptr2": (uctypes.PTR | 0, {"b": uctypes.UINT8 | 0}), "ptr2": (uctypes.PTR | 0, {"b": uctypes.UINT8 | 0}),
i.e. value is a 2-tuple, first element of which is PTR flag ORed i.e. value is a 2-tuple, first element of which is PTR flag ORed
with offset, second is descriptor of type pointed to. with offset, second is descriptor of type pointed to.
Bitfields:: * Bitfields::
"bitf0": uctypes.BFUINT16 | 0 | 0 << uctypes.BF_POS | 8 << uctypes.BF_LEN, "bitf0": uctypes.BFUINT16 | 0 | 0 << uctypes.BF_POS | 8 << uctypes.BF_LEN,
i.e. value is type of scalar value containing given bitfield (typenames are i.e. value is type of scalar value containing given bitfield (typenames are
similar to scalar types, but prefixes with "BF"), ORed with offset for similar to scalar types, but prefixes with "BF"), ORed with offset for
scalar value containing the bitfield, and further ORed with values for scalar value containing the bitfield, and further ORed with values for
bit offset and bit length of the bitfield within scalar value, shifted by bit offset and bit length of the bitfield within scalar value, shifted by
BF_POS and BF_LEN positions, respectively. Bitfield position is counted BF_POS and BF_LEN positions, respectively. Bitfield position is counted
from the least significant bit, and is the number of right-most bit of a from the least significant bit, and is the number of right-most bit of a
field (in other words, it's a number of bits a scalar needs to be shifted field (in other words, it's a number of bits a scalar needs to be shifted
right to extra the bitfield). right to extra the bitfield).
In the example above, first UINT16 value will be extracted at offset 0 In the example above, first UINT16 value will be extracted at offset 0
(this detail may be important when accessing hardware registers, where (this detail may be important when accessing hardware registers, where
particular access size and alignment are required), and then bitfield particular access size and alignment are required), and then bitfield
whose rightmost bit is least-significant bit of this UINT16, and length whose rightmost bit is least-significant bit of this UINT16, and length
is 8 bits, will be extracted - effectively, this will access is 8 bits, will be extracted - effectively, this will access
least-significant byte of UINT16. least-significant byte of UINT16.
Note that bitfield operations are independent of target byte endianness, Note that bitfield operations are independent of target byte endianness,
in particular, example above will access least-significant byte of UINT16 in particular, example above will access least-significant byte of UINT16
in both little- and big-endian structures. But it depends on the least in both little- and big-endian structures. But it depends on the least
significant bit being numbered 0. Some targets may use different significant bit being numbered 0. Some targets may use different
numbering in their native ABI, but ``uctypes`` always uses normalized numbering in their native ABI, but ``uctypes`` always uses normalized
numbering described above. numbering described above.
Module contents Module contents
--------------- ---------------
...@@ -103,17 +112,18 @@ Module contents ...@@ -103,17 +112,18 @@ Module contents
.. data:: LITTLE_ENDIAN .. data:: LITTLE_ENDIAN
Little-endian packed structure. (Packed means that every field occupies Layout type for a little-endian packed structure. (Packed means that every
exactly as many bytes as defined in the descriptor, i.e. alignment is 1). field occupies exactly as many bytes as defined in the descriptor, i.e.
the alignment is 1).
.. data:: BIG_ENDIAN .. data:: BIG_ENDIAN
Big-endian packed structure. Layour type for a big-endian packed structure.
.. data:: NATIVE .. data:: NATIVE
Native structure - with data endianness and alignment conforming to Layout type for a native structure - with data endianness and alignment
the ABI of the system on which MicroPython runs. conforming to the ABI of the system on which MicroPython runs.
.. function:: sizeof(struct) .. function:: sizeof(struct)
...@@ -145,33 +155,55 @@ Structure descriptors and instantiating structure objects ...@@ -145,33 +155,55 @@ Structure descriptors and instantiating structure objects
Given a structure descriptor dictionary and its layout type, you can Given a structure descriptor dictionary and its layout type, you can
instantiate a specific structure instance at a given memory address instantiate a specific structure instance at a given memory address
using uctypes.struct() constructor. Memory address usually comes from using :class:`uctypes.struct()` constructor. Memory address usually comes from
following sources: following sources:
* Predefined address, when accessing hardware registers on a baremetal * Predefined address, when accessing hardware registers on a baremetal
system. Lookup these addresses in datasheet for a particular MCU/SoC. system. Lookup these addresses in datasheet for a particular MCU/SoC.
* As return value from a call to some FFI (Foreign Function Interface) * As a return value from a call to some FFI (Foreign Function Interface)
function. function.
* From uctypes.addressof(), when you want to pass arguments to FFI * From uctypes.addressof(), when you want to pass arguments to an FFI
function, or alternatively, to access some data for I/O (for example, function, or alternatively, to access some data for I/O (for example,
data read from file or network socket). data read from a file or network socket).
Structure objects Structure objects
----------------- -----------------
Structure objects allow accessing individual fields using standard dot Structure objects allow accessing individual fields using standard dot
notation: ``my_struct.field1``. If a field is of scalar type, getting notation: ``my_struct.substruct1.field1``. If a field is of scalar type,
it will produce primitive value (Python integer or float) corresponding getting it will produce a primitive value (Python integer or float)
to value contained in a field. Scalar field can also be assigned to. corresponding to the value contained in a field. A scalar field can also
be assigned to.
If a field is an array, its individual elements can be accessed with If a field is an array, its individual elements can be accessed with
standard subscript operator - both read and assigned to. the standard subscript operator ``[]`` - both read and assigned to.
If a field is a pointer, it can be dereferenced using ``[0]`` syntax If a field is a pointer, it can be dereferenced using ``[0]`` syntax
(corresponding to C ``*`` operator, though ``[0]`` works in C too). (corresponding to C ``*`` operator, though ``[0]`` works in C too).
Subscripting pointer with other integer values but 0 are supported too, Subscripting a pointer with other integer values but 0 are supported too,
with the same semantics as in C. with the same semantics as in C.
Summing up, accessing structure fields generally follows C syntax, Summing up, accessing structure fields generally follows C syntax,
except for pointer derefence, you need to use ``[0]`` operator instead except for pointer derefence, when you need to use ``[0]`` operator
of ``*``. instead of ``*``.
Limitations
-----------
Accessing non-scalar fields leads to allocation of intermediate objects
to represent them. This means that special care should be taken to
layout a structure which needs to be accessed when memory allocation
is disabled (e.g. from an interrupt). The recommendations are:
* Avoid nested structures. For example, instead of
``mcu_registers.peripheral_a.register1``, define separate layout
descriptors for each peripheral, to be accessed as
``peripheral_a.register1``.
* Avoid other non-scalar data, like array. For example, instead of
``peripheral_a.register[0]`` use ``peripheral_a.register0``.
Note that these recommendations will lead to decreased readability
and conciseness of layouts, so they should be used only if the need
to access structure fields without allocation is anticipated (it's
even possible to define 2 parallel layouts - one for normal usage,
and a restricted one to use when memory allocation is prohibited).
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment