【雕爷学编程】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 模块是一个用于以结构化的方式访问二进制数据的接口。该模块的思想类似于 CPython 的 ctypes 模块,但实际的 API 不同,针对小尺寸进行了精简和优化。该模块允许定义数据结构布局,其功能与 C 语言允许的功能大致相同,然后使用熟悉的点语法访问它以引用子字段。

uctypes 模块的主要特点有:

它提供了一个通用的网络适配器接口,用于定义所有网络接口类的公共方法和属性。这些方法和属性包括激活或停用接口、连接或断开网络服务、查询或设置接口配置等。
它提供了一些具体的网络类实现,用于创建和管理特定类型的网络接口对象。这些类包括 WLAN(无线局域网)、LAN(有线局域网)、PPP(点对点协议)、Bluetooth(蓝牙)等。
它提供了一些网络功能函数,用于实现一些常用的网络任务。这些函数包括 ping(测试网络连通性)、route(查询或设置路由表)、reset(重置网络模块)等。

uctypes 模块可以用于以下一些应用场景:

二进制文件解析:利用 uctypes 模块,可以实现对各种格式的二进制文件的解析和访问。例如,可以使用 uctypes 模块来读取 ELF 文件头、BMP 图像文件、MP3 音频文件等,并获取它们的元数据和内容。
内存映射:利用 uctypes 模块,可以实现对内存地址空间的映射和操作。例如,可以使用 uctypes 模块来访问 CPU 寄存器、外设寄存器、内存缓冲区等,并进行读写操作。
数据结构操作:利用 uctypes 模块,可以实现对复杂的数据结构的操作和转换。例如,可以使用 uctypes 模块来创建和修改链表、树、图等,并支持指针、数组、结构体等。

使用 uctypes 模块时,需要注意以下事项:

在使用该模块之前,需要先导入 uctypes 模块,并根据不同的平台选择合适的字节序和对齐方式。
在定义结构布局时,需要使用一个字典来描述每个字段的名称、偏移量、类型等,并注意不同类型的编码方式和限制。
在访问结构数据时,需要使用一个字节对象或一个内存地址来创建一个结构对象,并使用点语法或下标运算符来引用子字段。

以下是一些使用 MicroPython 的 uctypes 模块的实际运用程序案例:

案例1:ELF 文件头解析:这是一个使用 uctypes 模块来解析 ELF 文件头并打印其信息的程序案例。:

# 导入 uctypes 模块
import uctypes

# 定义 ELF 文件头布局
ELF_HEADER = {
    # 魔数
    "EI_MAG": (0x0 | uctypes.ARRAY, 4 | uctypes.UINT8),
    # 字节序
    "EI_DATA": 0x5 | uctypes.UINT8,
    # 目标机器
    "e_machine": 0x12 | uctypes.UINT16,
    # 入口地址
    "e_entry": 0x18 | uctypes.UINT32,
}

# 打开一个 ELF 文件,以二进制模式读取
with open("test.elf", "rb") as f:
    # 读取文件头大小的字节
    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"
    # 打印目标机器
    print("machine:", hex(header.e_machine))
    # 打印入口地址
    print("entry:", hex(header.e_entry))

案例2:BMP 图像文件解析:这是一个使用 uctypes 模块来解析 BMP 图像文件并获取其宽度、高度和像素数据的程序案例。:

# 导入 uctypes 模块
import uctypes

# 定义 BMP 文件头布局
BMP_HEADER = {
    # 文件类型标识符
    "bfType": 0x0 | uctypes.UINT16,
    # 文件大小
    "bfSize": 0x2 | uctypes.UINT32,
    # 数据偏移量
    "bfOffBits": 0xA | uctypes.UINT32,
    # 信息头大小
    "biSize": 0xE | uctypes.UINT32,
    # 图像宽度
    "biWidth": 0x12 | uctypes.UINT32,
    # 图像高度
    "biHeight": 0x16 | uctypes.UINT32,
    # 颜色位数
    "biBitCount": 0x1C | uctypes.UINT16,
}

# 打开一个 BMP 文件,以二进制模式读取
with open("test.bmp", "rb") as f:
    # 读取文件头大小的字节
    buf = f.read(uctypes.sizeof(BMP_HEADER, uctypes.LITTLE_ENDIAN))
    # 创建一个结构对象,使用小端序
    header = uctypes.struct(uctypes.addressof(buf), BMP_HEADER, uctypes.LITTLE_ENDIAN)
    # 断言文件类型是否正确
    assert header.bfType == 0x4D42
    # 打印图像宽度和高度
    print("width:", header.biWidth)
    print("height:", header.biHeight)
    # 计算每行像素所占的字节数(需要对齐到 4 字节边界)
    row_size = (header.biWidth * header.biBitCount + 31) // 32 * 4
    # 计算图像数据所占的字节数(不包括文件头)
    data_size = row_size * header.biHeight
    # 定义图像数据布局,为一个字节数组
    BMP_DATA = (header.bfOffBits | uctypes.ARRAY, data_size | uctypes.UINT8)
    # 创建一个结构对象,使用小端序
    data = uctypes.struct(uctypes.addressof(buf), BMP_DATA, uctypes.LITTLE_ENDIAN)
    # 获取第一个像素的颜色值(RGB888 格式)
    pixel = data[0] + (data[1] << 8) + (data[2] << 16)
    # 打印第一个像素的颜色值(RGB888 格式)
    print("pixel:", hex(pixel))

案例3:链表操作:这是一个使用 uctypes 模块来创建和修改一个链表结构的程序案例。

# 导入 uctypes 和 gc 模块
import uctypes
import gc

# 定义链表节点布局,包含一个整数值和一个指针域
NODE = {
    "value": 0 | uctypes.INT32,
    "next": (4 | uctypes.PTR, None),
}

# 定义一个函数,用于创建一个链表节点,并返回其地址
def create_node(value, next=None):
    # 分配一块内存,大小为 NODE 的大小,并获取其地址
    addr = gc.mem_alloc(uctypes.sizeof(NODE))
    # 创建一个结构对象,使用 NODE 的布局和 NATIVE 的字节序
    node = uctypes.struct(addr, NODE, uctypes.NATIVE)
    # 设置节点的值和指针域
    node.value = value
    node.next = next
    # 返回节点的地址
    return addr

# 创建一个链表,包含三个节点,值分别为 1, 2, 3
head = create_node(1, create_node(2, create_node(3)))

# 定义一个函数,用于遍历一个链表,并打印每个节点的值和地址
def traverse_list(head):
    # 定义一个变量,用于存储当前节点的地址
    curr = head
    # 当当前节点不为空时,循环执行
    while curr:
        # 创建一个结构对象,使用 NODE 的布局和 NATIVE 的字节序
        node = uctypes.struct(curr, NODE, uctypes.NATIVE)
        # 打印当前节点的值和地址
        print("Value: {}, Address: {}".format(node.value, curr))
        # 将当前节点更新为下一个节点的地址
        curr = node.next

# 调用函数,遍历链表,并打印每个节点的值和地址
traverse_list(head)

# 定义一个函数,用于在链表的末尾插入一个新的节点,并返回新的头节点地址
def insert_at_end(head, value):
    # 创建一个新的节点,并获取其地址
    new_node = create_node(value)
    # 如果头节点为空,则直接返回新节点的地址作为头节点地址
    if not head:
        return new_node
    # 否则,定义一个变量,用于存储当前节点的地址
    curr = head
    # 当当前节点不为空时,循环执行
    while curr:
        # 创建一个结构对象,使用 NODE 的布局和 NATIVE 的字节序
        node = uctypes.struct(curr, NODE, uctypes.NATIVE)
        # 如果当前节点是最后一个节点(即没有下一个节点)
        if not node.next:
            # 将当前节点的指针域设置为新节点的地址
            node.next = new_node
            # 跳出循环
            break
        # 否则,将当前节点更新为下一个节点的地址
        curr = node.next
    # 返回原来的头节点地址
    return head

# 调用函数,在链表的末尾插入一个新的节点,值为 4,并返回新的头节点地址
head = insert_at_end(head, 4)

# 调用函数,遍历链表,并打印每个节点的值和地址(此时应该多了一个值为 4 的节点)
traverse_list(head)

案例4:解析二进制数据包:

import uctypes

# 定义数据包结构
packet_struct = {
    "header": uctypes.UINT8 | 0,
    "length": uctypes.UINT16 | 2,
    "data": uctypes.ARRAY(uctypes.UINT8, 8) | 4,
    "checksum": uctypes.UINT8 | 12,
}

# 二进制数据包
binary_data = b'\x01\x00\x08\x01\x02\x03\x04\x05\x06\x07\x08\xA5'

# 解析二进制数据包
packet = uctypes.struct(binary_data, packet_struct)

print("Header:", packet.header)
print("Length:", packet.length)
print("Data:", packet.data)
print("Checksum:", packet.checksum)

在这个例子中,我们先定义了一个数据包的结构,使用uctypes的类型标志符来定义每个字段的类型和偏移量。然后,我们有一个二进制数据包,使用uctypes的struct函数将二进制数据包解析为结构化数据。最后,我们可以使用结构化数据的字段进行后续操作。

案例5:访问硬件寄存器::

import uctypes
import machine

# 定义寄存器结构
register_struct = {
    "control": uctypes.UINT8 | 0,
    "status": uctypes.UINT8 | 1,
    "data": uctypes.UINT16 | 2,
}

# 访问硬件寄存器
register_address = 0x1234
register_data = bytearray(4)  # 寄存器数据为4个字节

i2c = machine.I2C(scl=machine.Pin(5), sda=machine.Pin(4))
i2c.readfrom_mem_into(0x18, register_address, register_data)

register = uctypes.struct(register_data, register_struct)

print("Control:", register.control)
print("Status:", register.status)
print("Data:", register.data)

在这个例子中,我们定义了一个寄存器的结构,使用uctypes的类型标志符来定义每个字段的类型和偏移量。然后,我们创建一个用于存储寄存器数据的字节数组,并使用I2C接口从设备的指定寄存器地址读取数据。最后,我们使用uctypes的struct函数将寄存器数据解析为结构化数据,并访问其中的字段。

案例6:操作二进制位字段:

import uctypes

# 定义位字段结构
bitfield_struct = {
    "flag1": uctypes.BFUINT8 | 0 | 2 << uctypes.BF_POS | 3 << uctypes.BF_LEN,
    "flag2": uctypes.BFUINT8 | 0 | 6 << uctypes.BF_POS | 2 << uctypes.BF_LEN,
}

# 位字段值
bitfield_value = 0b10101010

# 操作位字段
bitfield = uctypes.struct(bytearray([bitfield_value]), bitfield_struct)

print("Flag1:", bitfield.flag1)
print("Flag2:", bitfield.flag2)

在这个例子中,我们定义了一个包含位字段的结构,使用uctypes的类型标志符来定义每个位字段的类型、偏移量和长度。然后,我们有一个位字段的值,使用uctypes的struct函数将位字段值解析为结构化数据。最后,我们可以访问结构化数据的位字段。

案例7:解析二进制数据:使用uctypes模块解析二进制数据,将其转换为结构体或数组。

import uctypes

# 定义结构体描述符
struct_desc = {
    "x": uctypes.INT32,
    "y": uctypes.INT32,
    "z": uctypes.INT32
}

# 创建结构体实例
buffer = bytearray(b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00')
struct_instance = uctypes.struct(buffer, struct_desc)

# 访问结构体字段
print("x:", struct_instance.x)
print("y:", struct_instance.y)
print("z:", struct_instance.z)

案例8:操作指针:使用uctypes模块操作指针,读取或写入底层内存。:

import uctypes

# 创建指针
buffer = bytearray(4)
ptr = uctypes.addressof(buffer)

# 写入数据
uctypes.write(uctypes.UINT32, ptr, 0x12345678)

# 读取数据
data = uctypes.read(uctypes.UINT32, ptr)
print(hex(data))

案例9:操作数组:使用uctypes模块操作数组,读取或写入多个元素。:

import uctypes

# 创建数组
buffer = bytearray(8)
array = uctypes.array(uctypes.UINT32, buffer)

# 写入数据
array[0] = 0x12345678
array[1] = 0x87654321

# 读取数据
print(hex(array[0]))
print(hex(array[1]))

这些案例展示了MicroPython的uctypes模块的一些实际运用程序。通过使用uctypes模块,可以解析二进制数据、操作指针和操作数组等底层数据交互操作。请注意,在使用uctypes模块时,需要了解底层数据的结构和布局,并进行适当的类型转换和内存访问操作。

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

驴友花雕

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值