|
|
@ -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). |
|
|
|
|
|
|
|
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. |
|
|
|
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. |
|
|
|
|
|
|
|
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). |
|
|
|