[Diaoye learns programming] MicroPython manual's built-in module uctypes

Insert image description here

MicroPython is a lightweight version of the interpreter designed to run the Python 3 programming language in embedded systems. Compared with regular Python, the MicroPython interpreter is small (only about 100KB) and is compiled into a binary Executable file to run, resulting in higher execution efficiency. It uses a lightweight garbage collection mechanism and removes most of the Python standard library to accommodate resource-constrained microcontrollers.

The main features of MicroPython include:
1. The syntax and functions are compatible with standard Python, making it easy to learn and use. Supports most of Python's core syntax.
2. Directly access and control the hardware, control GPIO, I2C, SPI, etc. like Arduino.
3. A powerful module system that provides functions such as file system, network, and graphical interface.
4. Support cross-compilation to generate efficient native code, which is 10-100 times faster than the interpreter.
5. The amount of code is small, and the memory usage is small, which is suitable for running on MCU and development boards with small memory.
6. Open source license, free to use. The Shell interactive environment provides convenience for development and testing.
7. The built-in I/O driver supports a large number of microcontroller platforms, such as ESP8266, ESP32, STM32, micro:bit, control board and PyBoard, etc. There is an active community.

The application scenarios of MicroPython include:
1. Quickly build prototypes and user interactions for embedded products.
2. Make some small programmable hardware projects.
3. As an educational tool, it helps beginners learn Python and IoT programming.
4. Build smart device firmware to achieve advanced control and cloud connectivity.
5. Various microcontroller applications such as Internet of Things, embedded intelligence, robots, etc.

Pay attention to the following when using MicroPython:
1. The memory and Flash space are limited.
2. The explanation and execution efficiency is not as good as C language.
3. Some library functions are different from the standard version.
4. Optimize the syntax for the platform and correct the differences with standard Python.
5. Use memory resources rationally and avoid frequently allocating large memory blocks.
6. Use native code to improve the performance of speed-critical parts.
7. Use abstraction appropriately to encapsulate underlying hardware operations.

Generally speaking, MicroPython brings Python into the field of microcontrollers, which is an important innovation that not only lowers the programming threshold but also provides good hardware control capabilities. It is very suitable for the development of various types of Internet of Things and intelligent hardware.

Insert image description here

MicroPython's built-in module uctypes is a module that provides structured access to binary data.

Its main features are:

It is based on the data types and structures of C language and can define and operate complex data structures, such as arrays, pointers, bit fields, etc.
It can access any memory address, including I/O and control registers, allowing direct control of the hardware.
It supports different endianness and alignment methods and can handle data formats of different platforms and protocols.
It provides some convenient functions and methods, such as uctypes.sizeof(), uctypes.addressof(), uctypes.struct(), uctypes.bytearray_at(), etc.

The application scenarios of the uctypes module are:

When you need to process binary files or network packets, you can use the uctypes module to parse and construct data structures, thereby improving efficiency and flexibility.
When communication with external devices or sensors is required, the uctypes module can be used to access and control hardware registers, enabling low-level hardware programming.
When you need to optimize memory usage or performance, you can use the uctypes module to define and manipulate compact data structures, saving space and time.

Things to note about the uctypes module are:

Before using the uctypes module, you need to import it, such as import uctypes.
When defining a data structure, you need to specify the offset, type, size and other attributes of each field, as well as the endianness and alignment of the entire structure.
When accessing data structures, you need to use dot syntax or subscript syntax to refer to subfields or array elements, and use the functions and methods provided by the uctypes module to get or modify data values.
When using the uctypes module, you need to pay attention to memory safety and error handling, avoid accessing illegal or invalid memory addresses, and catch exceptions that may be thrown.

Several practical application examples of the uctypes module are:

Case 1: Parse an ELF file header and print out its magic number, endianness and machine type. Reference to the structure definition of the ELF file header.

import uctypes

# 定义ELF文件头的结构描述符
ELF_HEADER = {
    
    
    "EI_MAG": (0x0 | uctypes.ARRAY, 4 | uctypes.UINT8), # 魔数
    "EI_DATA": 0x5 | uctypes.UINT8, # 字节序
    "e_machine": 0x12 | uctypes.UINT16, # 机器类型
}

# 打开一个ELF文件并读取其头部数据
f = open("test.elf", "rb")
buf = f.read(uctypes.sizeof(ELF_HEADER, uctypes.LITTLE_ENDIAN))
f.close()

# 创建一个ELF文件头的结构对象,并指定其字节序为小端
header = uctypes.struct(uctypes.addressof(buf), ELF_HEADER, uctypes.LITTLE_ENDIAN)

# 打印出ELF文件头的信息
print("Magic:", header.EI_MAG) # 魔数应为b'\x7fELF'
print("Data:", header.EI_DATA) # 字节序应为1(小端)或2(大端)
print("Machine:", hex(header.e_machine)) # 机器类型应为0x28(ARM)或0x3e(x86_64)等

Case 2: Define a coordinate structure containing two floating point numbers, and a structure containing a byte and a pointer to the coordinate structure, then create such a structure object and modify the coordinate value pointed by its pointer.

import uctypes

# 定义坐标结构的描述符
COORD = {
    
    
    "x": 0 | uctypes.FLOAT32, # x坐标
    "y": 4 | uctypes.FLOAT32, # y坐标
}

# 定义包含一个字节和一个指针的结构的描述符
STRUCT1 = {
    
    
    "data1": 0 | uctypes.UINT8, # 一个字节
    "ptr": (4 | uctypes.PTR, COORD), # 一个指向坐标结构的指针
}

# 创建一个包含一个字节和一个指针的结构对象,并初始化其值
struct1 = uctypes.struct(0x20000000, STRUCT1, uctypes.NATIVE)
struct1.data1 = 0x55
struct1.ptr = 0x20000010

# 创建一个坐标结构对象,并初始化其值
coord = uctypes.struct(0x20000010, COORD, uctypes.NATIVE)
coord.x = 3.14
coord.y = 2.71

# 修改坐标结构对象的值,并打印出结构对象的值
coord.x += 1.0
coord.y -= 1.0
print("data1:", hex(struct1.data1))
print("ptr:", hex(struct1.ptr))
print("x:", struct1.ptr[0].x)
print("y:", struct1.ptr[0].y)

Case 3: Access and control the window watchdog (WWDG) register of the STM32F4xx chip to implement the watchdog function. Structure definition reference of WWDG register.

import uctypes
import utime

# 定义WWDG寄存器的结构描述符
WWDG_LAYOUT = {
    
    
    "WWDG_CR": (0, {
    
     # WWDG控制寄存器,位域类型为BFUINT32
        "WDGA": 7 << uctypes.BF_POS | 1 << uctypes.BF_LEN | uctypes.BFUINT32, # 看门狗激活位,置1启动看门狗
        "T": 0 << uctypes.BF_POS | 7 << uctypes.BF_LEN | uctypes.BFUINT32, # 看门狗计数器值,范围为0x40~0x7f,超时时间为T*PCLK/4096
    }),
    "WWDG_CFR": (4, {
    
     # WWDG配置寄存器,位域类型为BFUINT32
        "EWI": 9 << uctypes.BF_POS | 1 << uctypes.BF_LEN | uctypes.BFUINT32, # 看门狗早期唤醒中断使能位,置1使能中断
        "WDGTB": 7 << uctypes.BF_POS | 2 << uctypes.BF_LEN | uctypes.BFUINT32, # 看门狗预分频器值,范围为0~3,分频比为2^(WDGTB+2)
        "W": 0 << uctypes.BF_POS | 7 << uctypes.BF_LEN | uctypes.BFUINT32, # 看门狗窗口值,范围为0x40~0x7f,计数器必须在窗口内刷新,否则复位
    }),
}

# 创建一个WWDG寄存器的结构对象,并指定其地址为0x40002c00(参考芯片手册)
WWDG = uctypes.struct(0x40002c00, WWDG_LAYOUT)

# 设置WWDG寄存器的值,启动看门狗
WWDG.WWDG_CR.T = 0x7f # 设置计数器值为最大
WWDG.WWDG_CFR.WDGTB = 3 # 设置预分频器值为最大
WWDG.WWDG_CFR.W = 0x50 # 设置窗口值为0x50
WWDG.WWDG_CR.WDGA = 1 # 启动看门狗

# 在一个循环中刷新看门狗计数器,模拟正常运行和异常情况
while True:
    utime.sleep_ms(100) # 延时100毫秒
    if utime.ticks_ms() % 1000 < 500: # 模拟正常运行,每500毫秒刷新一次计数器
        WWDG.WWDG_CR.T = 0x7f # 刷新计数器值为最大
        print("Normal operation")
    else: # 模拟异常情况,不刷新计数器,导致超时复位
        print("Abnormal operation")

Case 4: Access fields in C structure:

import uctypes

# 定义C结构体
class Point(uctypes.Struct):
    _fields = [
        ("x", uctypes.INT),
        ("y", uctypes.INT),
    ]

# 创建C结构体实例
point = uctypes.struct(uctypes.addressof(uctypes.CArray(Point, 1)))
# 设置字段值
uctypes.set_field(point, Point.x, 10)
uctypes.set_field(point, Point.y, 20)
# 获取字段值
x = uctypes.field(point, Point.x)
y = uctypes.field(point, Point.y)
print("Point:", x, y)

In this example, we use the uctypes module to define a C structure named Point, which contains two integer fields x and y. We set and get the values ​​of the structure fields by creating a C structure instance and using the set_field() and field() functions provided by uctypes.

Case 5: Interacting with C pointers:

import uctypes

# 创建C指针
ptr = uctypes.pointer(uctypes.INT())
# 设置指针的值
uctypes.set(ptr, 42)
# 获取指针的值
value = uctypes.get(ptr)
print("Value:", value)

In this example, we use the uctypes module to create a C pointer and use the set() and get() functions to set and get the value pointed by the pointer. This can be used in MicroPython when interacting with C code, such as passing data via pointers.

Case 6: Reading external device registers:

import uctypes

# 定义寄存器映射结构体
class RegisterMap(uctypes.Struct):
    _fields = [
        ("status", uctypes.UINT8),
        ("data", uctypes.UINT16),
        # ...
    ]

# 从指定地址读取寄存器映射
address = 0x1000
register = uctypes.struct(address, RegisterMap)
status = uctypes.field(register, RegisterMap.status)
data = uctypes.field(register, RegisterMap.data)
print("Register Status:", status)
print("Register Data:", data)

In this example, we use the uctypes module to define a register-mapped C structure RegisterMap, which contains some register fields. By specifying the starting address of the register map, we can use the functions provided by uctypes to read the state and data of the register. These practical application examples demonstrate the ability to interact with C structures, pointers, and registers using MicroPython's built-in module uctypes. By using the uctypes module, we can access fields in C structures, interact with C pointers, and read registers of external devices. This is useful for interacting with the underlying hardware, accessing external C libraries, and doing low-level system programming.

It should be noted that using the uctypes module requires a certain understanding of C language structures and pointers to ensure that fields are defined and accessed correctly. Also, be careful when using uctypes in embedded systems to handle pointers and memory accesses to avoid potential security issues and memory errors.

Case 7: Parsing binary data

import uctypes

# 定义 C 结构体的描述符
descriptor = {
    
    
    "x": uctypes.UINT8,
    "y": uctypes.INT16,
    "z": uctypes.FLOAT,
}

# 创建字节对象
buffer = bytearray(b"\x01\x00\x7F\xFF\x41\x48\x00\x00")

# 解析二进制数据为 C 结构体
data = uctypes.struct(buffer, descriptor)

# 访问解析后的数据
print(data.x)  # 输出: 1
print(data.y)  # 输出: -129
print(data.z)  # 输出: 12.5

In this example, we define a descriptor of a C structure, describing the type and order of the fields in the structure. Then, we create a bytes object buffer, which contains binary data. By calling the uctypes.struct() function, we parse the bytes object into a C structure and can get the data by accessing the fields of the structure.

Case 8 |: Reading hardware registers

import uctypes
from machine import mem32

# 定义寄存器的描述符
descriptor = {
    
    
    "control": uctypes.BFUINT32 | 8 | 16,
    "status": uctypes.BFUINT32 | 24 | 8,
}

# 读取硬件寄存器
register = mem32[0x40000000]
data = uctypes.struct(register, descriptor)

# 访问解析后的数据
print(data.control)  # 输出: 0x12
print(data.status)  # 输出: 0x34

In this example, we define a hardware register descriptor, using the bit field type BFUINT32 to specify the bit width and offset of the field. By reading the value of a hardware register and passing it to the uctypes.struct() function, we can parse the data in the register and get the specific value by accessing the fields.

Case 9: Modify binary data

import uctypes

# 定义 C 结构体的描述符
descriptor = {
    
    
    "x": uctypes.UINT8,
    "y": uctypes.INT16,
    "z": uctypes.FLOAT,
}

# 创建字节对象
buffer = bytearray(b"\x01\x00\x7F\xFF\x41\x48\x00\x00")

# 解析二进制数据为 C 结构体
data = uctypes.struct(buffer, descriptor)

# 修改数据
data.x = 0xAA
data.y = 1000
data.z = 3.14

# 打印修改后的二进制数据
print(buffer)  # 输出: b'\xaa\x00\xe8\x03\x40\x49\x0f\xdb'

In this example, we create a bytes object buffer that contains binary data. By calling the uctypes.struct() function, we parse the byte object into a C structure and can modify the field values ​​of the structure. In this example, we modify the values ​​of fields x, y, and z, and then print the modified bytes object. You can see that the corresponding field values ​​have been updated.

These cases demonstrate practical applications of the uctypes module, including parsing binary data, reading hardware registers, and modifying binary data. By using the uctypes module, you can interact with C language data structures on MicroPython devices to interact with the underlying hardware and external binary data. Please note that when using the uctypes module, please refer to the MicroPython documentation for more details about data types and descriptors.

Insert image description here

おすすめ

転載: blog.csdn.net/weixin_41659040/article/details/132787842