docs/uctypes: Improve documentation.
Seealso and Limitations sectiosn added, better formatting and grammar.
This commit is contained in:
parent
79b40d1127
commit
343b5c1081
@ -1,97 +1,106 @@
|
||||
:mod:`uctypes` -- access C structures
|
||||
=====================================
|
||||
:mod:`uctypes` -- access binary data in a structured way
|
||||
========================================================
|
||||
|
||||
.. module:: uctypes
|
||||
:synopsis: access C structures
|
||||
:synopsis: access binary data in a structured way
|
||||
|
||||
This module implements "foreign data interface" for MicroPython. The idea
|
||||
behind it is similar to CPython's ``ctypes`` modules, but actual API is
|
||||
different, streamlined and optimized for small size.
|
||||
behind it is similar to CPython's ``ctypes`` modules, but the actual API is
|
||||
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
|
||||
-------------------------
|
||||
|
||||
Structure layout is defined by a "descriptor" - a Python dictionary which
|
||||
encodes field names as keys and other properties required to access them as
|
||||
an associated values. Currently, uctypes requires explicit specification of
|
||||
offsets for each field. Offset are given in bytes from structure start.
|
||||
associated values. Currently, uctypes requires explicit specification of
|
||||
offsets for each field. Offset are given in bytes from a structure start.
|
||||
|
||||
Following are encoding examples for various field types:
|
||||
|
||||
Scalar types::
|
||||
* Scalar types::
|
||||
|
||||
"field_name": uctypes.UINT32 | 0
|
||||
|
||||
in other words, value is scalar type identifier ORed with field offset
|
||||
(in bytes) from the start of the structure.
|
||||
in other words, value is scalar type identifier ORed with field offset
|
||||
(in bytes) from the start of the structure.
|
||||
|
||||
Recursive structures::
|
||||
* Recursive structures::
|
||||
|
||||
"sub": (2, {
|
||||
"b0": uctypes.UINT8 | 0,
|
||||
"b1": uctypes.UINT8 | 1,
|
||||
})
|
||||
|
||||
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
|
||||
are relative to a structure it defines).
|
||||
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
|
||||
are relative to a structure it defines).
|
||||
|
||||
Arrays of primitive types::
|
||||
* Arrays of primitive types::
|
||||
|
||||
"arr": (uctypes.ARRAY | 0, uctypes.UINT8 | 2),
|
||||
|
||||
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
|
||||
in array.
|
||||
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
|
||||
in array.
|
||||
|
||||
Arrays of aggregate types::
|
||||
* Arrays of aggregate types::
|
||||
|
||||
"arr2": (uctypes.ARRAY | 0, 2, {"b": uctypes.UINT8 | 0}),
|
||||
|
||||
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
|
||||
descriptor of element type.
|
||||
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
|
||||
descriptor of element type.
|
||||
|
||||
Pointer to a primitive type::
|
||||
* Pointer to a primitive type::
|
||||
|
||||
"ptr": (uctypes.PTR | 0, uctypes.UINT8),
|
||||
|
||||
i.e. value is a 2-tuple, first element of which is PTR flag ORed
|
||||
with offset, and second is scalar element type.
|
||||
i.e. value is a 2-tuple, first element of which is PTR flag ORed
|
||||
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}),
|
||||
|
||||
i.e. value is a 2-tuple, first element of which is PTR flag ORed
|
||||
with offset, second is descriptor of type pointed to.
|
||||
i.e. value is a 2-tuple, first element of which is PTR flag ORed
|
||||
with offset, second is descriptor of type pointed to.
|
||||
|
||||
Bitfields::
|
||||
* Bitfields::
|
||||
|
||||
"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
|
||||
similar to scalar types, but prefixes with "BF"), ORed with offset 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
|
||||
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
|
||||
field (in other words, it's a number of bits a scalar needs to be shifted
|
||||
right to extra the bitfield).
|
||||
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
|
||||
scalar value containing the bitfield, and further ORed with values for
|
||||
bit offset and bit length of the bitfield within scalar value, shifted by
|
||||
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
|
||||
field (in other words, it's a number of bits a scalar needs to be shifted
|
||||
right to extra the bitfield).
|
||||
|
||||
In the example above, first UINT16 value will be extracted at offset 0
|
||||
(this detail may be important when accessing hardware registers, where
|
||||
particular access size and alignment are required), and then bitfield
|
||||
whose rightmost bit is least-significant bit of this UINT16, and length
|
||||
is 8 bits, will be extracted - effectively, this will access
|
||||
least-significant byte of UINT16.
|
||||
In the example above, first UINT16 value will be extracted at offset 0
|
||||
(this detail may be important when accessing hardware registers, where
|
||||
particular access size and alignment are required), and then bitfield
|
||||
whose rightmost bit is least-significant bit of this UINT16, and length
|
||||
is 8 bits, will be extracted - effectively, this will access
|
||||
least-significant byte of UINT16.
|
||||
|
||||
Note that bitfield operations are independent of target byte endianness,
|
||||
in particular, example above will access least-significant byte of UINT16
|
||||
in both little- and big-endian structures. But it depends on the least
|
||||
significant bit being numbered 0. Some targets may use different
|
||||
numbering in their native ABI, but ``uctypes`` always uses normalized
|
||||
numbering described above.
|
||||
Note that bitfield operations are independent of target byte endianness,
|
||||
in particular, example above will access least-significant byte of UINT16
|
||||
in both little- and big-endian structures. But it depends on the least
|
||||
significant bit being numbered 0. Some targets may use different
|
||||
numbering in their native ABI, but ``uctypes`` always uses normalized
|
||||
numbering described above.
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
@ -103,17 +112,18 @@ Module contents
|
||||
|
||||
.. data:: LITTLE_ENDIAN
|
||||
|
||||
Little-endian packed structure. (Packed means that every field occupies
|
||||
exactly as many bytes as defined in the descriptor, i.e. alignment is 1).
|
||||
Layout type for a little-endian packed structure. (Packed means that every
|
||||
field occupies exactly as many bytes as defined in the descriptor, i.e.
|
||||
the alignment is 1).
|
||||
|
||||
.. data:: BIG_ENDIAN
|
||||
|
||||
Big-endian packed structure.
|
||||
Layour type for a big-endian packed structure.
|
||||
|
||||
.. data:: NATIVE
|
||||
|
||||
Native structure - with data endianness and alignment conforming to
|
||||
the ABI of the system on which MicroPython runs.
|
||||
Layout type for a native structure - with data endianness and alignment
|
||||
conforming to the ABI of the system on which MicroPython runs.
|
||||
|
||||
.. function:: sizeof(struct)
|
||||
|
||||
@ -145,33 +155,55 @@ Structure descriptors and instantiating structure objects
|
||||
|
||||
Given a structure descriptor dictionary and its layout type, you can
|
||||
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:
|
||||
|
||||
* Predefined address, when accessing hardware registers on a baremetal
|
||||
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.
|
||||
* 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,
|
||||
data read from file or network socket).
|
||||
data read from a file or network socket).
|
||||
|
||||
Structure objects
|
||||
-----------------
|
||||
|
||||
Structure objects allow accessing individual fields using standard dot
|
||||
notation: ``my_struct.field1``. If a field is of scalar type, getting
|
||||
it will produce primitive value (Python integer or float) corresponding
|
||||
to value contained in a field. Scalar field can also be assigned to.
|
||||
notation: ``my_struct.substruct1.field1``. If a field is of scalar type,
|
||||
getting it will produce a primitive value (Python integer or float)
|
||||
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
|
||||
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
|
||||
(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.
|
||||
|
||||
Summing up, accessing structure fields generally follows C syntax,
|
||||
except for pointer derefence, you need to use ``[0]`` operator instead
|
||||
of ``*``.
|
||||
except for pointer derefence, when you need to use ``[0]`` operator
|
||||
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).
|
||||
|
Loading…
Reference in New Issue
Block a user