Uctypes Module API Documentation#
Overview#
This module provides a way to access binary data in a structured manner. It is the “external data interface” module in MicroPython, conceptually similar to CPython’s ctypes module, but with a simplified and optimized API tailored for embedded systems. By defining data structures with layouts similar to those in C, users can access subfields using dot notation.
For specific details, please refer to the uctypes official documentation
Warning
The
uctypesmodule allows access to arbitrary memory addresses on the machine (including I/O and control registers). Improper use can lead to system crashes, data loss, or even hardware damage.Also see the
ustructmoduleThe
ustructmodule is the standard way to handle binary data in Python. It is suitable for handling simple data structures but not for complex, nested structures.
Structure Description#
Examples#
import uctypes
# Example 1: Parsing part of an ELF file header
ELF_HEADER = {
"EI_MAG": (0x0 | uctypes.ARRAY, 4 | uctypes.UINT8),
"EI_DATA": 0x5 | uctypes.UINT8,
"e_machine": 0x12 | uctypes.UINT16,
}
# Assuming "f" is an ELF file opened in binary mode
buf = f.read(uctypes.sizeof(ELF_HEADER, uctypes.LITTLE_ENDIAN))
header = uctypes.struct(uctypes.addressof(buf), ELF_HEADER, uctypes.LITTLE_ENDIAN)
assert header.EI_MAG == b"\x7fELF"
assert header.EI_DATA == 1, "Oops, endianness error. Try using uctypes.BIG_ENDIAN."
print("machine:", hex(header.e_machine))
# Example 2: Data structure in memory (with pointers)
COORD = {
"x": 0 | uctypes.FLOAT32,
"y": 4 | uctypes.FLOAT32,
}
STRUCT1 = {
"data1": 0 | uctypes.UINT8,
"data2": 4 | uctypes.UINT32,
"ptr": (8 | uctypes.PTR, COORD),
}
# Assuming you have a structure at address "addr"
struct1 = uctypes.struct(addr, STRUCT1, uctypes.NATIVE)
print("x:", struct1.ptr[0].x)
# Example 3: Accessing CPU registers, STM32F4xx WWDG registers
WWDG_LAYOUT = {
"WWDG_CR": (0, {
"WDGA": 7 << uctypes.BF_POS | 1 << uctypes.BF_LEN | uctypes.BFUINT32,
"T": 0 << uctypes.BF_POS | 7 << uctypes.BF_LEN | uctypes.BFUINT32,
}),
"WWDG_CFR": (4, {
"EWI": 9 << uctypes.BF_POS | 1 << uctypes.BF_LEN | uctypes.BFUINT32,
"WDGTB": 7 << uctypes.BF_POS | 2 << uctypes.BF_LEN | uctypes.BFUINT32,
"W": 0 << uctypes.BF_POS | 7 << uctypes.BF_LEN | uctypes.BFUINT32,
}),
}
WWDG = uctypes.struct(0x40002c00, WWDG_LAYOUT)
WWDG.WWDG_CFR.WDGTB = 0b10
WWDG.WWDG_CR.WDGA = 1
print("Current counter:", WWDG.WWDG_CR.T)
Structure Layout Definition#
uctypes uses Python dictionaries to define structure layouts. The keys in the dictionary are field names, and the values are field attributes (such as offset, data type, etc.). The offset of a field is calculated in bytes from the start of the structure.
Examples:
Scalar Types:
"field_name": offset | uctypes.UINT32
That is, the field value is the result of the bitwise OR operation between the offset and the scalar type.
Nested Structures:
"sub": (offset, {
"b0": 0 | uctypes.UINT8,
"b1": 1 | uctypes.UINT8,
})
i.e. value is a 2-tuple, first element of which is an offset, and second is a structure descriptor dictionary (note: offsets in recursive descriptors are relative to the structure it defines). Of course, recursive structures can be specified not just by a literal dictionary, but by referring to a structure descriptor dictionary (defined earlier) by name.
Arrays:
"arr": (offset | uctypes.ARRAY, size | uctypes.UINT8)
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 the array.
Arrays of aggregate types:
"arr2": (offset | uctypes.ARRAY, size, {"b": 0 | uctypes.UINT8})
i.e. value is a 3-tuple, first element of which is ARRAY flag ORed with offset, second is a number of elements in the array, and third is a descriptor of element type.
Pointer to a primitive type:
"ptr": (offset | uctypes.PTR, uctypes.UINT8)
i.e. value is a 2-tuple, first element of which is PTR flag ORed with offset, and second is a scalar element type.
Pointer to an aggregate type:
"ptr2": (offset | uctypes.PTR, {"b": 0 | uctypes.UINT8})
i.e. value is a 2-tuple, first element of which is PTR flag ORed with offset, second is a descriptor of type pointed to.
Bitfields:
"bitf0": offset | uctypes.BFUINT16 | lsbit << uctypes.BF_POS | bitsize << uctypes.BF_LEN
i.e. value is a 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 position and bit length of the bitfield within the scalar value, shifted by BF_POS and BF_LEN bits, respectively. A bitfield position is counted from the least significant bit of the scalar (having position of 0), 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 extract the bitfield).
In the example above, first a 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 lsbit bit of this UINT16, and length is bitsize bits, will be extracted. For example, if lsbit is 0 and bitsize is 8, then effectively it 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 the normalized numbering described above.
API Introduction#
struct Class#
class uctypes.struct(addr, descriptor, layout_type=NATIVE)
Instantiates a structure object based on memory address, descriptor, and layout type.
Parameters:
addr: Memory addressdescriptor: Structure descriptor (dictionary)layout_type: Optional, defaults toNATIVE(native byte order)
Returns: Returns a structure object
sizeof#
uctypes.sizeof(struct, layout_type=NATIVE)
Return size of data structure in bytes. The struct argument can be either a structure class or a specific instantiated structure object (or its aggregate field).
Parameters:
struct: Structure class or instancelayout_type: Optional, defaults toNATIVE
Returns: Size of the structure
addressof#
uctypes.addressof(obj)
Returns the memory address of an object.
Parameters:
obj: An object that supports the buffer protocol
Returns: Memory address of the object
bytes_at#
uctypes.bytes_at(addr, size)
Captures memory at the given address and size, returning an immutable bytes object.
bytearray_at#
uctypes.bytearray_at(addr, size)
Captures memory at the given address and size, returning a mutable bytearray object.
string_at#
uctypes.string_at(addr, size=1048576)
Gets a string from the given address, with a maximum length specified by size.
Constant Definitions#
uctypes.LITTLE_ENDIAN#
Represents little-endian byte order layout type.
uctypes.BIG_ENDIAN#
Represents big-endian byte order layout type.
uctypes.NATIVE#
Represents native byte order and alignment layout type.
Integer Types#
Defines various bit-width integer types, including:
uctypes.UINT8,uctypes.INT8uctypes.UINT16,uctypes.INT16uctypes.UINT32,uctypes.INT32uctypes.UINT64,uctypes.INT64
Floating Point Types#
uctypes.FLOAT32,uctypes.FLOAT64
uctypes.VOID#
Used to represent a void type, commonly used for pointers.
uctypes.PTR and uctypes.ARRAY#
Type constants for pointers and arrays. Note that there is no explicit constant for structures, it’s implicit: an aggregate type without PTR or ARRAY flags is a structure.
Structure Descriptor and Structure Object Instantiation#
You can instantiate structure objects at specified memory addresses using the structure descriptor dictionary and layout type with uctypes.struct().
