3、OpenMV基础编程:掌握MicroPython语法基础、GPIO、PWM、UART等常用外设的初始化与调用

OpenMV Cam 作为机器视觉核心模块,其底层运行 MicroPython 解释器,这意味着我们既可以利用 Python 的简洁语法快速开发视觉算法,又可以直接操作寄存器级别的硬件外设。在二维码识别项目中,摄像头采集图像、补光灯亮度调节、与主控(如 STM32、ESP32)通信,均依赖于 GPIO、PWM、UART 等外设的正确配置。本章将系统讲解 MicroPython 在 OpenMV 上的语法特性、外设初始化流程及调用方法,为后续二维码识别与串口输出打下坚实的工程基础。

MicroPython 核心 外设驱动层 应用层(二维码项目) 语法基础:变量/循环/函数 缩进、列表、字典、异常 机器视觉库 sensor 初始化/帧率/窗口设置 图像处理 image find_barcodes/二维码定位 内置对象与常量 LED、时钟、延时函数 GPIO 通用输入输出 Pin 对象 / 上拉下拉 / 中断 PWM 脉宽调制 频率/占空比/补光灯控制 UART 串口通信 波特率/数据位/校验位 I2C / SPI 扩展 外接传感器/显示屏 二维码识别流程 ROI设置/解码/数据提取 串口数据帧封装 帧头/长度/校验/帧尾 设备控制指令 GPIO输出/PWM调节 异常处理与重试 看门狗/超时/日志 MicroPython 解释器 OpenMV 固件 HAL 层 用户业务逻辑

上图展示了 OpenMV 编程的三层架构:最底层是 MicroPython 解释器与固件提供的硬件抽象层(HAL),中间是 GPIO、PWM、UART 等外设驱动接口,最上层则是我们编写的二维码识别与设备控制逻辑。理解这一分层模型,有助于我们在编程时合理调用 API,避免跨层操作带来的稳定性问题。

3.1 MicroPython 语法核心与 OpenMV 编程模型

3.1.1 核心语法特性

MicroPython 是 Python 3 的精简实现,保留了绝大多数语法特性,但运行在资源受限的 MCU 上。在 OpenMV 中编程,必须注意以下几点:

  • 缩进即代码块:Python 使用缩进(通常为 4 个空格)表示代码层级,不能混用 Tab 与空格。
  • 动态类型:变量无需声明类型,但要注意内存占用。OpenMV 的 RAM 通常只有 256KB~1MB,应避免创建大列表或长字符串。
  • 内存管理:MicroPython 使用自动垃圾回收(GC),但频繁分配大对象会导致 GC 停顿,影响实时性。建议复用对象,避免在循环中创建新对象。
  • 异常处理:使用 try-except 捕获硬件错误(如 I2C 通信失败),防止程序崩溃。
# 示例:MicroPython 基础语法
import sensor, time

# 变量与列表
thresholds = [(0, 100, -20, 20, -20, 20)]  # 颜色阈值
count = 0

# 循环与条件
while True:
    img = sensor.snapshot()  # 获取图像
    for blob in img.find_blobs(thresholds):
        count += 1
        if count % 10 == 0:
            print("Found:", count)
    time.sleep_ms(50)

3.1.2 OpenMV 的模块导入机制

OpenMV 固件预装了大量模块,常用的包括:

模块名功能常用 API
sensor摄像头传感器控制sensor.reset(), sensor.set_pixformat(), sensor.set_framesize()
image图像处理算法img.find_barcodes(), img.draw_rectangle()
pyb板级外设控制pyb.Pin(), pyb.UART(), pyb.Timer()
time时间与延时time.sleep_ms(), time.ticks_ms()
utimeMicroPython 时间模块utime.ticks_diff()
经验提示:在 OpenMV 中,pyb 模块是板级外设的核心入口,而 machine 模块在部分固件版本中也可用。建议优先使用 pyb,因为其 API 更稳定且文档丰富。

3.2 GPIO 编程:从初始化到中断响应

3.2.1 GPIO 初始化流程

GPIO(通用输入输出)是控制外部设备的基础。在 OpenMV 中,通过 pyb.Pin 对象操作。初始化步骤包括:

  1. 创建 Pin 对象:指定引脚编号(如 "P0")和模式(Pin.OUT 或 Pin.IN)。
  2. 设置上下拉电阻:输入模式时,可配置 Pin.PULL_UP 或 Pin.PULL_DOWN。
  3. 读写电平:使用 pin.value() 读取或设置高低电平。
import pyb

# 初始化 LED 引脚(输出模式)
led_pin = pyb.Pin("P2", pyb.Pin.OUT_PP, pyb.Pin.PULL_NONE)
led_pin.value(1)  # 点亮 LED

# 初始化按键引脚(输入模式,上拉)
btn_pin = pyb.Pin("P3", pyb.Pin.IN, pyb.Pin.PULL_UP)
if btn_pin.value() == 0:
    print("Button pressed!")

3.2.2 外部中断与回调

轮询方式会浪费 CPU 资源,OpenMV 支持外部中断(EXTI)。使用 pyb.ExtInt 类:

import pyb

def btn_callback(line):
    print("Interrupt on line:", line)

# 配置 P3 引脚下降沿触发中断
ext = pyb.ExtInt(pyb.Pin("P3"), pyb.ExtInt.IRQ_FALLING, pyb.Pin.PULL_UP, btn_callback)
风险提示:中断回调函数中不要执行耗时操作(如图像处理、串口打印大量数据),否则会阻塞主循环。建议在回调中仅设置标志位,主循环中处理具体逻辑。

3.3 PWM 输出:精确控制补光灯与电机

3.3.1 PWM 初始化与参数配置

PWM(脉宽调制)通过调节占空比控制输出功率。在 OpenMV 中,使用 pyb.Timer 和 pyb.Pin 配合生成 PWM 信号:

import pyb

# 创建定时器 4,频率 1kHz
tim = pyb.Timer(4, freq=1000)
# 将 P7 引脚绑定到定时器通道 1
ch = tim.channel(1, pyb.Timer.PWM, pin=pyb.Pin("P7"))
# 设置占空比 50%
ch.pulse_width_percent(50)

关键参数说明:

  • 频率(freq):PWM 波形频率,补光灯通常 1kHz~10kHz,电机驱动 10kHz~20kHz。
  • 占空比(pulse_width_percent):0~100,对应输出平均电压。
  • 分辨率:OpenMV 的定时器为 16 位,可设置 pulse_width(微秒级)实现更高精度。

3.3.2 补光灯亮度调节实战

二维码识别时,环境光线不足会导致解码失败。通过 PWM 控制板载补光灯:

import pyb, sensor

# 初始化补光灯引脚(通常为 P2 或 P8)
light_tim = pyb.Timer(2, freq=5000)
light_ch = light_tim.channel(1, pyb.Timer.PWM, pin=pyb.Pin("P2"))

def set_brightness(percent):
    """设置补光灯亮度 0~100%"""
    light_ch.pulse_width_percent(max(0, min(100, percent)))

# 根据环境亮度自动调节
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.skip_frames(10)

while True:
    img = sensor.snapshot()
    avg_brightness = img.get_statistics().mean()
    if avg_brightness < 80:
        set_brightness(80)  # 光线暗,调亮
    elif avg_brightness > 150:
        set_brightness(30)  # 光线强,调暗
    else:
        set_brightness(50)

3.4 UART 串口通信:数据发送与接收

3.4.1 UART 初始化参数

UART 是 OpenMV 与外部主控通信最常用的接口。初始化时需指定:

参数可选值说明
波特率9600, 115200, 460800通信速率,需与对端一致
数据位8(默认)通常为 8 位
校验位None, 0, 1None 表示无校验
停止位1, 2通常为 1 位
import pyb

# 初始化 UART3(TX=P4, RX=P5),波特率 115200
uart = pyb.UART(3, 115200, timeout_char=1000)
uart.init(115200, bits=8, parity=None, stop=1)

# 发送数据
uart.write("Hello OpenMV\r\n")

# 接收数据(非阻塞)
if uart.any():
    data = uart.read()
    print("Received:", data)

3.4.2 数据帧协议设计

在二维码识别项目中,需要将识别结果发送给主控。为避免数据粘包,设计简单的帧协议:

import pyb, ustruct

# 帧格式:0xAA 0x55 [长度] [数据...] [CRC8]
def send_qr_data(qr_string):
    payload = qr_string.encode('utf-8')
    length = len(payload)
    crc = 0
    for b in payload:
        crc ^= b
    frame = b'\xAA\x55' + bytes([length]) + payload + bytes([crc])
    uart.write(frame)

# 接收并解析(假设主控发送指令)
def parse_command():
    if uart.any() >= 4:
        header = uart.read(2)
        if header == b'\xAA\x55':
            length = uart.read(1)[0]
            data = uart.read(length)
            crc = uart.read(1)[0]
            # 校验 CRC...
            return data
    return None
工程要点:串口通信必须考虑超时与错误恢复。建议在发送前检查 uart.write 返回值,接收时使用 timeout_char 参数避免阻塞。

3.5 综合案例:二维码识别与设备联动控制

3.5.1 项目需求与硬件连接

假设我们需要实现:OpenMV 识别二维码后,根据内容控制外部设备(如继电器、LED 灯带)。硬件连接如下:

  • OpenMV P0 → 继电器控制引脚(GPIO 输出)
  • OpenMV P2 → LED 灯带 PWM 控制
  • OpenMV UART3 (P4/TX, P5/RX) → 主控板串口

3.5.2 完整代码实现

import sensor, image, time, pyb

# 初始化摄像头
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QQVGA)  # 160x120,提高帧率
sensor.skip_frames(10)
sensor.set_auto_gain(False)  # 关闭自动增益,保证亮度稳定

# 初始化外设
relay_pin = pyb.Pin("P0", pyb.Pin.OUT_PP)
light_tim = pyb.Timer(2, freq=5000)
light_ch = light_tim.channel(1, pyb.Timer.PWM, pin=pyb.Pin("P2"))
uart = pyb.UART(3, 115200, timeout_char=100)

# 状态变量
current_state = "IDLE"
last_detect_time = time.ticks_ms()

def set_light(brightness):
    light_ch.pulse_width_percent(brightness)

def send_status(state, qr_data=""):
    msg = "{}:{}".format(state, qr_data)
    uart.write(msg + "\r\n")

while True:
    img = sensor.snapshot()
    codes = img.find_barcodes()  # 二维码也是条形码的一种

    if codes:
        for code in codes:
            if code.type() == "QRCODE":
                qr_info = code.payload()
                print("QR:", qr_info)
                send_status("DETECTED", qr_info)

                # 根据内容控制设备
                if qr_info == "ON":
                    relay_pin.value(1)
                    set_light(100)
                    current_state = "ON"
                elif qr_info == "OFF":
                    relay_pin.value(0)
                    set_light(0)
                    current_state = "OFF"
                elif qr_info.startswith("BRIGHT:"):
                    # 格式:BRIGHT:80
                    try:
                        val = int(qr_info.split(":")[1])
                        set_light(val)
                        current_state = "BRIGHT:{}".format(val)
                    except:
                        send_status("ERROR", "Invalid brightness")
                last_detect_time = time.ticks_ms()
    else:
        # 超时未检测到,进入待机
        if time.ticks_diff(time.ticks_ms(), last_detect_time) > 3000:
            if current_state != "IDLE":
                send_status("IDLE", "")
                current_state = "IDLE"
                relay_pin.value(0)
                set_light(20)  # 低亮度待机

    time.sleep_ms(50)

3.5.3 案例复盘与优化建议

问题现象可能原因解决方案
二维码识别率低图像模糊、反光、分辨率不足开启 sensor.set_auto_whitebal(),调整 ROI 区域,使用 img.lens_corr() 矫正畸变
串口数据乱码波特率不匹配、电平不兼容确认双方波特率一致,检查 TX/RX 是否交叉连接
PWM 控制不线性LED 驱动电路非线性使用对数曲线映射:actual = int(100 * (percent/100)**2)
系统卡死内存泄漏或死循环使用 gc.collect() 手动回收,增加看门狗 pyb.wdt()

3.6 常见误区与调试技巧

3.6.1 典型错误分析

  • 误区一:直接使用 print() 调试外设。print 会占用 UART 或 USB 虚拟串口,干扰正常通信。应使用 uart.write() 输出调试信息。
  • 误区二:忽略引脚复用功能。部分引脚(如 P4/P5)默认用于 UART,若需用作 GPIO 必须先释放外设功能。
  • 误区三:PWM 频率设置过高。超过 20kHz 可能导致 MOS 管驱动不足,产生热量。

3.6.2 调试工具与方法

  1. 逻辑分析仪:抓取 UART 波形,验证波特率和数据帧。
  2. 示波器:测量 PWM 占空比和频率是否准确。
  3. OpenMV IDE 的串行终端:实时查看 print() 输出,但注意与业务串口区分。
  4. 单元测试:单独测试每个外设,确保硬件正常后再集成。
重要提醒:在修改外设配置前,务必调用 pyb.Pin.disable_af() 或重新初始化,避免残留配置导致异常。例如切换 UART 引脚为 GPIO 时:
# 释放 UART 引脚
uart.deinit()
# 重新配置为 GPIO
p4 = pyb.Pin("P4", pyb.Pin.OUT_PP)