Skip to content

Commit 3b782a1

Browse files
committed
Restructure drivers
Now includes manifest files to freeze all drivers into firmware Display drivers broken up into multiple classes New ST7789 PIO driver added (not working yet)
1 parent 270f2ae commit 3b782a1

File tree

12 files changed

+606
-420
lines changed

12 files changed

+606
-420
lines changed

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ CURRENT_DIR = $(shell pwd)
77
# Set the MicroPython user C module path to the OpenCV module
88
MAKE_ARGS = USER_C_MODULES="$(CURRENT_DIR)/src/opencv_upy.cmake"
99

10+
# Use the OpenCV driver manifest
11+
MAKE_ARGS += FROZEN_MANIFEST="$(CURRENT_DIR)/cv2_drivers/manifest.py"
12+
1013
# Build MicroPython with the OpenCV module
1114
all:
1215
@cd micropython/ports/rp2 && export CMAKE_ARGS="$(CMAKE_ARGS)" && make -f Makefile $(MAKEFLAGS) $(MAKE_ARGS)

cv2_drivers/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from . import displays
2+
from . import cameras

cv2_drivers/cameras/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import hm01b0_pio

drivers/camera/hm01b0_pio.py renamed to cv2_drivers/cameras/hm01b0_pio.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
import rp2
2-
from machine import Pin, I2C
2+
from machine import Pin
33
from ulab import numpy as np
44
from time import sleep_us
55
import cv2
66

77
# Derived from:
88
# https://github.com/openmv/openmv/blob/5acf5baf92b4314a549bdd068138e5df6cc0bac7/drivers/sensors/hm01b0.c
99
class HM01B0_PIO():
10-
# Derived from:
11-
# https://github.com/openmv/openmv/blob/5acf5baf92b4314a549bdd068138e5df6cc0bac7/drivers/sensors/hm01b0_regs.h
1210

1311
# Read only registers
1412
MODEL_ID_H = 0x0000

cv2_drivers/displays/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from . import cv2_display
2+
from . import st7789
3+
from . import st7789_spi
4+
from . import st7789_pio

cv2_drivers/displays/cv2_display.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import cv2
2+
from ulab import numpy as np
3+
from machine import Pin
4+
5+
class CV2_Display():
6+
def __init__(self, buffer_size):
7+
# Create the frame buffer
8+
self.buffer = np.zeros(buffer_size, dtype=np.uint8)
9+
10+
def _get_common_roi_with_buffer(self, image):
11+
"""
12+
Get the common region of interest (ROI) between the image and the
13+
display's internal buffer.
14+
15+
Args:
16+
image (ndarray): Image to display
17+
18+
Returns:
19+
tuple: (image_roi, buffer_roi)
20+
"""
21+
# Ensure image is a NumPy ndarray
22+
if type(image) is not np.ndarray:
23+
raise TypeError("Image must be a NumPy ndarray")
24+
25+
# Determing number of rows and columns in the image
26+
image_rows = image.shape[0]
27+
if image.ndim < 2:
28+
image_cols = 1
29+
else:
30+
image_cols = image.shape[1]
31+
32+
# Get the common ROI between the image and the buffer
33+
row_max = min(image_rows, self.height)
34+
col_max = min(image_cols, self.width)
35+
img_roi = image[:row_max, :col_max]
36+
buffer_roi = self.buffer[:row_max, :col_max]
37+
return img_roi, buffer_roi
38+
39+
def _convert_image_to_uint8(self, image):
40+
"""
41+
Convert the image to uint8 format if necessary.
42+
43+
Args:
44+
image (ndarray): Image to convert
45+
46+
Returns:
47+
Image: Converted image
48+
"""
49+
# Check if the image is already in uint8 format
50+
if image.dtype is np.uint8:
51+
return image
52+
53+
# Convert to uint8 format. This unfortunately requires creating a new
54+
# buffer for the converted image, which takes more memory
55+
if image.dtype == np.int8:
56+
return cv2.convertScaleAbs(image, alpha=1, beta=127)
57+
elif image.dtype == np.int16:
58+
return cv2.convertScaleAbs(image, alpha=1/255, beta=127)
59+
elif image.dtype == np.uint16:
60+
return cv2.convertScaleAbs(image, alpha=1/255)
61+
elif image.dtype == np.float:
62+
# This implementation creates an additional buffer from np.clip()
63+
# TODO: Find another solution that avoids an additional buffer
64+
return cv2.convertScaleAbs(np.clip(image, 0, 1), alpha=255)
65+
else:
66+
raise ValueError(f"Unsupported image dtype: {image.dtype}")
67+
68+
def _write_image_to_buffer_bgr565(self, image_roi, buffer_roi):
69+
"""
70+
Convert the image ROI to BGR565 format and write it to the buffer ROI.
71+
72+
Args:
73+
image_roi (ndarray): Image region of interest
74+
buffer_roi (ndarray): Buffer region of interest
75+
"""
76+
# Determine the number of channels in the image
77+
if image_roi.ndim < 3:
78+
ch = 1
79+
else:
80+
ch = image_roi.shape[2]
81+
82+
if ch == 1: # Grayscale
83+
buffer_roi = cv2.cvtColor(image_roi, cv2.COLOR_GRAY2BGR565, buffer_roi)
84+
elif ch == 2: # Already in BGR565 format
85+
buffer_roi[:] = image_roi
86+
elif ch == 3: # BGR
87+
buffer_roi = cv2.cvtColor(image_roi, cv2.COLOR_BGR2BGR565, buffer_roi)
88+
else:
89+
raise ValueError("Image must be 1, 2 or 3 channels (grayscale, BGR565, or BGR)")
90+
91+
def savePinModeAlt(self, pin):
92+
"""
93+
Saves the current `mode` and `alt` of the pin so it can be restored
94+
later. Mostly used to restore the SPI mode (MISO) of the DC pin after
95+
communication with the display in case another device is using the same
96+
SPI bus.
97+
98+
Returns:
99+
tuple: (mode, alt)
100+
"""
101+
# See: https://github.com/micropython/micropython/issues/17515
102+
# There's no way to get the mode and alt of a pin directly, so we
103+
# convert the pin to a string and parse it. Example formats:
104+
# "Pin(GPIO16, mode=OUT)"
105+
# "Pin(GPIO16, mode=ALT, alt=SPI)"
106+
pinStr = str(pin)
107+
108+
# Extract the "mode" parameter from the pin string
109+
if "mode=" in pinStr:
110+
# Split between "mode=" and the next comma or closing parenthesis
111+
modeStr = pinStr.split("mode=")[1].split(",")[0].split(")")[0]
112+
113+
# Look up the mode in Pin class dictionary
114+
mode = Pin.__dict__[modeStr]
115+
else:
116+
# No mode specified, just set to None
117+
mode = None
118+
119+
# Extrct the "alt" parameter from the pin string
120+
if "alt=" in pinStr:
121+
# Split between "alt=" and the next comma or closing parenthesis
122+
altStr = pinStr.split("alt=")[1].split(",")[0].split(")")[0]
123+
124+
# Sometimes the value comes back as a number instead of a valid
125+
# "ALT_xyz" string, so we need to check it
126+
if "ALT_" + altStr in Pin.__dict__:
127+
# Look up the alt in Pin class dictionary (with "ALT_" prefix)
128+
alt = Pin.__dict__["ALT_" + altStr]
129+
else:
130+
# Convert the altStr to an integer
131+
alt = int(altStr)
132+
else:
133+
# No alt specified, just set to None
134+
alt = None
135+
136+
# Return the mode and alt as a tuple
137+
return (mode, alt)

0 commit comments

Comments
 (0)