3、OpenMV基础编程:掌握MicroPython语法基础、GPIO、PWM、UART等常用外设的初始化与调用
OpenMV Cam 作为机器视觉核心模块,其底层运行 MicroPython 解释器,这意味着我们既可以利用 Python 的简洁语法快速开发视觉算法,又可以直接操作寄存器级别的硬件外设。在二维码识别项目中,摄像头采集图像、补光灯亮度调节、与主控(如 STM32、ESP32)通信,均依赖于 GPIO、PWM、UART 等外设的正确配置。本章将系统讲解 MicroPython 在 OpenMV 上的语法特性、外设初始化流程及调用方法,为后续二维码识别与串口输出打下坚实的工程基础。
上图展示了 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() |
utime | MicroPython 时间模块 | utime.ticks_diff() |
pyb 模块是板级外设的核心入口,而 machine 模块在部分固件版本中也可用。建议优先使用 pyb,因为其 API 更稳定且文档丰富。3.2 GPIO 编程:从初始化到中断响应
3.2.1 GPIO 初始化流程
GPIO(通用输入输出)是控制外部设备的基础。在 OpenMV 中,通过 pyb.Pin 对象操作。初始化步骤包括:
- 创建 Pin 对象:指定引脚编号(如
"P0")和模式(Pin.OUT或Pin.IN)。 - 设置上下拉电阻:输入模式时,可配置
Pin.PULL_UP或Pin.PULL_DOWN。 - 读写电平:使用
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, 1 | None 表示无校验 |
| 停止位 | 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 调试工具与方法
- 逻辑分析仪:抓取 UART 波形,验证波特率和数据帧。
- 示波器:测量 PWM 占空比和频率是否准确。
- OpenMV IDE 的串行终端:实时查看
print()输出,但注意与业务串口区分。 - 单元测试:单独测试每个外设,确保硬件正常后再集成。
pyb.Pin.disable_af() 或重新初始化,避免残留配置导致异常。例如切换 UART 引脚为 GPIO 时:# 释放 UART 引脚
uart.deinit()
# 重新配置为 GPIO
p4 = pyb.Pin("P4", pyb.Pin.OUT_PP)