【雕爷学编程】MicroPython手册之内置模块 uctypes

在这里插入图片描述

MicroPython是为了在嵌入式系统中运行Python 3编程语言而设计的轻量级版本解释器。与常规Python相比,MicroPython解释器体积小(仅100KB左右),通过编译成二进制Executable文件运行,执行效率较高。它使用了轻量级的垃圾回收机制并移除了大部分Python标准库,以适应资源限制的微控制器。

MicroPython主要特点包括:
1、语法和功能与标准Python兼容,易学易用。支持Python大多数核心语法。
2、对硬件直接访问和控制,像Arduino一样控制GPIO、I2C、SPI等。
3、强大的模块系统,提供文件系统、网络、图形界面等功能。
4、支持交叉编译生成高效的原生代码,速度比解释器快10-100倍。
5、代码量少,内存占用小,适合运行在MCU和内存小的开发板上。
6、开源许可,免费使用。Shell交互环境为开发测试提供便利。
7、内置I/O驱动支持大量微控制器平台,如ESP8266、ESP32、STM32、micro:bit、掌控板和PyBoard等。有活跃的社区。

MicroPython的应用场景包括:
1、为嵌入式产品快速构建原型和用户交互。
2、制作一些小型的可 programmable 硬件项目。
3、作为教育工具,帮助初学者学习Python和物联网编程。
4、构建智能设备固件,实现高级控制和云连接。
5、各种微控制器应用如物联网、嵌入式智能、机器人等。

使用MicroPython需要注意:
1、内存和Flash空间有限。
2、解释执行效率不如C语言。
3、部分库函数与标准版有差异。
4、针对平台优化语法,订正与标准Python的差异。
5、合理使用内存资源,避免频繁分配大内存块。
6、利用原生代码提升速度关键部位的性能。
7、适当使用抽象来封装底层硬件操作。

总体来说,MicroPython让Python进入了微控制器领域,是一项重要的创新,既降低了编程门槛,又提供了良好的硬件控制能力。非常适合各类物联网和智能硬件的开发。

在这里插入图片描述

MicroPython的内置模块uctypes是一个提供对二进制数据以结构化的方式访问的模块。

它的主要特点是:

它基于C语言的数据类型和结构体,可以定义和操作复杂的数据结构,如数组、指针、位域等。
它可以访问任意内存地址,包括I/O和控制寄存器,从而实现对硬件的直接控制。
它支持不同的字节序和对齐方式,可以处理不同平台和协议的数据格式。
它提供了一些便捷的函数和方法,如uctypes.sizeof(), uctypes.addressof(), uctypes.struct(), uctypes.bytearray_at()等。

uctypes模块的应用场景是:

当需要处理二进制文件或网络数据包时,可以使用uctypes模块来解析和构造数据结构,从而提高效率和灵活性。
当需要与外部设备或传感器通信时,可以使用uctypes模块来访问和控制硬件寄存器,从而实现低级别的硬件编程。
当需要优化内存使用或性能时,可以使用uctypes模块来定义和操作紧凑的数据结构,从而节省空间和时间。

uctypes模块的注意事项是:

在使用uctypes模块之前,需要导入它,如import uctypes。
在定义数据结构时,需要指定每个字段的偏移量、类型、大小等属性,以及整个结构的字节序和对齐方式。
在访问数据结构时,需要使用点语法或下标语法来引用子字段或数组元素,以及使用uctypes模块提供的函数和方法来获取或修改数据值。
在使用uctypes模块时,需要注意内存安全和错误处理,避免访问非法或无效的内存地址,以及捕获可能抛出的异常。

uctypes模块的几个实际运用程序案例是:

案例一:解析一个ELF文件头,并打印出其魔数、字节序和机器类型。ELF文件头的结构定义参考。

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)等

案例二:定义一个包含两个浮点数的坐标结构,以及一个包含一个字节和一个指向坐标结构的指针的结构,然后创建一个这样的结构对象,并修改其指针指向的坐标值。

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)

案例三:访问和控制STM32F4xx芯片的窗口看门狗(WWDG)寄存器,实现看门狗功能。WWDG寄存器的结构定义参考。

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")

案例四:访问C结构体中的字段:

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)

在这个示例中,我们使用uctypes模块定义了一个名为Point的C结构体,包含了两个整数字段x和y。我们通过创建C结构体实例,并使用uctypes提供的set_field()和field()函数来设置和获取结构体字段的值。

案例五:与C指针交互:

import uctypes

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

在这个示例中,我们使用uctypes模块创建了一个C指针,并使用set()和get()函数来设置和获取指针所指向的值。这可以在MicroPython中与C代码进行交互时使用,例如通过指针传递数据。

案例六:读取外部设备寄存器:

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)

在这个示例中,我们使用uctypes模块定义了一个寄存器映射的C结构体RegisterMap,包含了一些寄存器的字段。通过指定寄存器映射的起始地址,我们可以使用uctypes提供的函数来读取寄存器的状态和数据。这些实际运用程序案例展示了使用MicroPython内置模块uctypes与C语言结构体、指针和寄存器进行交互的功能。通过使用uctypes模块,我们可以访问C结构体中的字段,与C指针交互以及读取外部设备的寄存器。这对于与底层硬件交互、访问外部C库以及进行底层系统编程非常有用。

需要注意的是,使用uctypes模块需要对C语言结构体和指针有一定的了解,以确保正确地定义和访问字段。此外,注意在嵌入式系统中使用uctypes时,要小心处理指针和内存访问,以避免潜在的安全问题和内存错误。

案例七:解析二进制数据

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

在这个例子中,我们定义了一个 C 结构体的描述符 descriptor,描述了结构体中的字段类型和顺序。然后,我们创建了一个字节对象 buffer,其中包含了二进制数据。通过调用 uctypes.struct() 函数,我们将字节对象解析为 C 结构体,并可以通过访问结构体的字段来获取数据。

案例八|:读取硬件寄存器

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

在这个例子中,我们定义了一个硬件寄存器的描述符 descriptor,使用了位字段类型 BFUINT32 来指定字段的位宽和偏移量。通过读取硬件寄存器的值,并将其传递给 uctypes.struct() 函数,我们可以解析寄存器中的数据,并通过访问字段来获取具体的值。

案例九:修改二进制数据

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'

在这个例子中,我们创建了一个字节对象 buffer,其中包含了二进制数据。通过调用 uctypes.struct() 函数,我们将字节对象解析为 C 结构体,并可以修改结构体的字段值。在这个例子中,我们修改了字段 x、y 和 z 的值,然后打印修改后的字节对象,可以看到相应的字段值已被更新。

这些案例展示了 uctypes 模块的实际应用,包括解析二进制数据、读取硬件寄存器以及修改二进制数据。通过使用 uctypes 模块,你可以在 MicroPython 设备上与 C 语言数据结构进行交互,从而实现与底层硬件和外部二进制数据的交互。请注意,在使用 uctypes 模块时,请参考 MicroPython 的文档以了解更多关于数据类型和描述符的细节。

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_41659040/article/details/132787842