OpenMV AE3

OpenMV AE3 ถูกสร้างขึ้นบน Alif Ensemble E3 ซึ่งเป็น SoC แบบ dual ARM Cortex‑M55 (คอร์ HP 400 MHz + คอร์ HE 160 MHz) พร้อม NPU ออนชิปสองตัว (HP NPU 400 MHz / 204 GOPS + HE NPU 160 MHz / 46 GOPS) บอร์ดนี้จับคู่ NPU กับ sensor ชัตเตอร์แบบ global PAG7936 ขนาด 1 MP, USB‑C ความเร็วสูง, Wi‑Fi, Bluetooth 5.1, IMU แบบ LSM6DSM, ไมโครโฟน และ rangefinder แบบ time-of-flight ขนาด 8×8 VL53L8CX ทั้งหมดบนบอร์ดขนาด 30 × 30 มม.

OpenMV AE3

สำหรับ datasheet ฉบับเต็ม รูปถ่าย และขนาด ดูได้ที่ หน้าผลิตภัณฑ์ OpenMV AE3

จุดเด่น

  • Alif Ensemble E3 — dual ARM Cortex‑M55 พร้อม Helium 128‑bit SIMD, คอร์ HP 400 MHz + คอร์ HE 160 MHz (~640 / ~256 DMIPS, CoreMark 1748 / 752)

  • Dual NPUs: HP NPU 400 MHz / 204 GOPS + HE NPU 160 MHz / 46 GOPS สำหรับ AI/ML — รัน YOLO object detection ควบคู่กับงานอื่นๆ

  • 2D GPU แบบฮาร์ดแวร์สำหรับการปรับขนาด

  • SRAM ภายใน 13.5 MB พร้อม MRAM ออนชิป 5.5 MB และ octal flash ภายนอก 32 MB (DDR 8‑bit 100 MHz, อ่านได้ 200 MB/s)

  • backup RAM 4 KB พร้อม RTC ออนชิป

  • PAG7936 sensor สีแบบ global-shutter ขนาด 1 MP

  • IMU ออนบอร์ด (LSM6DSM accelerometer + gyroscope), ไมโครโฟน และ sensor แบบ time-of-flight 8×8 VL53L8CX (สูงสุด 4 ม.)

  • USB‑C ความเร็วสูง (480 Mb/s) พร้อมการกรอง EMI และการป้องกัน TVS, Wi‑Fi a/b/g/n + Bluetooth 5.1 (เสาอากาศแบบชิปหรือตัวเลือก U.FL)

  • พิน I/O ผู้ใช้ 10 พิน — P0–P3 บน side headers, P4–P5 บนตัวเชื่อมต่อ Qwiic และ P6–P9 บน B2B header ที่ด้านหลัง นอกจากนี้ยังมีการเดินสายดีบักและ recovery เพิ่มเติมไปยัง B2B header ด้วย

  • พินทั้งหมดรองรับเอาต์พุต 3.3 V / ทนต่อ 3.3 V, 25 mA ต่อพิน, รองรับอินเทอร์รัปต์ อินพุต ADC อ้างอิงที่ 1.8 V

  • LED RGB ของผู้ใช้, ปุ่มผู้ใช้, สวิตช์ recovery, ตัวเชื่อมต่อ Qwiic

  • deep sleep 80 µA ที่ 3.3 V (idle 24 mA, active 50–60 mA)

Warning

พิน I/O ของ AE3 ไม่ทนต่อ 5 V อย่าเชื่อมต่ออุปกรณ์โดยตรงกับ MCU 5 V เช่น Arduino Mega — ให้ใช้ level shifter สำหรับสัญญาณ 5 V ใดๆ

แผนผังพิน

OpenMV AE3 PAG7936 Pinout

อ้างอิงพิน

AE3 เปิดเผยพินผู้ใช้ 10 พินบน side headers (P0–P9) สัญญาณเพิ่มเติม รวมถึง JTAG และสาย recovery ถูกเดินสายไปยัง B2B (board‑to‑board) header ที่ด้านหลัง ของบอร์ดสำหรับ shields และ carrier boards

ชื่อพิน

อ้างอิง

ฟังก์ชัน

P0

3.3 V

SPI0 MOSI / I2C2 SCL / UART4 TX / TIM0 T1 / PDM D3

P1

3.3 V

SPI0 MISO / I2C2 SDA / UART4 RX / TIM0 T0

P2

3.3 V

SPI0 SCLK / LPI2C SDA / UART5 TX / TIM1 T1

P3

3.3 V

SPI0 SS / LPI2C SCL / UART5 RX / TIM1 T0 / PDM C3

P4

3.3 V

I2C1 SCL / UART1 TX / TIM2 T1 / PDM C0 / CAN TX

P5

3.3 V

I2C1 SDA / UART1 RX / TIM2 T0 / PDM D0 / CAN RX

P6

1.8 V

I2C1 SDA / UART3 CTS / TIM9 T0 (B2B only)

P7

1.8 V

I2C1 SCL / UART3 RTS / TIM9 T1 (B2B only)

P8

1.8 V

I3C SDA / UART3 RX / TIM5 T0 / ADC ch S10 (B2B only)

P9

1.8 V

I3C SCL / UART3 TX / TIM5 T1 / ADC ch S11 (B2B only)

P10

1.8 V

GPIO / JTAG TCK (B2B only)

P11

1.8 V

GPIO / JTAG TDO (B2B only)

P13

1.8 V

GPIO / JTAG TMS (B2B only)

P14

1.8 V

GPIO / JTAG TDI (B2B only)

RESET

3.3 V

ดึงลง GND เพื่อรีเซ็ตบอร์ด

SW

3.3 V

ปุ่มผู้ใช้ (active low)

LED_RED

3.3 V

ช่องสีแดงของ RGB LED (active low)

LED_GREEN

3.3 V

ช่องสีเขียวของ RGB LED (active low)

LED_BLUE

3.3 V

ช่องสีน้ำเงินของ RGB LED (active low)

Note

P0–P5 อยู่บน side headers (อ้างอิง 3.3 V); P6–P9 เปิดเผยเฉพาะบน B2B header ที่ด้านหลังของบอร์ดและอ้างอิง 1.8 V การจ่ายแรงดัน 3.3 V เข้าสู่พินที่อ้างอิง 1.8 V จะทำให้ SoC เสียหาย — ตรวจสอบให้แน่ใจว่าสัญญาณที่เชื่อมต่อกับ B2B header อยู่ที่ 1.8 V

พินจ่ายไฟ

  • 3.3V — ราง power หลักของ AE3 ราง 3.3 V เดียวกันนี้ถูกเปิดเผยบน GPIO header solder pads, ตัวเชื่อมต่อ Qwiic และ B2B header ที่ด้านหลังของบอร์ด

  • 1.8V — เปิดเผยบน B2B header เป็น เอาต์พุตเท่านั้น ใช้เพื่อจ่ายไฟให้กับอุปกรณ์ต่อพ่วงที่ใช้ลอจิก 1.8 V บน B2B carrier; อย่าจ่ายไฟจากภายนอกบอร์ด

  • GND — กราวด์ร่วม

AE3 ไม่มีพิน VIN และไม่มีวงจรชาร์จ LiPo สามารถจ่ายไฟผ่านสามทางใดก็ได้:

  • USB‑C — ตัวควบคุมออนบอร์ดลดแรงดัน 5 V จาก USB เป็น 3.3 V และต่อเข้ากับราง 3.3 V

  • ตัวเชื่อมต่อ Qwiic — จ่ายแรงดัน 3.3 V ที่ควบคุมแล้วเข้าสู่ Qwiic header เพื่อจ่ายไฟให้บอร์ดจากโมดูล Qwiic

  • GPIO header / B2B 3.3 V pads — จ่ายแรงดัน 3.3 V ที่ควบคุมแล้วเข้าสู่ 3.3 V pads ใดๆ บน I/O header หรือตัวเชื่อมต่อ B2B

ตัวควบคุม USB ป้อนราง power ผ่าน ideal diode ดังนั้นแหล่งจ่ายไฟ 3.3 V ภายนอกที่ด้าน Qwiic / GPIO / B2B สามารถจ่ายไฟให้บอร์ดได้แม้ว่า USB ยังคงต่ออยู่ โดยไม่ต้อง back-drive ตัวควบคุม USB

Tip

ใช้ ตัวประมาณอายุแบตเตอรี่ เพื่อจำลองว่า AE3 จะทำงานนานเท่าใดบนแบตเตอรี่สำหรับ duty cycle active / deep-sleep ที่กำหนด

Recovery และพินดีบัก

  • RESET — ดึงลง GND เพื่อรีเซ็ตบอร์ด การปล่อยจะให้ SoC เริ่มต้นทำงานตามปกติ

มี recovery switch บน หน้า (ด้าน camera) ของบอร์ด ที่มุมล่างซ้าย เมื่อเปิดใช้งาน จะบังคับให้ SE UART ของ AE3 ส่งออกผ่าน USB เพื่อให้ OpenMV IDE สามารถ reflash bootloader ออนบอร์ดได้ recovery mode เดียวกันนี้สามารถเปิดใช้งานจากระยะไกลได้โดยการดึงพิน RECOVERY บนตัวเชื่อมต่อ B2B ลงต่ำ

AE3 รองรับทั้ง SWD และ JTAG แบบเต็มรูปแบบในการดีบัก:

  • 1.8 V SWD header ที่ด้านข้างของบอร์ดสำหรับสาย Tag-Connect ECV3-06-CTX และเปิดเผยสัญญาณ SWD สี่ตัว (TCK / TMS / TDO / RSTN) พร้อม GND

  • B2B header ที่ด้านหลังของบอร์ดเปิดเผยพินดีบักเดียวกัน (P10 = TCK, P11 = TDO, P13 = TMS, P14 = TDI) พร้อม RSTN ของระบบและ JTAG RSTN แยกต่างหาก พินเหล่านี้สามารถใช้สำหรับ SWD (TCK + TMS) หรือ JTAG แบบเต็มรูปแบบ ไลน์ JTAG RSTN จำเป็นเฉพาะในโหมด full-JTAG เท่านั้น

สัญญาณดีบักทั้งหมด อ้างอิง 1.8 V — ตรวจสอบให้แน่ใจว่า debug adapter ของคุณได้รับการกำหนดค่าสำหรับลอจิก 1.8 V ก่อนเชื่อมต่อ

อุปกรณ์ต่อพ่วงออนบอร์ด

LED

AE3 มี RGB LED ผู้ใช้หนึ่งดวง ควบคุมด้วยซอฟต์แวร์ผ่าน machine.LED

from machine import LED

LED("LED_RED").on()
LED("LED_GREEN").on()
LED("LED_BLUE").on()

ปุ่มผู้ใช้

AE3 มีปุ่มผู้ใช้หนึ่งปุ่ม (SW):

from machine import Pin

sw = Pin("SW", Pin.IN)
print(sw.value())

เพื่อให้บอร์ดเข้าสู่ deep sleep และให้ SW ปลุกมันกลับมา เพียงเรียก machine.deepsleep() — ไม่จำเป็นต้องกำหนดค่า wakeup เนื่องจากปุ่มถูกต่อตรงกับ wake input:

import machine

machine.deepsleep()   # press SW to wake the board

คุณยังสามารถต่อ SW เป็น soft power switch ได้ ทริกเกอร์บนขอบ rising — ไลน์จะสงบที่ระดับสูงหลังจากผู้ใช้ปล่อยปุ่ม ดังนั้นการกดครั้งถัดไปจึงเป็น wake event ที่ไม่คลุมเครือ:

import machine
from machine import Pin

def power_off(_):
    machine.deepsleep()

Pin("SW", Pin.IN).irq(power_off, Pin.IRQ_RISING)

# ...rest of the application runs here. Press SW once to sleep,
# press it again to wake.

Camera sensor

PAG7936 ถูกขับเคลื่อนผ่านโมดูล csi --- เซ็นเซอร์กล้อง

import csi

cam = csi.CSI()
cam.reset()
cam.pixformat(csi.RGB565)
cam.framesize(csi.HD)         # 1280×800
cam.snapshot(time=2000)       # let auto‑exposure settle

while True:
    img = cam.snapshot()

PAG7936 รองรับ triggered mode — การรวมพิกเซลสอดคล้องกับการเรียก csi.CSI.snapshot แต่ละครั้งอย่างแม่นยำแทนที่จะเป็น free-running frame clock ซึ่งมีประโยชน์สำหรับการซิงค์การจับภาพกับ event ภายนอกหรือ sensor อื่น เปิดใช้งานผ่าน csi.CSI.ioctl ด้วย csi.IOCTL_SET_TRIGGERED_MODE อัตราเฟรมจะลดลงเหลือประมาณครึ่งหนึ่งของโหมด free-running เนื่องจากการอ่านไม่ pipeline กับการรวมเฟรมถัดไปอีกต่อไป:

cam.ioctl(csi.IOCTL_SET_TRIGGERED_MODE, True)

NPU

NPU ออนชิปสองตัวของ AE3 (HP NPU 400 MHz / 204 GOPS + HE NPU 160 MHz / 46 GOPS) ถูกเปิดเผยผ่านโมดูล ml --- Machine Learning โมเดลที่จัดเก็บบน filesystem แบบ read-only /rom โหลดโดยตรงจาก flash โดยไม่ต้องคัดลอกไปยัง RAM ดังนั้นแม้แต่ detectors ขนาดใหญ่ก็สามารถใส่ไว้ร่วมกับ framebuffer สดได้อย่างสบาย รัน YOLOv8 detector บนทุกเฟรมและวาดการทำนายทับบนภาพสด:

import csi
import time
import ml
from ml.postprocessing.ultralytics import YoloV8

# Initialize the sensor.
csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.VGA)

# Load YOLO V8 model from ROM FS.
model = ml.Model("/rom/yolov8n_192.tflite", postprocess=YoloV8(threshold=0.4))
print(model)

# Visualization parameters.
n = len(model.labels)
model_class_colors = [
    (int(255 * i // n), int(255 * (n - i - 1) // n), 255)
    for i in range(n)
]

clock = time.clock()
while True:
    clock.tick()
    img = csi0.snapshot()

    # boxes is a list of list per class of ((x, y, w, h), score) tuples
    boxes = model.predict([img])

    # Draw bounding boxes around the detected objects
    for i, class_detections in enumerate(boxes):
        rects = [r for r, score in class_detections]
        labels = [model.labels[i] for j in range(len(rects))]
        colors = [model_class_colors[i] for j in range(len(rects))]
        ml.utils.draw_predictions(img, rects, labels, colors, format=None)

    print(clock.fps(), "fps")

HE core

AE3 บรรจุคอร์ Cortex‑M55 สองคอร์ไว้ใน MCU เดียว: คอร์ high-performance (HP) ที่รัน MicroPython instance หลัก, camera, HP NPU, USB และอื่นๆ; และ คอร์ high-efficiency (HE) ที่ทำงานที่พลังงานต่ำกว่ามากและบูตเข้าสู่ MicroPython instance เล็กๆ ของตัวเอง ทั้งสองคอร์ใช้ Open-AMP / RPMsg message bus ร่วมกัน ดังนั้น HP core สามารถส่ง Python functions ไปยัง HE core รับผลลัพธ์กลับมา และทำให้ทั้งสองส่วนแยกออกจากกัน

จุดเข้าที่ง่ายที่สุดคือ decorator @openamp.async_remote มันจัดส่ง Python function ไปยัง HE core และ HE core รันมันเป็น asyncio task หลังจากลงทะเบียน tasks แล้ว ให้สร้าง openamp.RemoteProc ด้วยที่อยู่ flash ของ HE firmware และเรียก rproc.start() เพื่อบูต second core เมื่อไม่มี callback เอาต์พุต print() ของ function ที่ decorated จะถูกส่งต่อผ่าน default endpoint ไปยัง stdout ของ HP core — มีประโยชน์สำหรับ "hello world":

import time
import openamp

@openamp.async_remote
async def task1(ept):
    import asyncio
    while True:
        print("Hello from the HE core!")
        await asyncio.sleep(1)

# Boot the HE core. This runs the registered tasks.
rproc = openamp.RemoteProc(0x80320000)
rproc.start()

while True:
    print("Hello from the HP core!")
    time.sleep(1)

สำหรับ bidirectional messaging ให้ส่ง callback ไปยัง decorator callback จะทำงานบน HP core เมื่อใดก็ตามที่ HE task เรียก ept.send()

import time
import openamp

def task_callback(src_addr, data):
    print("HP received:", data.decode())

@openamp.async_remote(task_callback)
async def task1(ept):
    import asyncio
    count = 0
    while True:
        ept.send(f"count = {count}")
        count += 1
        await asyncio.sleep(1)

rproc = openamp.RemoteProc(0x80320000)
rproc.start()

while True:
    time.sleep(1)

HE core มี HE NPU ของตัวเอง (160 MHz, 46 GOPS) ดังนั้นมันสามารถรัน ML model ที่สองได้พร้อมกันกับสิ่งที่ HP NPU ของ HP core กำลังยุ่งอยู่ การแยกที่มีประโยชน์คือการวาง trigger / classifier model ที่เปิดตลอดเวลาขนาดเล็กไว้ที่ฝั่ง HE และให้ HP core ตอบสนองเฉพาะเมื่อมีบางอย่างน่าสนใจถูกตั้งค่าสถานะ — keyword spotting จากไมโครโฟนออนบอร์ดเหมาะสมดีเนื่องจากมันต่อเนื่อง, bandwidth ต่ำ และ HE core ใช้พลังงานต่ำกว่า HP มาก helper ml.apps.MicroSpeech ที่ถูก frozen รู้จัก "Yes" และ "No" ออกจากกล่อง — พูดคำเหล่านั้นดังๆ และชัดเจนเข้าไมค์ออนบอร์ดเพื่อทริกเกอร์การตรวจจับ:

import time
import openamp

def task_callback(src_addr, data):
    print("Heard:", data.decode())

@openamp.async_remote(task_callback)
async def task1(ept):
    from ml.apps import MicroSpeech
    speech = MicroSpeech(gain_db=24)
    while True:
        label, scores = speech.listen(timeout=0, threshold=0.70)
        if label:
            ept.send(label)

rproc = openamp.RemoteProc(0x80320000)
rproc.start()

while True:
    time.sleep(1)

สำหรับการแยกที่สมบูรณ์ยิ่งขึ้น ให้รัน BlazeFace บน HP NPU ในขณะที่ HE core จัดการ keyword spotting ในเบื้องหลัง — HP loop ซ้อนทับ keyword ล่าสุดที่ได้ยินบนเฟรม camera:

import csi
import time
import openamp
import ml
from ml.postprocessing.mediapipe import BlazeFace

label = None
label_ticks = 0
LABEL_HOLD_MS = 2000

def task_callback(src_addr, data):
    global label, label_ticks
    label = data.decode()
    label_ticks = time.ticks_ms()

@openamp.async_remote(task_callback)
async def task1(ept):
    from ml.apps import MicroSpeech
    speech = MicroSpeech(gain_db=24)
    while True:
        l, scores = speech.listen(timeout=0, threshold=0.70)
        if l:
            ept.send(l)

# Start the HE core before initializing the camera on the HP core.
rproc = openamp.RemoteProc(0x80320000)
rproc.start()

csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.VGA)
csi0.window((400, 400))

model = ml.Model("/rom/blazeface_front_128.tflite",
                 postprocess=BlazeFace(threshold=0.4))

clock = time.clock()
while True:
    clock.tick()
    img = csi0.snapshot()
    for r, score, keypoints in model.predict([img]):
        ml.utils.draw_predictions(img, [r], ("face",),
                                  ((0, 0, 255),), format=None)
        ml.utils.draw_keypoints(img, keypoints, color=(255, 0, 0))
    if label is not None:
        if time.ticks_diff(time.ticks_ms(), label_ticks) < LABEL_HOLD_MS:
            img.draw_string((4, 4), f"Heard: {label}",
                            color=(255, 0, 0), scale=2)
        else:
            label = None
    print(clock.fps(), "fps")

HE core เหมาะกับงานแบบ always-on หรือ low-rate ที่คุณไม่ต้องการให้แข่งขันกับ camera/NPU pipeline ที่ฝั่ง HP — ML inference ขนาดเล็ก, DSP น้ำหนักเบาบนข้อมูลไมโครโฟนหรือ IMU และงานพื้นหลังที่คล้ายกัน

ข้อจำกัดบางประการที่ควรคำนึงถึง:

  • ใช้ไมโครโฟนและ IMU เมื่อขับเคลื่อนอุปกรณ์ต่อพ่วงจาก HE core — นั่นคือสิ่งที่ฝั่ง HE ถูกออกแบบมาเพื่อ แต่ละอุปกรณ์ต่อพ่วงสามารถเป็นเจ้าของได้โดยคอร์เดียวในแต่ละครั้ง ดังนั้นเลือก HP หรือ HE สำหรับมันและยึดติดกับนั้นตลอดอายุของสคริปต์

  • แต่ละ body ของ task @openamp.async_remote ต้องจัดส่งได้ขนาดไม่เกิน 500 bytes ของ mpy bytecode — ทำให้ function เล็กและแยก logic ที่หนักกว่าออกเป็น library modules แยกต่างหากที่ถูก frozen เข้าไปใน firmware

  • Imports ภายใน dispatched function เห็นเฉพาะโมดูลที่มีอยู่บน filesystem ของ HE core HE core มี ROMFS /rom ของตัวเอง — แยกจาก /rom ของ HP core — ดังนั้นโมดูลและ ML models ที่คุณต้องการให้ใช้งานได้บน HE จำเป็นต้องถูกอบเข้าไปใน HE-side ROMFS image ไม่ใช่ HP

ไมโครโฟน

ไมโครโฟนออนบอร์ดถูกจับภาพผ่าน audio --- โมดูล Audio แต่ละบัฟเฟอร์มาถึงเป็น PCM bytearray แบบ signed‑16‑bit ซึ่งทำให้ง่ายต่อการป้อนเข้าสู่ ulab/numpy สำหรับ DSP เร็วๆ detector ความดัง — พิมพ์เมื่อปริมาณ RMS ข้ามค่าขีดแบ่ง:

import audio
from ulab import numpy as np

def loudness(pcmbuf):
    samples = np.array(np.frombuffer(pcmbuf, dtype=np.int16), dtype=np.float)
    rms = np.sqrt(np.mean(samples ** 2))
    if rms > 10000:
        print("Loud!", int(rms))

audio.init(channels=1, frequency=16000, gain_db=24)
audio.start_streaming(loudness)

while True:
    pass

IMU

LSM6DSM accelerometer + gyroscope ออนบอร์ดถูกเปิดเผยผ่าน imu --- imu sensor

import imu
import time

while True:
    print(imu.acceleration_mg())   # (x, y, z) in milli‑g
    print(imu.angular_rate_mdps()) # (x, y, z) in milli‑deg/s
    time.sleep_ms(100)

sensor แบบ time-of-flight

AE3 มี sensor แบบ time-of-flight หลายโซน 8×8 VL53L8CX ที่ส่งคืนการอ่านระยะทางสูงสุด 64 รายการต่อเฟรม โดยมีระยะสูงสุดประมาณ 4 ม. มันถูกเปิดเผยผ่านโมดูล tof --- ไดรเวอร์เซนเซอร์วัดระยะ time-of-flight — เรียก tof.init() เพื่อเริ่ม sensor และ tof.read_depth() เพื่อดึงเฟรม depth เป็น flat list ของการอ่านค่าเป็นมิลลิเมตร (หนึ่งต่อโซน):

import tof

tof.init()
while True:
    depth, depth_min, depth_max = tof.read_depth()
    print("min:", depth_min, "mm  max:", depth_max, "mm")

อาร์เรย์ depth ยังสามารถวาดทับเฟรมสีจาก sensor หลักได้ — tof.draw_depth() วาดลงบน image.Image ที่มีอยู่ ในขณะที่ tof.snapshot() ส่งคืนภาพ depth ที่เรนเดอร์ใหม่:

import image
import tof
import csi

# Bring up the VL53L8CX time-of-flight sensor.
tof.init()

# Configure the main camera at VGA RGB565.
cam = csi.CSI()
cam.reset()
cam.pixformat(csi.RGB565)
cam.framesize(csi.VGA)

# Off-screen framebuffer used to compose the camera frame and the
# up-scaled depth heat-map side by side before pushing the result
# back to the live preview.
b = image.Image(640, 480, image.RGB565)

while True:
    # Grab a colour frame from the main camera.
    img = cam.snapshot()

    try:
        # Capture TOF data [depth map, min distance, max distance].
        # vflip / hmirror align the ToF orientation with the camera.
        depth, dmin, dmax = tof.read_depth(vflip=True, hmirror=True)

        # Zones with no return read back as 0.0 — clamp them to the
        # frame's max distance so the colour palette doesn't show
        # them as "closest".
        for i in range(0, len(depth)):
            if depth[i] == 0.0:
                depth[i] = dmax

    except RuntimeError:
        # The sensor occasionally faults on a frame; reset and skip.
        tof.reset()
        continue

    # Draw the camera frame into the left half of the framebuffer,
    # scaled to 60% so it leaves room for the depth heat-map on
    # the right.
    b.draw_image(img, x=0, y=64+8, x_scale=0.6, hint=image.BILINEAR)

    # Up-sample the 8x8 depth array 30x with bicubic smoothing and
    # blend it into the right half using the depth palette.
    # scale=(0, 400) maps 0-400 mm to the full palette range.
    tof.draw_depth(b, depth, x=320+64+16, y=64+8, alpha=255,
                   hint=image.BICUBIC, x_scale=30, y_scale=30,
                   scale=(0, 400), color_palette=image.PALETTE_DEPTH)

    # Copy the composed framebuffer back into the live preview so
    # OpenMV IDE shows both panels.
    img.set(b)

Wi‑Fi

CYW43439 ออนบอร์ดถูกเปิดเผยผ่าน network --- การกำหนดค่าเครือข่าย เป็น station interface หลังจากเชื่อมต่อแล้ว ipconfig("addr4") จะส่งคืน pair (ip, netmask)

import network, time

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect("ssid", "password")
while not wlan.isconnected():
    time.sleep(1)
print("Wi‑Fi IP:", wlan.ipconfig("addr4")[0])

Bluetooth

CYW43439 เดียวกันนี้ยังเปิดเผย Bluetooth 5.1 ด้วย ใช้ aioble --- Async BLE สำหรับ BLE แบบ asyncio‑friendly — ตัวอย่างเช่น โฆษณาเป็น peripheral และรอให้ central เชื่อมต่อ:

import asyncio
import aioble

async def run():
    while True:
        conn = await aioble.advertise(250_000, name="OpenMV-AE3")
        print("Connected:", conn.device)
        await conn.disconnected()

asyncio.run(run())

อ้างอิง Bus

GPIO

ใช้ machine.Pin เพื่ออ่านหรือขับเคลื่อนพินที่มีการระบุซิลค์สกรีนใดๆ เอาต์พุตเป็น CMOS 3.3 V และสามารถ sink/source ได้สูงสุด 25 mA ต่อพิน

from machine import Pin

out = Pin("P0", Pin.OUT)
out.on()
out.off()
out.value(1)

inp = Pin("P1", Pin.IN, Pin.PULL_UP)
print(inp.value())

พิน input ใดก็ตามยังสามารถทริกเกอร์อินเทอร์รัปต์บนการเปลี่ยนขอบได้:

def handler(pin):
    print("triggered:", pin)

Pin("P1", Pin.IN, Pin.PULL_UP).irq(
    handler, Pin.IRQ_FALLING | Pin.IRQ_RISING,
)

UART

Bus

TX

RX

RTS

CTS

UART1

P4

P5

UART3

P9

P8

P7

P6

UART4

P0

P1

UART5

P2

P3

from machine import UART

uart = UART(1, baudrate=115200)
uart.write("hello")
uart.read(5)

UART3 เป็น bus เดียวที่มี hardware flow control เนื่องจาก P6–P9 อยู่บน B2B header และอ้างอิง 1.8 V UART3 จึงทำงานได้เฉพาะผ่าน level shifter หรือ B2B carrier — อย่าเชื่อมต่อลอจิก 3.3 V โดยตรง

I²C

Bus

SCL

SDA

I2C1

P4

P5

I2C2

P0

P1

LPI2C

P3

P2

from machine import I2C

i2c = I2C(1, freq=400_000)
i2c.scan()
i2c.writeto(0x76, b"hi")

ตัวเชื่อมต่อ Qwiic ออนบอร์ดเปิดเผย I2C2 ที่ 3.3 V

I2C1 และ I2C2 ยังสามารถใช้ในโหมด target (slave) ผ่าน machine.I2CTarget เพื่อเปิดเผย memory region ให้กับ I²C controller อื่น:

from machine import I2CTarget

buf = bytearray(32)
target = I2CTarget(1, addr=0x42, mem=buf)

Note

อุปกรณ์ต่อพ่วง LPI2C ไม่ได้เปิดเผยใน firmware มันจะรองรับเฉพาะ โหมด target (slave) เท่านั้นหากถูกเปิดเผย และ I2C1 และ I2C2 ครอบคลุมทั้งการทำงานแบบ controller และ target อยู่แล้ว

SPI

Bus

MOSI

MISO

SCK

CS

SPI0

P0

P1

P2

P3

from machine import SPI
from machine import Pin

spi = SPI(0, baudrate=10_000_000)
cs = Pin("P3", Pin.OUT, value=1)   # CS is not driven by the SPI peripheral

cs.value(0)
spi.write(b"hello")
cs.value(1)

ADC

Alif Ensemble E3 เปิดเผยช่อง ADC 12‑bit สองช่องบน P8 และ P9 (เฉพาะ B2B header) อินพุตทั้งสองอ้างอิง 1.8 Vread_u16 ส่งคืน 0–65535 ตลอด 0–1.8 V ที่พิน:

from machine import ADC
import time

adc = ADC("P8")
while True:
    voltage = adc.read_u16() * 1.8 / 65535
    print(voltage)
    time.sleep_ms(100)

Warning

อินพุต ADC ของ AE3 อ้างอิง 1.8 V ไม่ใช่ 3.3 V การจ่ายสัญญาณ 3.3 V ดิบเข้าไปจะทำให้ converter อิ่มตัวและอาจทำให้พินเสียหาย — ลดแรงดันที่สูงกว่าลงด้วยภายนอก

PWM

พิน

Timer / channel

P0

TIM0 T1

P1

TIM0 T0

P2

TIM1 T1

P3

TIM1 T0

P4

TIM2 T1

P5

TIM2 T0

P6

TIM9 T0 (B2B only)

P7

TIM9 T1 (B2B only)

P8

TIM5 T0 (B2B only)

P9

TIM5 T1 (B2B only)

ขับเคลื่อนทุกตัวผ่าน machine.PWM

from machine import Pin, PWM

pwm = PWM(Pin("P0"), freq=1_000, duty_u16=32768)

Bus แบบ software bit‑banged

machine.SoftI2C และ machine.SoftSPI ทำงานบน GPIO ใดๆ หากคุณต้องการ bus เพิ่มเติม

Thermal sensor (ภายนอกบอร์ด)

Firmware รวม driver fir --- ไดรเวอร์เซนเซอร์ความร้อน (fir == far infrared) สำหรับ AMG8833 8 × 8 thermal imager ที่ต่อสายภายนอก เชื่อมต่อโมดูลกับ I²C bus ที่ระบุด้านล่าง จากนั้นอ่านเฟรมด้วย fir.init() + fir.snapshot()

import time
import image
import fir

fir.init()                          # auto‑detects the sensor
clock = time.clock()

while True:
    clock.tick()
    try:
        img = fir.snapshot(x_scale=5, y_scale=5,
                           color_palette=image.PALETTE_IRONBOW,
                           hint=image.BICUBIC,
                           copy_to_fb=True)
    except OSError:
        continue
    print(clock.fps())

driver fir คุยกับ sensor ผ่าน I²C 1 เท่านั้น — ต่อสายโมดูลกับ P4 (SCL) และ P5 (SDA)

เวลา

time

โมดูล time ครอบคลุม blocking delays, monotonic ticks และการวัดเวลาที่ผ่านไป:

import time

time.sleep(1)              # seconds
time.sleep_ms(500)
time.sleep_us(10)

start = time.ticks_ms()
# ...do work...
elapsed = time.ticks_diff(time.ticks_ms(), start)

Virtual timers

machine.Timer กำหนดเวลา callbacks แบบ periodic หรือ one‑shot โดยไม่ใช้ hardware timer slot ส่ง -1 เป็น id เพื่อใช้ virtual (software) timer:

from machine import Timer

one_shot = Timer(-1)
one_shot.init(period=5_000, mode=Timer.ONE_SHOT,
              callback=lambda t: print("once"))

periodic = Timer(-1)
periodic.init(period=2_000, mode=Timer.PERIODIC,
              callback=lambda t: print("tick"))

ค่า Period เป็นมิลลิวินาที เรียก deinit() เพื่อหยุดและปล่อย slot

Real‑time clock

machine.RTC รักษาเวลานาฬิกาแขวนผ่านการรีเซ็ต สำรองด้วย backup RAM ออนชิป 4 KB ที่รอดจาก deep sleep:

from machine import RTC

rtc = RTC()
rtc.datetime((2026, 4, 30, 4, 12, 0, 0, 0))   # Y, M, D, weekday, h, m, s, subsec
print(rtc.datetime())

RTC ยังทำงานผ่าน deep sleep ดังนั้นคุณสามารถใช้มันเป็น wakeup source สำหรับ machine.deepsleep()

ข้อมูลการบูตและ runtime

หน้าต่าง USB bootloader

ในทุกการเปิดเครื่อง กล้องจะรัน bootloader สั้นๆ (สักสองสามวินาที) ที่ให้ OpenMV IDE อัปเดต firmware โดยไม่ต้องให้ผู้ใช้เข้าสู่โหมด DFU หลังจากหน้าต่างหมดเวลา bootloader จะส่งต่อให้ boot.py และจากนั้น main.py

สคริปต์ที่กำลังทำงานสามารถเข้าสู่ bootloader อีกครั้งตามต้องการโดยเรียก machine.bootloader()

import machine

machine.bootloader()

Filesystem และลำดับการบูต

AE3 firmware mount สูงสุดสอง filesystems เมื่อบูต:

  • Internal flash — mount ที่ /flash เสมอ เก็บ main.py และ README.txt โดยค่าเริ่มต้น สร้างขึ้นในการบูตครั้งแรก

  • ROMFS — filesystem แบบ read-only, memory-mapped ที่ /rom ใช้เพื่อจัดส่ง data assets ขนาดใหญ่ (เช่น AI models) ที่ได้ประโยชน์จากการเข้าถึงแบบ zero-copy Mount โดยอัตโนมัติโดย MicroPython เมื่อเริ่มต้น ก่อน Python ของผู้ใช้ใดๆ ทำงาน

หลังจาก mount แล้ว working directory จะถูกตั้งค่าเป็น /flash จากนั้น interpreter จะรันสคริปต์จากไดเรกทอรีนั้น:

  • boot.py ถูกรันในทุก soft reset (cold boot, Ctrl‑D จาก REPL หรือเมื่อสคริปต์ที่กำลังทำงานส่งคืน)

  • main.py ถูกรัน เฉพาะใน cold boot เท่านั้น ทันทีหลังจาก boot.py soft resets ต่อมาจะรัน boot.py อีกครั้งแต่ตกลงตรงไปยัง REPL — เพื่อรัน main.py อีกครั้ง คุณต้องรีเซ็ตบอร์ดอย่างเต็มรูปแบบ

main.py เริ่มต้นที่จัดส่งบนบอร์ดที่ flash ใหม่เพียงแค่กะพริบช่อง สีน้ำเงิน ของ RGB LED ผู้ใช้เป็น heartbeat (สองพัลส์สั้น ช่องว่างสั้น) เพื่อให้คุณทราบว่า firmware บูตสะอาดโดยไม่มีโฮสต์ต่ออยู่

sys.path ถูกขยายให้รวมทั้งสอง filesystems และ subdirectories lib/ ของมัน ดังนั้นโมดูลที่ import ได้สามารถอยู่ใน /flash/lib หรือ /rom/lib

เมื่อเชื่อมต่อผ่าน USB, /flash ยังแสดงผลเป็น USB mass‑storage drive บนโฮสต์ด้วย ให้คุณแก้ไข boot.py, main.py และไฟล์อื่นๆ ได้โดยตรง Eject drive ก่อนรีเซ็ต camera เพื่อให้โฮสต์ flush การเขียนที่ cached ไว้

Note

เนื่องจาก OS จัดการ drive เป็น passive block device ไฟล์ที่สร้างหรือแก้ไขโดย code ที่ทำงานบน OpenMV Cam จะไม่แสดงขึ้นจนกว่าโฮสต์จะ mount drive อีกครั้ง หาก OS และ OpenMV Cam เขียน filesystem เดียวกันพร้อมกัน OS จะชนะและเขียนทับการเปลี่ยนแปลงที่ทำโดย camera

Note

ช่อง สีแดง ของ RGB LED ผู้ใช้อาจสว่างขึ้นชั่วคราวในขณะที่โฮสต์กำลังอ่านหรือเขียนไปยัง USB mass‑storage drive — นี่คือตัวบ่งชี้กิจกรรมที่ขับเคลื่อนด้วย firmware ไม่ใช่ข้อผิดพลาด

ขนาดพื้นที่จัดเก็บ

AE3 จัดส่งพร้อมกับ:

  • /flash — FAT filesystem ขนาด 8 MB, read/write

  • /rom บน HP core — 24 MB ROMFS แบบ read-only memory-mapped สำหรับสคริปต์และข้อมูลที่ HP core โหลดเมื่อเริ่มต้น

  • /rom บน HE core — 1 MB ROMFS แบบ read-only ที่ HE core เป็นเจ้าของ โมดูลและ ML models ที่คุณต้องการให้ใช้งานได้กับ tasks @openamp.async_remote จำเป็นต้องถูกอบเข้าไปใน image นี้ ไม่ใช่ HP

ตัวบ่งชี้ hard-fault

หาก RGB LED ผู้ใช้หมุนวนผ่านสีทั้งหมดอย่างรวดเร็ว — เร็วพอที่มีแนวโน้มจะดูเหมือน LED สีขาวที่กะพริบ มากกว่าสีที่แตกต่างกัน — firmware ได้เจอ hard fault ที่ไม่สามารถกู้คืนได้ Reflash firmware เพื่อกู้คืน หากการ reflash ไม่ช่วย บอร์ดอาจเสียหายทางกายภาพ

Software libraries

ดู library index สำหรับรายการโมดูลทั้งหมด รวมถึงโมดูลที่เป็นเอกลักษณ์ของ AE3 build