machine — 硬件控制
machine 用来访问开发板的引脚、通信总线和系统功能。本页按 OpenMV H7 Plus、固件 5.0.1(OPENMV4P) 整理,使用板上丝印 P0~P9,不套用其他 OpenMV 板卡的编号。
摄像头初始化与采集见 csi,图像处理见 image,模型推理见 ml。DAC 在这块板上使用 pyb.DAC,不是 machine.DAC。
引脚与资源
| 功能 | H7 Plus 的编号与引脚 |
|---|---|
| GPIO | P0~P9 |
| UART1 | TX:P1,RX:P0 |
| UART3 | TX:P4,RX:P5 |
| I2C2 | SCL:P4,SDA:P5 |
| I2C4 | SCL:P7,SDA:P8 |
| SPI2 | MOSI:P0,MISO:P1,SCK:P2,CS 可用 P3 手动控制 |
| CAN2 | TX:P2,RX:P3;需外接 CAN 收发器 |
| ADC / DAC | P6 |
| PWM / TIM2 | P4:CH3,P5:CH4,P6:CH1 |
| PWM / TIM4 | P7:CH1,P8:CH2 |
| RGB LED | LED_RED、LED_GREEN、LED_BLUE |
同一引脚不能同时分配给两个外设。例如使用 UART3 时,不再把 P4/P5 配置为 I2C2。PWM 的同一定时器通道共享频率;TIM1 被摄像头占用,P0/P1/P2 不作为本页 PWM 引脚。P7/P8 的 TIM4 也不能与 pyb.Servo 混用。
GPIO 输出为 3.3 V;P6 在 ADC/DAC 模式下只能接 0~3.3 V,不能接 5 V。与另一块控制板通信时需要共地。SYN 直接连到传感器,不是可用 machine.Pin 控制的 MCU 引脚。
下面签名中的 / 表示之前的参数只能按位置传入,* 表示之后的参数必须写名称;-> 是返回类型。签名用于查阅,实际用法见 Python 示例。
Pin — 数字输入与输出
machine.Pin(id: int | str, mode: int = -1, pull: int = -1, *, value: Any = None, drive: int = 0, alt: int = -1)
id 使用引脚名;mode 常用 Pin.IN、Pin.OUT、Pin.OPEN_DRAIN;pull 常用 Pin.PULL_UP、Pin.PULL_DOWN 或 Pin.PULL_NONE。输出时用 value 给出初始电平,避免先输出错误电平再修改。alt 用于复用功能;常规 UART、SPI、I2C 由对应外设类配置,不必手动填它。
from machine import Pin
out = Pin("P0", Pin.OUT, value=0)
button = Pin("P1", Pin.IN, Pin.PULL_UP)
out.value(button.value())
P1 默认由上拉保持高电平,外接按钮把 P1 接到 GND 时读到 0。示例只读一次,要连续检测时把读取语句放进主循环。
Pin.value()、on()、off()
Pin.value(x: Any = None, /) -> int | None
Pin.on() -> None
Pin.off() -> None
Pin.init(mode: int = -1, pull: int = -1, *, value: Any = None, drive: int = 0, alt: int = -1) -> None
value() 读取输入电平;value(0) / value(1) 设置输出低/高电平,设置时返回 None。on() / off() 分别输出高/低电平,不表示外部器件一定开启/关闭。init() 重新配置已有引脚,未指定的设置保持原样。STM32 的 Pin 不提供 toggle(),翻转输出可用 out.value(not out.value())。
Pin.irq()
Pin.irq(handler: Callable[[Pin], None] | None = None, trigger: int = (Pin.IRQ_FALLING | Pin.IRQ_RISING), *, priority: int = 1, wake: int | None = None, hard: bool = False) -> None
handler(pin) 接收触发中断的引脚对象;trigger 选 Pin.IRQ_RISING、Pin.IRQ_FALLING 或两者按位或。handler=None 关闭回调。priority、wake、hard 属于移植层相关选项,普通按钮检测保持默认即可;回调只记录标志,图像处理放回主循环。
from machine import Pin
pressed = False
def on_press(pin):
global pressed
pressed = True
button = Pin("P1", Pin.IN, Pin.PULL_UP)
button.irq(handler=on_press, trigger=Pin.IRQ_FALLING)
这段只登记按下事件,不含机械按键消抖。主循环读到 pressed 后处理,并在适当的临界区内清除它。
LED — 板载指示灯
machine.LED(pin_name: str | Pin) -> LED
LED.on() -> None
LED.off() -> None
LED.toggle() -> None
LED.value(v: int | None = None, /) -> int | None
RGB 指示灯使用逻辑开关:on() 点亮,off() 熄灭,toggle() 翻转,value() 读取 0/1,value(1) 点亮。LED 不提供亮度调节。
from machine import LED
red = LED("LED_RED")
red.on()
H7 Plus 的 LED_IR 与 RGB 灯的硬件有效电平不同,不能直接照搬上述 RGB 开关逻辑。需要红外补光时按 H7 Plus 官方 LED 专项说明 使用;这里不把它当作普通 RGB 指示灯。
UART — 串口
machine.UART(id: int, baudrate: int = 9600, bits: int = 8, parity: int | None = None, stop: int = 1, *, tx: Pin | None = None, rx: Pin | None = None, rts: Pin | None = None, cts: Pin | None = None, txbuf: int | None = None, rxbuf: int | None = None, timeout: int | None = None, timeout_char: int | None = None, invert: int = 0, flow: int = 0)
H7 Plus 常用 id=1 或 3,引脚见上表。两端 baudrate、数据位、校验和停止位必须一致;常用 115200、8 位、parity=None、1 位停止位。parity=0 为偶校验,1 为奇校验。timeout 是等待首字符的毫秒数,timeout_char 是字符间等待时间,rxbuf 为接收缓冲区大小。通用签名里的引脚重映射、反相与流控选项受移植层和实际引脚限制;本板按固定 UART 引脚使用。
UART.read()、write()
UART.any() -> int
UART.read(nbytes: int | None = None, /) -> bytes | None
UART.readinto(buf: bytearray, nbytes: int | None = None, /) -> int | None
UART.readline() -> bytes | None
UART.write(buf: bytes) -> int | None
UART.flush() -> None
UART.txdone() -> bool
UART.deinit() -> None
any() 判断是否有待读数据;其返回值不一定是缓冲区完整字节数。read() 返回 bytes,超时且无数据时为 None;readline() 读到换行或超时。readinto() 写入已有缓冲区并返回读到的字节数,超时无数据时为 None。write() 返回写入字节数,超时时可能为 None,因此协议发送应检查返回值。
flush() 等待发送完成,txdone() 查询是否完成,deinit() 关闭串口。串口没有消息边界,一次 read() 不一定对应一次对方的 write();用换行、长度或校验字段划分数据包。
from machine import UART
uart = UART(3, baudrate=115200, timeout=20)
uart.write(b"hello\n")
data = uart.read()
if data is not None:
print(data)
连接:H7 Plus 的 P4(TX)接对方 RX,P5(RX)接对方 TX,GND 接 GND。此示例只是收发用法,是否收到数据取决于对方设备。
I2C — 控制器
machine.I2C(id: int, *, scl: Pin | None = None, sda: Pin | None = None, freq: int = 400000, timeout: int = 50000)
I2C.scan() -> List[int]
id=2 对应 P4/P5,id=4 对应 P7/P8;freq 单位 Hz。SCL/SDA 需要上拉到合适的电压,常见模块已带上拉电阻。scan() 返回响应的 7 位设备地址列表,无设备时为 [],不要把读写位并入地址。
from machine import I2C
i2c = I2C(2, freq=400000)
print([hex(addr) for addr in i2c.scan()])
I2C 读写与寄存器访问
I2C.readfrom(addr: int, nbytes: int, stop: bool = True, /) -> bytes
I2C.readfrom_into(addr: int, buf: bytearray, stop: bool = True, /) -> None
I2C.writeto(addr: int, buf: bytes, stop: bool = True, /) -> int
I2C.writevto(addr: int, vector: tuple | list, stop: bool = True, /) -> int
I2C.readfrom_mem(addr: int, memaddr: int, nbytes: int, *, addrsize: int = 8) -> bytes
I2C.readfrom_mem_into(addr: int, memaddr: int, buf: bytearray, *, addrsize: int = 8) -> None
I2C.writeto_mem(addr: int, memaddr: int, buf: bytes, *, addrsize: int = 8) -> None
addr 为设备地址,nbytes 为字节数;readfrom() 返回新 bytes,readfrom_into() 填充已有缓冲区。writeto() / writevto() 返回得到应答的字节数;writevto() 可依次发送多个缓冲区。stop=False 不发送 STOP,用于需要重复起始条件的协议。
带 _mem 的方法先发送设备内部寄存器地址 memaddr,再读写数据;addrsize 是寄存器地址位数,默认 8,和设备的 7 位 I2C 地址无关。寄存器编号和读写内容必须按外接器件手册填写,不能从另一个传感器示例照搬。
SoftI2C — 软件总线
machine.SoftI2C(scl: Pin, sda: Pin, *, freq: int = 400000, timeout: int = 50000)
显式传入 scl=Pin(...)、sda=Pin(...),可在空闲 GPIO 上增加一条软件 I2C。常规 scan、读写和寄存器方法与上面相同;timeout 为等待时钟拉伸的微秒数。start()、stop()、底层 readinto() / write() 原语只属于 SoftI2C,不应当作硬件 I2C 通用方法使用。
I2CTarget — 让主控读取 OpenMV
machine.I2CTarget(id: int, addr: int, *, addrsize: int = 7, mem: bytearray | None = None, mem_addrsize: int = 8, scl: Pin | None = None, sda: Pin | None = None)
I2CTarget.deinit() -> None
当 STM32 等主控发起 I2C 读写、OpenMV 作为被访问的设备时使用。id 仍选 2 或 4,addr 是分配给 OpenMV 的地址;addrsize 默认 7。mem 是暴露给主控的共享字节缓冲区,mem_addrsize 是访问该缓冲区时的地址位数,默认 8。
from machine import I2CTarget
data = bytearray(32)
target = I2CTarget(2, addr=0x42, mem=data)
data[0] = 1
主控按 0x42 访问这个设备,从寄存器地址 0 开始读可得到 data 中的数据。视觉主循环可以更新它;多字节结果需要双方约定更新时机,避免读取一半新、一半旧的数据。deinit() 释放目标模式接口。无共享内存的中断模式见 官方类参考。
SPI — 同步串行总线
machine.SPI(id: int, baudrate: int = 1000000, *, polarity: int = 0, phase: int = 0, bits: int = 8, firstbit: int = MSB, sck: Pin | None = None, mosi: Pin | None = None, miso: Pin | None = None)
H7 Plus 用 SPI(2, ...)。baudrate 为 Hz;polarity 是空闲时钟电平,phase 选择第一个或第二个边沿采样,必须与设备一致。常用 bits=8、firstbit=SPI.MSB。SPI 不替用户控制 CS,需通过 Pin 拉低/拉高片选。
SPI.read()、write_readinto()
SPI.read(nbytes: int, write: int = 0x00) -> bytes
SPI.readinto(buf: bytearray, write: int = 0x00) -> None
SPI.write(buf: bytes) -> None
SPI.write_readinto(write_buf: bytes, read_buf: bytearray) -> None
SPI.deinit() -> None
read() 返回读到的 bytes,同时发送 write 指定的填充字节。readinto() 复用缓冲区;write() 只发送;write_readinto() 同时发送和接收,两个缓冲区长度必须一致。后三种传输方法返回 None。
from machine import SPI, Pin
spi = SPI(2, baudrate=1000000, polarity=0, phase=0)
cs = Pin("P3", Pin.OUT, value=1)
rx = bytearray(2)
cs.value(0)
try:
spi.write_readinto(b"\x00\x00", rx)
finally:
cs.value(1)
print(rx)
这是两字节交换框架,b"\x00\x00" 需换成器件协议要求的数据;没有接设备时读数不代表有效结果。finally 保证传输抛异常时仍释放片选。
SoftSPI — 软件总线
machine.SoftSPI(baudrate: int = 500000, *, polarity: int = 0, phase: int = 0, bits: int = 8, firstbit: int = MSB, sck: Pin | None = None, mosi: Pin | None = None, miso: Pin | None = None)
显式指定空闲的 sck、mosi、miso 引脚,方法与 SPI 的读写方法一致。软件总线占用 CPU,速率受解释器与 GPIO 操作限制,不保证达到请求的 baudrate。
PWM — 输出占空比
machine.PWM(dest: Pin | int, *, freq: int | None = None, duty_u16: int | None = None, duty_ns: int | None = None, invert: bool = False)
PWM.freq() -> int
PWM.freq(value: int, /) -> None
PWM.duty_u16() -> int
PWM.duty_u16(value: int, /) -> None
PWM.duty_ns() -> int
PWM.duty_ns(value: int, /) -> None
PWM.deinit() -> None
freq 单位 Hz;duty_u16 范围 0~65535,表示占空比;duty_ns 直接给脉宽,单位 ns。两种占空比参数选一种。相应方法无参数读取、带参数设置;deinit() 关闭输出。invert 控制输出极性,保持默认 False 即可。
from machine import Pin, PWM
pwm = PWM(Pin("P7"), freq=1000, duty_u16=32768)
1000 Hz 的周期是 1 ms,32768 约为 50%,所以高电平约 0.5 ms。修改 P7 的频率也会影响同属 TIM4 的 P8;不同占空比可以共存,不同频率不能共用这个定时器。
ADC — 读取 P6 模拟电压
machine.ADC(id: int | str | Pin) -> ADC
ADC.read_u16() -> int
外部模拟输入使用 ADC("P6")。read_u16() 返回缩放到 0~65535 的整数,不表示 ADC 原生分辨率就是 16 位。H7 Plus 不提供其他移植层的 read_uv()。
from machine import ADC
adc = ADC("P6")
raw = adc.read_u16()
voltage = raw * 3.3 / 65535
print(raw, voltage)
这里按参考电压 3.3 V 估算:32768 对应约 1.65 V,真实精度受供电、参考电压和输入电路影响。ADC.CORE_TEMP、CORE_VREF 等是芯片内部通道;CORE_VBAT 也不等于 H7 Plus 的外接 LiPo 电压检测,板上没有该电池检测电路。
CAN — 与控制器交换消息
machine.CAN(id: int, *args, **kwargs)
CAN.init(bitrate: int, mode: int = CAN.MODE_NORMAL, sample_point: int = 75, sjw: int = 1, tseg1: int | None = None, tseg2: int | None = None) -> None
CAN.set_filters(filters: list | tuple | None) -> None
CAN.send(id: int, data: bytes, flags: int = 0) -> int | None
CAN.recv(arg: list | None = None) -> list | None
本板控制器为 CAN(2, bitrate),P2/P3 连接外部收发器的 TXD/RXD,收发器再接 CAN 总线;不能把 P2/P3 直接当 CANH/CANL。machine.CAN 提供经典 CAN;需要 CAN FD 时另查 pyb.CAN。
bitrate 为 bit/s;默认正常模式,sample_point、sjw、tseg1、tseg2 是位时序设置,普通连接先保持默认并与对端波特率一致。set_filters(None) 接收全部消息,set_filters([]) 禁止接收;自定义过滤项为 (identifier, bit_mask, flags)。
send() 成功入发送队列返回槽位编号,队列满时可能返回 None,不等于消息已经送达。recv() 无数据返回 None,有数据时返回四元素列表 [id, data, flags, errors],其中 data 为 memoryview,errors 是接收错误标志而非时间戳;数据与标志含义见 官方 CAN 参考。
from machine import CAN
can = CAN(2, 500000)
can.set_filters(None)
slot = can.send(0x123, b"\x01\x02")
message = can.recv()
print(slot, message)
Timer — 软件定时器
machine.Timer(id: int = -1, /, *, mode: int = PERIODIC, period: int = -1, callback: Callable[[Timer], None] | None = None)
Timer.init(*, mode: int = PERIODIC, period: int = -1, callback: Callable[[Timer], None] | None = None) -> None
Timer.deinit() -> None
id 只使用 -1。period 单位毫秒,mode 为 Timer.PERIODIC(周期)或 Timer.ONE_SHOT(单次),callback(timer) 接收定时器对象。deinit() 停止定时器并取消待处理回调。
from machine import Timer, LED
blue = LED("LED_BLUE")
def tick(timer):
blue.toggle()
timer = Timer(-1)
timer.init(period=500, mode=Timer.PERIODIC, callback=tick)
每 500 ms 翻转一次,亮灭完整周期为 1 s。它用于调度短任务;硬件输入捕获、编码器等不属于这个类,不要把 STM32 的硬件定时器编号传入。
RTC — 日期与唤醒
machine.RTC(id: int = 0)
RTC.datetime(datetimetuple: tuple | None = None, /) -> tuple | None
RTC.wakeup(timeout_ms: int | None, callback: Callable[[RTC], None] | None = None, /) -> None
RTC() 取得同一个 RTC 对象。datetime() 读取八元组,传入八元组则设置;H7 Plus 的星期编号为周一 1~周日 7。
from machine import RTC
rtc = RTC()
rtc.datetime((2026, 11, 11, 3, 12, 0, 0, 0))
print(rtc.datetime())
八项依次是年、月、日、星期、时、分、秒、子秒。跨复位计时不等于完全断电后仍有备份供电。
wakeup(timeout_ms, callback) 配置周期唤醒中断,wakeup(None) 取消;回调在中断中执行。STM32 上的 RTC.init() 不传日期参数,设置日期用 datetime()。其他板卡文档中的 alarm()、alarm_left()、alarm_cancel() 不属于本页 H7 Plus 用法。
WDT — 看门狗
machine.WDT(id: int = 0, timeout: int = 5000)
WDT.feed() -> None
WDT.timeout_ms(timeout: int) -> None
构造时立即启动,timeout 单位毫秒;超时前没有 feed() 就复位。只有主循环完成必要工作后再喂狗,不能在独立定时器里无条件喂狗,否则主循环卡住也可能检测不到。
启动后不能停止。官方概述写“不能重新配置”,但同页为 STM32 列出了 timeout_ms(timeout),可修改超时时间并重装计数器;这里保留这个具体接口及差异,不推断存在停止方法。
系统函数
复位与设备信息
machine.reset() -> None
machine.soft_reset() -> None
machine.reset_cause() -> int
machine.bootloader(value: int | None = None, /) -> NoReturn
machine.unique_id() -> bytes
machine.freq(hz: int | None = None, /) -> int | tuple[int, ...]
reset() 硬复位;soft_reset() 重置解释器并清空 Python 对象。bootloader() 进入固件下载模式且不返回,value 在 OpenMV 上被忽略。unique_id() 在 STM32 返回 12 字节标识。
H7 Plus 的 freq() 返回 (SYSCLK, HCLK, PCLK1, PCLK2),单位 Hz;传入 hz 设置频率会抛 NotImplementedError。reset_cause() 与以下常量比较,不写死数值:
| 常量 | 复位原因 |
|---|---|
machine.PWRON_RESET | 上电 |
machine.HARD_RESET | 硬件复位 |
machine.WDT_RESET | 看门狗超时 |
machine.SOFT_RESET | 解释器软复位 |
machine.DEEPSLEEP_RESET | 从深度睡眠唤醒 |
中断控制
machine.disable_irq() -> int
machine.enable_irq(state: int) -> None
disable_irq() 返回之前的中断状态,把该值原样交给 enable_irq(state) 恢复。关中断区域只放少量必须连续完成的操作,不能在里面采图或推理。
休眠
machine.idle() -> None
machine.lightsleep(time_ms: int | None = None, /) -> None
machine.deepsleep(time_ms: int | None = None, /) -> NoReturn
idle() 等待中断时暂停 CPU;lightsleep() 返回后从原位置继续;deepsleep() 唤醒后重新启动,原 Python 状态不能保留。time_ms 是最长休眠毫秒数,配置的唤醒源可以提前唤醒;不传时间时需先准备外部唤醒条件。旧 sleep() 已弃用,新代码用 lightsleep()。
脉宽测量
machine.time_pulse_us(pin: Pin, pulse_level: int, timeout_us: int = 1000000, /) -> int
pin 先配置为输入,pulse_level=1 测高电平、0 测低电平,返回微秒数。等待脉冲开始超时返回 -2,脉冲已开始但等待结束超时返回 -1;这两种情况不是有效脉宽。