Skip to content

Commit 7a0636e

Browse files
committed
docs: Add initial "uctypes" modules docs. WIP.
1 parent 7ee91cf commit 7a0636e

2 files changed

Lines changed: 139 additions & 0 deletions

File tree

docs/library/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ it will fallback to loading the built-in ``ujson`` module.
3939
:maxdepth: 1
4040

4141
ubinascii.rst
42+
uctypes.rst
4243
uhashlib.rst
4344
uheapq.rst
4445
ujson.rst

docs/library/uctypes.rst

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
:mod:`uctypes` -- access C structures
2+
=====================================
3+
4+
.. module:: uctypes
5+
:synopsis: access C structures
6+
7+
This module implements "foreign data interface" for MicroPython. The idea
8+
behind it is similar to CPython's ``ctypes`` modules, but actual API is
9+
different, steamlined and optimized for small size.
10+
11+
Defining structure layout
12+
-------------------------
13+
14+
Structure layout is defined by a "descriptor" - a Python dictionary which
15+
encodes field names as keys and other properties required to access them as
16+
an associated values. Currently, uctypes requires explicit specification of
17+
offsets for each field. Offset are given in bytes from structure start.
18+
19+
Following are encoding examples for various field types:
20+
21+
Scalar types::
22+
23+
"field_name": uctypes.UINT32 | 0
24+
25+
in other words, value is scalar type identifier ORed with field offset
26+
(in bytes) from the start of the structure.
27+
28+
Recursive structures::
29+
30+
"sub": (2, {
31+
"b0": uctypes.UINT8 | 0,
32+
"b1": uctypes.UINT8 | 1,
33+
})
34+
35+
i.e. value is a 2-tuple, first element of which is offset, and second is
36+
a structure descriptor dictionary (note: offsets in recursive descriptors
37+
are relative to a structure it defines).
38+
39+
Arrays of primitive types::
40+
41+
"arr": (uctypes.ARRAY | 0, uctypes.UINT8 | 2),
42+
43+
i.e. value is a 2-tuple, first element of which is ARRAY flag ORed
44+
with offset, and second is scalar element type ORed number of elements
45+
in array.
46+
47+
Arrays of aggregate types::
48+
49+
"arr2": (uctypes.ARRAY | 0, 2, {"b": uctypes.UINT8 | 0}),
50+
51+
i.e. value is a 3-tuple, first element of which is ARRAY flag ORed
52+
with offset, second is a number of elements in array, and third is
53+
descriptor of element type.
54+
55+
Pointer to a primitive type::
56+
57+
"ptr": (uctypes.PTR | 0, uctypes.UINT8),
58+
59+
i.e. value is a 2-tuple, first element of which is PTR flag ORed
60+
with offset, and second is scalar element type.
61+
62+
Pointer to aggregate type::
63+
64+
"ptr2": (uctypes.PTR | 0, {"b": uctypes.UINT8 | 0}),
65+
66+
i.e. value is a 2-tuple, first element of which is PTR flag ORed
67+
with offset, second is descriptor of type pointed to.
68+
69+
Bitfields::
70+
71+
"bitf0": uctypes.BFUINT16 | 0 | 0 << uctypes.BF_POS | 8 << uctypes.BF_LEN,
72+
73+
i.e. value is type of scalar value containing given bitfield (typenames are
74+
similar to scalar types, but prefixes with "BF"), ORed with offset for
75+
scalar value containing the bitfield, and further ORed with values for
76+
bit offset and bit length of the bitfield within scalar value, shifted by
77+
BF_POS and BF_LEN positions, respectively. Bitfield position is counted
78+
from the least significant bit, and is the number of right-most bit of a
79+
field (in other words, it's a number of bits a scalar needs to be shifted
80+
right to extra the bitfield).
81+
82+
In the example above, first UINT16 value will be extracted at offset 0
83+
(this detail may be important when accessing hardware registers, where
84+
particular access size and alignment are required), and then bitfield
85+
whose rightmost bit is least-significant bit of this UINT16, and length
86+
is 8 bits, will be extracted - effectively, this will access
87+
least-significant byte of UINT16.
88+
89+
Note that bitfield operations are independent of target byte endianness,
90+
in particular, example above will access least-significant byte of UINT16
91+
in both little- and big-endian structures. But it depends on the least
92+
significant bit being numbered 0. Some targets may use different
93+
numbering in their native ABI, but ``uctypes`` always uses normalized
94+
numbering described above.
95+
96+
Module contents
97+
---------------
98+
99+
.. class:: struct(descriptor, layout_type)
100+
101+
Create a "foreign data structure" object based on its descriptor (encoded
102+
as a dictionary) and layout type.
103+
104+
.. data:: LITTLE_ENDIAN
105+
106+
Little-endian packed structure. (Packed means that every field occupies
107+
exactly many bytes as defined in the descriptor, i.e. alignment is 1).
108+
109+
.. data:: BIG_ENDIAN
110+
111+
Big-endian packed structure.
112+
113+
.. data:: NATIVE
114+
115+
Native structure - with data endianness and alignment conforming to
116+
the target ABI.
117+
118+
(to be continued)
119+
120+
Structure objects
121+
-----------------
122+
123+
Structure objects allow accessing individual fields using standard dot
124+
notation: ``my_struct.field1``. If a field is of scalar type, getting
125+
it will produce primitive value (Python integer or float) corresponding
126+
to value contained in a field. Scalar field can also be assigned to.
127+
128+
If a field is an array, its individual elements can be accessed with
129+
standard subscript operator - both read and assigned to.
130+
131+
If a field is a pointer, it can be dereferenced using ``[0]`` syntax
132+
(corresponding to C ``*`` operator, though ``[0]`` works in C too).
133+
Subscripting pointer with other integer values but 0 are supported too,
134+
with the same semantics as in C.
135+
136+
Summing up, accessing structure fields generally follows C syntax,
137+
except for pointer derefence, you need to use ``[0]`` operator instead
138+
of ``*``.

0 commit comments

Comments
 (0)