Skip to content

Commit 343b5c1

Browse files
committed
docs/uctypes: Improve documentation.
Seealso and Limitations sectiosn added, better formatting and grammar.
1 parent 79b40d1 commit 343b5c1

1 file changed

Lines changed: 99 additions & 67 deletions

File tree

docs/library/uctypes.rst

Lines changed: 99 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,106 @@
1-
:mod:`uctypes` -- access C structures
2-
=====================================
1+
:mod:`uctypes` -- access binary data in a structured way
2+
========================================================
33

44
.. module:: uctypes
5-
:synopsis: access C structures
5+
:synopsis: access binary data in a structured way
66

77
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, streamlined and optimized for small size.
8+
behind it is similar to CPython's ``ctypes`` modules, but the actual API is
9+
different, streamlined and optimized for small size. The basic idea of the
10+
module is to define data structure layout with about the same power as the
11+
C language allows, and the access it using familiar dot-syntax to reference
12+
sub-fields.
13+
14+
.. seealso::
15+
16+
Module :mod:`ustruct`
17+
Standard Python way to access binary data structures (doesn't scale
18+
well to large and complex structures).
1019

1120
Defining structure layout
1221
-------------------------
1322

1423
Structure layout is defined by a "descriptor" - a Python dictionary which
1524
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.
25+
associated values. Currently, uctypes requires explicit specification of
26+
offsets for each field. Offset are given in bytes from a structure start.
1827

1928
Following are encoding examples for various field types:
2029

21-
Scalar types::
30+
* Scalar types::
2231

2332
"field_name": uctypes.UINT32 | 0
2433

25-
in other words, value is scalar type identifier ORed with field offset
26-
(in bytes) from the start of the structure.
34+
in other words, value is scalar type identifier ORed with field offset
35+
(in bytes) from the start of the structure.
2736

28-
Recursive structures::
37+
* Recursive structures::
2938

3039
"sub": (2, {
3140
"b0": uctypes.UINT8 | 0,
3241
"b1": uctypes.UINT8 | 1,
3342
})
3443

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).
44+
i.e. value is a 2-tuple, first element of which is offset, and second is
45+
a structure descriptor dictionary (note: offsets in recursive descriptors
46+
are relative to a structure it defines).
3847

39-
Arrays of primitive types::
48+
* Arrays of primitive types::
4049

4150
"arr": (uctypes.ARRAY | 0, uctypes.UINT8 | 2),
4251

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.
52+
i.e. value is a 2-tuple, first element of which is ARRAY flag ORed
53+
with offset, and second is scalar element type ORed number of elements
54+
in array.
4655

47-
Arrays of aggregate types::
56+
* Arrays of aggregate types::
4857

4958
"arr2": (uctypes.ARRAY | 0, 2, {"b": uctypes.UINT8 | 0}),
5059

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.
60+
i.e. value is a 3-tuple, first element of which is ARRAY flag ORed
61+
with offset, second is a number of elements in array, and third is
62+
descriptor of element type.
5463

55-
Pointer to a primitive type::
64+
* Pointer to a primitive type::
5665

5766
"ptr": (uctypes.PTR | 0, uctypes.UINT8),
5867

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.
68+
i.e. value is a 2-tuple, first element of which is PTR flag ORed
69+
with offset, and second is scalar element type.
6170

62-
Pointer to aggregate type::
71+
* Pointer to an aggregate type::
6372

6473
"ptr2": (uctypes.PTR | 0, {"b": uctypes.UINT8 | 0}),
6574

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.
75+
i.e. value is a 2-tuple, first element of which is PTR flag ORed
76+
with offset, second is descriptor of type pointed to.
6877

69-
Bitfields::
78+
* Bitfields::
7079

7180
"bitf0": uctypes.BFUINT16 | 0 | 0 << uctypes.BF_POS | 8 << uctypes.BF_LEN,
7281

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.
82+
i.e. value is type of scalar value containing given bitfield (typenames are
83+
similar to scalar types, but prefixes with "BF"), ORed with offset for
84+
scalar value containing the bitfield, and further ORed with values for
85+
bit offset and bit length of the bitfield within scalar value, shifted by
86+
BF_POS and BF_LEN positions, respectively. Bitfield position is counted
87+
from the least significant bit, and is the number of right-most bit of a
88+
field (in other words, it's a number of bits a scalar needs to be shifted
89+
right to extra the bitfield).
90+
91+
In the example above, first UINT16 value will be extracted at offset 0
92+
(this detail may be important when accessing hardware registers, where
93+
particular access size and alignment are required), and then bitfield
94+
whose rightmost bit is least-significant bit of this UINT16, and length
95+
is 8 bits, will be extracted - effectively, this will access
96+
least-significant byte of UINT16.
97+
98+
Note that bitfield operations are independent of target byte endianness,
99+
in particular, example above will access least-significant byte of UINT16
100+
in both little- and big-endian structures. But it depends on the least
101+
significant bit being numbered 0. Some targets may use different
102+
numbering in their native ABI, but ``uctypes`` always uses normalized
103+
numbering described above.
95104

96105
Module contents
97106
---------------
@@ -103,17 +112,18 @@ Module contents
103112

104113
.. data:: LITTLE_ENDIAN
105114

106-
Little-endian packed structure. (Packed means that every field occupies
107-
exactly as many bytes as defined in the descriptor, i.e. alignment is 1).
115+
Layout type for a little-endian packed structure. (Packed means that every
116+
field occupies exactly as many bytes as defined in the descriptor, i.e.
117+
the alignment is 1).
108118

109119
.. data:: BIG_ENDIAN
110120

111-
Big-endian packed structure.
121+
Layour type for a big-endian packed structure.
112122

113123
.. data:: NATIVE
114124

115-
Native structure - with data endianness and alignment conforming to
116-
the ABI of the system on which MicroPython runs.
125+
Layout type for a native structure - with data endianness and alignment
126+
conforming to the ABI of the system on which MicroPython runs.
117127

118128
.. function:: sizeof(struct)
119129

@@ -145,33 +155,55 @@ Structure descriptors and instantiating structure objects
145155

146156
Given a structure descriptor dictionary and its layout type, you can
147157
instantiate a specific structure instance at a given memory address
148-
using uctypes.struct() constructor. Memory address usually comes from
158+
using :class:`uctypes.struct()` constructor. Memory address usually comes from
149159
following sources:
150160

151161
* Predefined address, when accessing hardware registers on a baremetal
152162
system. Lookup these addresses in datasheet for a particular MCU/SoC.
153-
* As return value from a call to some FFI (Foreign Function Interface)
163+
* As a return value from a call to some FFI (Foreign Function Interface)
154164
function.
155-
* From uctypes.addressof(), when you want to pass arguments to FFI
165+
* From uctypes.addressof(), when you want to pass arguments to an FFI
156166
function, or alternatively, to access some data for I/O (for example,
157-
data read from file or network socket).
167+
data read from a file or network socket).
158168

159169
Structure objects
160170
-----------------
161171

162172
Structure objects allow accessing individual fields using standard dot
163-
notation: ``my_struct.field1``. If a field is of scalar type, getting
164-
it will produce primitive value (Python integer or float) corresponding
165-
to value contained in a field. Scalar field can also be assigned to.
173+
notation: ``my_struct.substruct1.field1``. If a field is of scalar type,
174+
getting it will produce a primitive value (Python integer or float)
175+
corresponding to the value contained in a field. A scalar field can also
176+
be assigned to.
166177

167178
If a field is an array, its individual elements can be accessed with
168-
standard subscript operator - both read and assigned to.
179+
the standard subscript operator ``[]`` - both read and assigned to.
169180

170181
If a field is a pointer, it can be dereferenced using ``[0]`` syntax
171182
(corresponding to C ``*`` operator, though ``[0]`` works in C too).
172-
Subscripting pointer with other integer values but 0 are supported too,
183+
Subscripting a pointer with other integer values but 0 are supported too,
173184
with the same semantics as in C.
174185

175186
Summing up, accessing structure fields generally follows C syntax,
176-
except for pointer derefence, you need to use ``[0]`` operator instead
177-
of ``*``.
187+
except for pointer derefence, when you need to use ``[0]`` operator
188+
instead of ``*``.
189+
190+
Limitations
191+
-----------
192+
193+
Accessing non-scalar fields leads to allocation of intermediate objects
194+
to represent them. This means that special care should be taken to
195+
layout a structure which needs to be accessed when memory allocation
196+
is disabled (e.g. from an interrupt). The recommendations are:
197+
198+
* Avoid nested structures. For example, instead of
199+
``mcu_registers.peripheral_a.register1``, define separate layout
200+
descriptors for each peripheral, to be accessed as
201+
``peripheral_a.register1``.
202+
* Avoid other non-scalar data, like array. For example, instead of
203+
``peripheral_a.register[0]`` use ``peripheral_a.register0``.
204+
205+
Note that these recommendations will lead to decreased readability
206+
and conciseness of layouts, so they should be used only if the need
207+
to access structure fields without allocation is anticipated (it's
208+
even possible to define 2 parallel layouts - one for normal usage,
209+
and a restricted one to use when memory allocation is prohibited).

0 commit comments

Comments
 (0)