1301 lines
29 KiB
Python
1301 lines
29 KiB
Python
# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
|
|
# SPDX-FileCopyrightText: Copyright (c) 2021 Jeff Epler for Adafruit Industries
|
|
#
|
|
# SPDX-License-Identifier: MIT
|
|
"""
|
|
`adafruit_ov2640`
|
|
================================================================================
|
|
|
|
CircuitPython driver for OV2640 Camera.
|
|
|
|
|
|
* Author(s): Jeff Epler
|
|
|
|
Implementation Notes
|
|
--------------------
|
|
|
|
**Hardware:**
|
|
|
|
* `ESP32-S2 Kaluga Dev Kit featuring ESP32-S2 WROVER <https://www.adafruit.com/product/4729>`_
|
|
|
|
**Software and Dependencies:**
|
|
|
|
* Adafruit CircuitPython firmware for the supported boards:
|
|
https://github.com/adafruit/circuitpython/releases
|
|
|
|
* Adafruit's Bus Device library: https:# github.com/adafruit/Adafruit_CircuitPython_BusDevice
|
|
"""
|
|
|
|
# pylint: disable=too-many-lines
|
|
# imports
|
|
|
|
__version__ = "0.0.0-auto.0"
|
|
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_OV7670.git"
|
|
|
|
import time
|
|
|
|
import digitalio
|
|
import imagecapture
|
|
import pwmio
|
|
from adafruit_bus_device.i2c_device import I2CDevice
|
|
|
|
from micropython import const
|
|
|
|
CTRLI = const(0x50)
|
|
_R_BYPASS = const(0x05)
|
|
_QS = const(0x44)
|
|
_CTRLI = const(0x50)
|
|
_HSIZE = const(0x51)
|
|
_VSIZE = const(0x52)
|
|
_XOFFL = const(0x53)
|
|
_YOFFL = const(0x54)
|
|
_VHYX = const(0x55)
|
|
_DPRP = const(0x56)
|
|
_TEST = const(0x57)
|
|
_ZMOW = const(0x5A)
|
|
_ZMOH = const(0x5B)
|
|
_ZMHH = const(0x5C)
|
|
_BPADDR = const(0x7C)
|
|
_BPDATA = const(0x7D)
|
|
_CTRL2 = const(0x86)
|
|
_CTRL3 = const(0x87)
|
|
_SIZEL = const(0x8C)
|
|
_HSIZE8 = const(0xC0)
|
|
_VSIZE8 = const(0xC1)
|
|
_CTRL0 = const(0xC2)
|
|
_CTRL1 = const(0xC3)
|
|
_R_DVP_SP = const(0xD3)
|
|
_IMAGE_MODE = const(0xDA)
|
|
_RESET = const(0xE0)
|
|
_MS_SP = const(0xF0)
|
|
_SS_ID = const(0xF7)
|
|
_SS_CTRL = const(0xF7)
|
|
_MC_BIST = const(0xF9)
|
|
_MC_AL = const(0xFA)
|
|
_MC_AH = const(0xFB)
|
|
_MC_D = const(0xFC)
|
|
_P_CMD = const(0xFD)
|
|
_P_STATUS = const(0xFE)
|
|
_BANK_SEL = const(0xFF)
|
|
|
|
_CTRLI_LP_DP = const(0x80)
|
|
_CTRLI_ROUND = const(0x40)
|
|
|
|
_CTRL0_AEC_EN = const(0x80)
|
|
_CTRL0_AEC_SEL = const(0x40)
|
|
_CTRL0_STAT_SEL = const(0x20)
|
|
_CTRL0_VFIRST = const(0x10)
|
|
_CTRL0_YUV422 = const(0x08)
|
|
_CTRL0_YUV_EN = const(0x04)
|
|
_CTRL0_RGB_EN = const(0x02)
|
|
_CTRL0_RAW_EN = const(0x01)
|
|
|
|
_CTRL2_DCW_EN = const(0x20)
|
|
_CTRL2_SDE_EN = const(0x10)
|
|
_CTRL2_UV_ADJ_EN = const(0x08)
|
|
_CTRL2_UV_AVG_EN = const(0x04)
|
|
_CTRL2_CMX_EN = const(0x01)
|
|
|
|
_CTRL3_BPC_EN = const(0x80)
|
|
_CTRL3_WPC_EN = const(0x40)
|
|
|
|
_R_DVP_SP_AUTO_MODE = const(0x80)
|
|
|
|
_R_BYPASS_DSP_EN = const(0x00)
|
|
_R_BYPASS_DSP_BYPAS = const(0x01)
|
|
|
|
OV2640_COLOR_RGB = 0
|
|
OV2640_COLOR_YUV = 1
|
|
|
|
_IMAGE_MODE_Y8_DVP_EN = const(0x40)
|
|
_IMAGE_MODE_JPEG_EN = const(0x10)
|
|
_IMAGE_MODE_YUV422 = const(0x00)
|
|
_IMAGE_MODE_RAW10 = const(0x04)
|
|
_IMAGE_MODE_RGB565 = const(0x08)
|
|
_IMAGE_MODE_HREF_VSYNC = const(0x02)
|
|
_IMAGE_MODE_LBYTE_FIRST = const(0x01)
|
|
|
|
_RESET_MICROC = const(0x40)
|
|
_RESET_SCCB = const(0x20)
|
|
_RESET_JPEG = const(0x10)
|
|
_RESET_DVP = const(0x04)
|
|
_RESET_IPU = const(0x02)
|
|
_RESET_CIF = const(0x01)
|
|
|
|
_MC_BIST_RESET = const(0x80)
|
|
_MC_BIST_BOOT_ROM_SEL = const(0x40)
|
|
_MC_BIST_12KB_SEL = const(0x20)
|
|
_MC_BIST_12KB_MASK = const(0x30)
|
|
_MC_BIST_512KB_SEL = const(0x08)
|
|
_MC_BIST_512KB_MASK = const(0x0C)
|
|
_MC_BIST_BUSY_BIT_R = const(0x02)
|
|
_MC_BIST_MC_RES_ONE_SH_W = const(0x02)
|
|
_MC_BIST_LAUNCH = const(0x01)
|
|
|
|
|
|
_BANK_DSP = const(0)
|
|
_BANK_SENSOR = const(1)
|
|
|
|
# Sensor register bank FF=0x01
|
|
_GAIN = const(0x00)
|
|
_COM1 = const(0x03)
|
|
_REG04 = const(0x04)
|
|
_REG08 = const(0x08)
|
|
_COM2 = const(0x09)
|
|
_REG_PID = const(0x0A)
|
|
_REG_VER = const(0x0B)
|
|
_COM3 = const(0x0C)
|
|
_COM4 = const(0x0D)
|
|
_AEC = const(0x10)
|
|
_CLKRC = const(0x11)
|
|
_COM7 = const(0x12)
|
|
_COM8 = const(0x13)
|
|
_COM9 = const(0x14) # AGC gain ceiling
|
|
_COM10 = const(0x15)
|
|
_HSTART = const(0x17)
|
|
_HSTOP = const(0x18)
|
|
_VSTART = const(0x19)
|
|
_VSTOP = const(0x1A)
|
|
_MIDH = const(0x1C)
|
|
_MIDL = const(0x1D)
|
|
_AEW = const(0x24)
|
|
_AEB = const(0x25)
|
|
_VV = const(0x26)
|
|
_REG2A = const(0x2A)
|
|
_FRARL = const(0x2B)
|
|
_ADDVSL = const(0x2D)
|
|
_ADDVSH = const(0x2E)
|
|
_YAVG = const(0x2F)
|
|
_HSDY = const(0x30)
|
|
_HEDY = const(0x31)
|
|
_REG32 = const(0x32)
|
|
_ARCOM2 = const(0x34)
|
|
_REG45 = const(0x45)
|
|
_FLL = const(0x46)
|
|
_FLH = const(0x47)
|
|
_COM19 = const(0x48)
|
|
_ZOOMS = const(0x49)
|
|
_COM22 = const(0x4B)
|
|
_COM25 = const(0x4E)
|
|
_BD50 = const(0x4F)
|
|
_BD60 = const(0x50)
|
|
_REG5D = const(0x5D)
|
|
_REG5E = const(0x5E)
|
|
_REG5F = const(0x5F)
|
|
_REG60 = const(0x60)
|
|
_HISTO_LOW = const(0x61)
|
|
_HISTO_HIGH = const(0x62)
|
|
|
|
_REG04_DEFAULT = const(0x28)
|
|
_REG04_HFLIP_IMG = const(0x80)
|
|
_REG04_VFLIP_IMG = const(0x40)
|
|
_REG04_VREF_EN = const(0x10)
|
|
_REG04_HREF_EN = const(0x08)
|
|
_REG04_SET = lambda x: (_REG04_DEFAULT | x)
|
|
|
|
_COM2_STDBY = const(0x10)
|
|
_COM2_OUT_DRIVE_1x = const(0x00)
|
|
_COM2_OUT_DRIVE_2x = const(0x01)
|
|
_COM2_OUT_DRIVE_3x = const(0x02)
|
|
_COM2_OUT_DRIVE_4x = const(0x03)
|
|
|
|
_COM3_DEFAULT = const(0x38)
|
|
_COM3_BAND_50Hz = const(0x04)
|
|
_COM3_BAND_60Hz = const(0x00)
|
|
_COM3_BAND_AUTO = const(0x02)
|
|
_COM3_BAND_SET = lambda x: (_COM3_DEFAULT | x)
|
|
|
|
_COM7_SRST = const(0x80)
|
|
_COM7_RES_UXGA = const(0x00) # UXGA
|
|
_COM7_RES_SVGA = const(0x40) # SVGA
|
|
_COM7_RES_CIF = const(0x20) # CIF
|
|
_COM7_ZOOM_EN = const(0x04) # Enable Zoom
|
|
_COM7_COLOR_BAR = const(0x02) # Enable Color Bar Test
|
|
|
|
_COM8_DEFAULT = const(0xC0)
|
|
_COM8_BNDF_EN = const(0x20) # Enable Banding filter
|
|
_COM8_AGC_EN = const(0x04) # AGC Auto/Manual control selection
|
|
_COM8_AEC_EN = const(0x01) # Auto/Manual Exposure control
|
|
_COM8_SET = lambda x: (_COM8_DEFAULT | x)
|
|
|
|
_COM9_DEFAULT = const(0x08)
|
|
_COM9_AGC_GAIN_2x = const(0x00) # AGC: 2x
|
|
_COM9_AGC_GAIN_4x = const(0x01) # AGC: 4x
|
|
_COM9_AGC_GAIN_8x = const(0x02) # AGC: 8x
|
|
_COM9_AGC_GAIN_16x = const(0x03) # AGC: 16x
|
|
_COM9_AGC_GAIN_32x = const(0x04) # AGC: 32x
|
|
_COM9_AGC_GAIN_64x = const(0x05) # AGC: 64x
|
|
_COM9_AGC_GAIN_128x = const(0x06) # AGC: 128x
|
|
_COM9_AGC_SET = lambda x: (_COM9_DEFAULT | (x << 5))
|
|
|
|
_COM10_HREF_EN = const(0x80) # HSYNC changes to HREF
|
|
_COM10_HSYNC_EN = const(0x40) # HREF changes to HSYNC
|
|
_COM10_PCLK_FREE = const(0x20) # PCLK output option: free running PCLK
|
|
_COM10_PCLK_EDGE = const(0x10) # Data is updated at the rising edge of PCLK
|
|
_COM10_HREF_NEG = const(0x08) # HREF negative
|
|
_COM10_VSYNC_NEG = const(0x02) # VSYNC negative
|
|
_COM10_HSYNC_NEG = const(0x01) # HSYNC negative
|
|
|
|
_CTRL1_AWB = const(0x08) # Enable AWB
|
|
|
|
_VV_AGC_TH_SET = lambda h, l: ((h << 4) | (l & 0x0F))
|
|
|
|
_REG32_UXGA = const(0x36)
|
|
_REG32_SVGA = const(0x09)
|
|
_REG32_CIF = const(0x89)
|
|
|
|
_CLKRC_2X = const(0x80)
|
|
_CLKRC_2X_UXGA = const(0x01 | _CLKRC_2X)
|
|
_CLKRC_2X_SVGA = _CLKRC_2X
|
|
_CLKRC_2X_CIF = _CLKRC_2X
|
|
|
|
_OV2640_MODE_CIF = const(0)
|
|
_OV2640_MODE_SVGA = const(1)
|
|
_OV2640_MODE_UXGA = const(2)
|
|
|
|
OV2640_SIZE_96X96 = 0 # 96x96
|
|
OV2640_SIZE_QQVGA = 1 # 160x120
|
|
OV2640_SIZE_QCIF = 2 # 176x144
|
|
OV2640_SIZE_HQVGA = 3 # 240x176
|
|
OV2640_SIZE_240X240 = 4 # 240x240
|
|
OV2640_SIZE_QVGA = 5 # 320x240
|
|
OV2640_SIZE_CIF = 6 # 400x296
|
|
OV2640_SIZE_HVGA = 7 # 480x320
|
|
OV2640_SIZE_VGA = 8 # 640x480
|
|
OV2640_SIZE_SVGA = 9 # 800x600
|
|
OV2640_SIZE_XGA = 10 # 1024x768
|
|
OV2640_SIZE_HD = 11 # 1280x720
|
|
OV2640_SIZE_SXGA = 12 # 1280x1024
|
|
OV2640_SIZE_UXGA = 13 # 1600x1200
|
|
|
|
_ASPECT_RATIO_4X3 = const(0)
|
|
_ASPECT_RATIO_3X2 = const(1)
|
|
_ASPECT_RATIO_16X10 = const(2)
|
|
_ASPECT_RATIO_5X3 = const(3)
|
|
_ASPECT_RATIO_16X9 = const(4)
|
|
_ASPECT_RATIO_21X9 = const(5)
|
|
_ASPECT_RATIO_5X4 = const(6)
|
|
_ASPECT_RATIO_1X1 = const(7)
|
|
_ASPECT_RATIO_9X16 = const(8)
|
|
|
|
_resolution_info = [
|
|
[96, 96, _ASPECT_RATIO_1X1], # 96x96
|
|
[160, 120, _ASPECT_RATIO_4X3], # QQVGA
|
|
[176, 144, _ASPECT_RATIO_5X4], # QCIF
|
|
[240, 176, _ASPECT_RATIO_4X3], # HQVGA
|
|
[240, 240, _ASPECT_RATIO_1X1], # 240x240
|
|
[320, 240, _ASPECT_RATIO_4X3], # QVGA
|
|
[400, 296, _ASPECT_RATIO_4X3], # CIF
|
|
[480, 320, _ASPECT_RATIO_3X2], # HVGA
|
|
[640, 480, _ASPECT_RATIO_4X3], # VGA
|
|
[800, 600, _ASPECT_RATIO_4X3], # SVGA
|
|
[1024, 768, _ASPECT_RATIO_4X3], # XGA
|
|
[1280, 720, _ASPECT_RATIO_16X9], # HD
|
|
[1280, 1024, _ASPECT_RATIO_5X4], # SXGA
|
|
[1600, 1200, _ASPECT_RATIO_4X3], # UXGA
|
|
]
|
|
|
|
_ratio_table = [
|
|
# ox, oy, mx, my
|
|
[0, 0, 1600, 1200], # 4x3
|
|
[8, 72, 1584, 1056], # 3x2
|
|
[0, 100, 1600, 1000], # 16x10
|
|
[0, 120, 1600, 960], # 5x3
|
|
[0, 150, 1600, 900], # 16x9
|
|
[2, 258, 1596, 684], # 21x9
|
|
[50, 0, 1500, 1200], # 5x4
|
|
[200, 0, 1200, 1200], # 1x1
|
|
[462, 0, 676, 1200], # 9x16
|
|
]
|
|
|
|
# 30fps@24MHz
|
|
_ov2640_settings_cif = bytes(
|
|
[
|
|
_BANK_SEL,
|
|
_BANK_DSP,
|
|
0x2C,
|
|
0xFF,
|
|
0x2E,
|
|
0xDF,
|
|
_BANK_SEL,
|
|
_BANK_SENSOR,
|
|
0x3C,
|
|
0x32,
|
|
_CLKRC,
|
|
0x01,
|
|
_COM2,
|
|
_COM2_OUT_DRIVE_3x,
|
|
_REG04,
|
|
_REG04_DEFAULT,
|
|
_COM8,
|
|
_COM8_DEFAULT | _COM8_BNDF_EN | _COM8_AGC_EN | _COM8_AEC_EN,
|
|
_COM9,
|
|
_COM9_AGC_SET(_COM9_AGC_GAIN_8x),
|
|
0x2C,
|
|
0x0C,
|
|
0x33,
|
|
0x78,
|
|
0x3A,
|
|
0x33,
|
|
0x3B,
|
|
0xFB,
|
|
0x3E,
|
|
0x00,
|
|
0x43,
|
|
0x11,
|
|
0x16,
|
|
0x10,
|
|
0x39,
|
|
0x92,
|
|
0x35,
|
|
0xDA,
|
|
0x22,
|
|
0x1A,
|
|
0x37,
|
|
0xC3,
|
|
0x23,
|
|
0x00,
|
|
_ARCOM2,
|
|
0xC0,
|
|
0x06,
|
|
0x88,
|
|
0x07,
|
|
0xC0,
|
|
_COM4,
|
|
0x87,
|
|
0x0E,
|
|
0x41,
|
|
0x4C,
|
|
0x00,
|
|
0x4A,
|
|
0x81,
|
|
0x21,
|
|
0x99,
|
|
_AEW,
|
|
0x40,
|
|
_AEB,
|
|
0x38,
|
|
_VV,
|
|
_VV_AGC_TH_SET(8, 2),
|
|
0x5C,
|
|
0x00,
|
|
0x63,
|
|
0x00,
|
|
_HISTO_LOW,
|
|
0x70,
|
|
_HISTO_HIGH,
|
|
0x80,
|
|
0x7C,
|
|
0x05,
|
|
0x20,
|
|
0x80,
|
|
0x28,
|
|
0x30,
|
|
0x6C,
|
|
0x00,
|
|
0x6D,
|
|
0x80,
|
|
0x6E,
|
|
0x00,
|
|
0x70,
|
|
0x02,
|
|
0x71,
|
|
0x94,
|
|
0x73,
|
|
0xC1,
|
|
0x3D,
|
|
0x34,
|
|
0x5A,
|
|
0x57,
|
|
_BD50,
|
|
0xBB,
|
|
_BD60,
|
|
0x9C,
|
|
_COM7,
|
|
_COM7_RES_CIF,
|
|
_HSTART,
|
|
0x11,
|
|
_HSTOP,
|
|
0x43,
|
|
_VSTART,
|
|
0x00,
|
|
_VSTOP,
|
|
0x25,
|
|
_REG32,
|
|
0x89,
|
|
0x37,
|
|
0xC0,
|
|
_BD50,
|
|
0xCA,
|
|
_BD60,
|
|
0xA8,
|
|
0x6D,
|
|
0x00,
|
|
0x3D,
|
|
0x38,
|
|
_BANK_SEL,
|
|
_BANK_DSP,
|
|
0xE5,
|
|
0x7F,
|
|
_MC_BIST,
|
|
_MC_BIST_RESET | _MC_BIST_BOOT_ROM_SEL,
|
|
0x41,
|
|
0x24,
|
|
_RESET,
|
|
_RESET_JPEG | _RESET_DVP,
|
|
0x76,
|
|
0xFF,
|
|
0x33,
|
|
0xA0,
|
|
0x42,
|
|
0x20,
|
|
0x43,
|
|
0x18,
|
|
0x4C,
|
|
0x00,
|
|
_CTRL3,
|
|
_CTRL3_WPC_EN | 0x10,
|
|
0x88,
|
|
0x3F,
|
|
0xD7,
|
|
0x03,
|
|
0xD9,
|
|
0x10,
|
|
_R_DVP_SP,
|
|
_R_DVP_SP_AUTO_MODE | 0x02,
|
|
0xC8,
|
|
0x08,
|
|
0xC9,
|
|
0x80,
|
|
_BPADDR,
|
|
0x00,
|
|
_BPDATA,
|
|
0x00,
|
|
_BPADDR,
|
|
0x03,
|
|
_BPDATA,
|
|
0x48,
|
|
_BPDATA,
|
|
0x48,
|
|
_BPADDR,
|
|
0x08,
|
|
_BPDATA,
|
|
0x20,
|
|
_BPDATA,
|
|
0x10,
|
|
_BPDATA,
|
|
0x0E,
|
|
0x90,
|
|
0x00,
|
|
0x91,
|
|
0x0E,
|
|
0x91,
|
|
0x1A,
|
|
0x91,
|
|
0x31,
|
|
0x91,
|
|
0x5A,
|
|
0x91,
|
|
0x69,
|
|
0x91,
|
|
0x75,
|
|
0x91,
|
|
0x7E,
|
|
0x91,
|
|
0x88,
|
|
0x91,
|
|
0x8F,
|
|
0x91,
|
|
0x96,
|
|
0x91,
|
|
0xA3,
|
|
0x91,
|
|
0xAF,
|
|
0x91,
|
|
0xC4,
|
|
0x91,
|
|
0xD7,
|
|
0x91,
|
|
0xE8,
|
|
0x91,
|
|
0x20,
|
|
0x92,
|
|
0x00,
|
|
0x93,
|
|
0x06,
|
|
0x93,
|
|
0xE3,
|
|
0x93,
|
|
0x05,
|
|
0x93,
|
|
0x05,
|
|
0x93,
|
|
0x00,
|
|
0x93,
|
|
0x04,
|
|
0x93,
|
|
0x00,
|
|
0x93,
|
|
0x00,
|
|
0x93,
|
|
0x00,
|
|
0x93,
|
|
0x00,
|
|
0x93,
|
|
0x00,
|
|
0x93,
|
|
0x00,
|
|
0x93,
|
|
0x00,
|
|
0x96,
|
|
0x00,
|
|
0x97,
|
|
0x08,
|
|
0x97,
|
|
0x19,
|
|
0x97,
|
|
0x02,
|
|
0x97,
|
|
0x0C,
|
|
0x97,
|
|
0x24,
|
|
0x97,
|
|
0x30,
|
|
0x97,
|
|
0x28,
|
|
0x97,
|
|
0x26,
|
|
0x97,
|
|
0x02,
|
|
0x97,
|
|
0x98,
|
|
0x97,
|
|
0x80,
|
|
0x97,
|
|
0x00,
|
|
0x97,
|
|
0x00,
|
|
0xA4,
|
|
0x00,
|
|
0xA8,
|
|
0x00,
|
|
0xC5,
|
|
0x11,
|
|
0xC6,
|
|
0x51,
|
|
0xBF,
|
|
0x80,
|
|
0xC7,
|
|
0x10,
|
|
0xB6,
|
|
0x66,
|
|
0xB8,
|
|
0xA5,
|
|
0xB7,
|
|
0x64,
|
|
0xB9,
|
|
0x7C,
|
|
0xB3,
|
|
0xAF,
|
|
0xB4,
|
|
0x97,
|
|
0xB5,
|
|
0xFF,
|
|
0xB0,
|
|
0xC5,
|
|
0xB1,
|
|
0x94,
|
|
0xB2,
|
|
0x0F,
|
|
0xC4,
|
|
0x5C,
|
|
_CTRL1,
|
|
0xFD,
|
|
0x7F,
|
|
0x00,
|
|
0xE5,
|
|
0x1F,
|
|
0xE1,
|
|
0x67,
|
|
0xDD,
|
|
0x7F,
|
|
_IMAGE_MODE,
|
|
0x00,
|
|
_RESET,
|
|
0x00,
|
|
_R_BYPASS,
|
|
_R_BYPASS_DSP_EN,
|
|
]
|
|
)
|
|
|
|
_ov2640_settings_to_cif = bytes(
|
|
[
|
|
_BANK_SEL,
|
|
_BANK_SENSOR,
|
|
_COM7,
|
|
_COM7_RES_CIF,
|
|
# Set the sensor output window
|
|
_COM1,
|
|
0x0A,
|
|
_REG32,
|
|
_REG32_CIF,
|
|
_HSTART,
|
|
0x11,
|
|
_HSTOP,
|
|
0x43,
|
|
_VSTART,
|
|
0x00,
|
|
_VSTOP,
|
|
0x25,
|
|
# _CLKRC, 0x00,
|
|
_BD50,
|
|
0xCA,
|
|
_BD60,
|
|
0xA8,
|
|
0x5A,
|
|
0x23,
|
|
0x6D,
|
|
0x00,
|
|
0x3D,
|
|
0x38,
|
|
0x39,
|
|
0x92,
|
|
0x35,
|
|
0xDA,
|
|
0x22,
|
|
0x1A,
|
|
0x37,
|
|
0xC3,
|
|
0x23,
|
|
0x00,
|
|
_ARCOM2,
|
|
0xC0,
|
|
0x06,
|
|
0x88,
|
|
0x07,
|
|
0xC0,
|
|
_COM4,
|
|
0x87,
|
|
0x0E,
|
|
0x41,
|
|
0x4C,
|
|
0x00,
|
|
_BANK_SEL,
|
|
_BANK_DSP,
|
|
_RESET,
|
|
_RESET_DVP,
|
|
# Set the sensor resolution (UXGA, SVGA, CIF)
|
|
_HSIZE8,
|
|
0x32,
|
|
_VSIZE8,
|
|
0x25,
|
|
_SIZEL,
|
|
0x00,
|
|
# Set the image window size >= output size
|
|
_HSIZE,
|
|
0x64,
|
|
_VSIZE,
|
|
0x4A,
|
|
_XOFFL,
|
|
0x00,
|
|
_YOFFL,
|
|
0x00,
|
|
_VHYX,
|
|
0x00,
|
|
_TEST,
|
|
0x00,
|
|
_CTRL2,
|
|
_CTRL2_DCW_EN | 0x1D,
|
|
_CTRLI,
|
|
_CTRLI_LP_DP | 0x00,
|
|
# _R_DVP_SP, 0x08,
|
|
]
|
|
)
|
|
|
|
_ov2640_settings_to_svga = bytes(
|
|
[
|
|
_BANK_SEL,
|
|
_BANK_SENSOR,
|
|
_COM7,
|
|
_COM7_RES_SVGA,
|
|
# Set the sensor output window
|
|
_COM1,
|
|
0x0A,
|
|
_REG32,
|
|
_REG32_SVGA,
|
|
_HSTART,
|
|
0x11,
|
|
_HSTOP,
|
|
0x43,
|
|
_VSTART,
|
|
0x00,
|
|
_VSTOP,
|
|
0x4B,
|
|
# _CLKRC, 0x00,
|
|
0x37,
|
|
0xC0,
|
|
_BD50,
|
|
0xCA,
|
|
_BD60,
|
|
0xA8,
|
|
0x5A,
|
|
0x23,
|
|
0x6D,
|
|
0x00,
|
|
0x3D,
|
|
0x38,
|
|
0x39,
|
|
0x92,
|
|
0x35,
|
|
0xDA,
|
|
0x22,
|
|
0x1A,
|
|
0x37,
|
|
0xC3,
|
|
0x23,
|
|
0x00,
|
|
_ARCOM2,
|
|
0xC0,
|
|
0x06,
|
|
0x88,
|
|
0x07,
|
|
0xC0,
|
|
_COM4,
|
|
0x87,
|
|
0x0E,
|
|
0x41,
|
|
0x42,
|
|
0x03,
|
|
0x4C,
|
|
0x00,
|
|
_BANK_SEL,
|
|
_BANK_DSP,
|
|
_RESET,
|
|
_RESET_DVP,
|
|
# Set the sensor resolution (UXGA, SVGA, CIF)
|
|
_HSIZE8,
|
|
0x64,
|
|
_VSIZE8,
|
|
0x4B,
|
|
_SIZEL,
|
|
0x00,
|
|
# Set the image window size >= output size
|
|
_HSIZE,
|
|
0xC8,
|
|
_VSIZE,
|
|
0x96,
|
|
_XOFFL,
|
|
0x00,
|
|
_YOFFL,
|
|
0x00,
|
|
_VHYX,
|
|
0x00,
|
|
_TEST,
|
|
0x00,
|
|
_CTRL2,
|
|
_CTRL2_DCW_EN | 0x1D,
|
|
_CTRLI,
|
|
_CTRLI_LP_DP | 0x00,
|
|
# _R_DVP_SP, 0x08,
|
|
]
|
|
)
|
|
|
|
_ov2640_settings_to_uxga = bytes(
|
|
[
|
|
_BANK_SEL,
|
|
_BANK_SENSOR,
|
|
_COM7,
|
|
_COM7_RES_UXGA,
|
|
# Set the sensor output window
|
|
_COM1,
|
|
0x0F,
|
|
_REG32,
|
|
_REG32_UXGA,
|
|
_HSTART,
|
|
0x11,
|
|
_HSTOP,
|
|
0x75,
|
|
_VSTART,
|
|
0x01,
|
|
_VSTOP,
|
|
0x97,
|
|
# _CLKRC, 0x00,
|
|
0x3D,
|
|
0x34,
|
|
_BD50,
|
|
0xBB,
|
|
_BD60,
|
|
0x9C,
|
|
0x5A,
|
|
0x57,
|
|
0x6D,
|
|
0x80,
|
|
0x39,
|
|
0x82,
|
|
0x23,
|
|
0x00,
|
|
0x07,
|
|
0xC0,
|
|
0x4C,
|
|
0x00,
|
|
0x35,
|
|
0x88,
|
|
0x22,
|
|
0x0A,
|
|
0x37,
|
|
0x40,
|
|
_ARCOM2,
|
|
0xA0,
|
|
0x06,
|
|
0x02,
|
|
_COM4,
|
|
0xB7,
|
|
0x0E,
|
|
0x01,
|
|
0x42,
|
|
0x83,
|
|
_BANK_SEL,
|
|
_BANK_DSP,
|
|
_RESET,
|
|
_RESET_DVP,
|
|
# Set the sensor resolution (UXGA, SVGA, CIF)
|
|
_HSIZE8,
|
|
0xC8,
|
|
_VSIZE8,
|
|
0x96,
|
|
_SIZEL,
|
|
0x00,
|
|
# Set the image window size >= output size
|
|
_HSIZE,
|
|
0x90,
|
|
_VSIZE,
|
|
0x2C,
|
|
_XOFFL,
|
|
0x00,
|
|
_YOFFL,
|
|
0x00,
|
|
_VHYX,
|
|
0x88,
|
|
_TEST,
|
|
0x00,
|
|
_CTRL2,
|
|
_CTRL2_DCW_EN | 0x1D,
|
|
_CTRLI,
|
|
0x00,
|
|
# _R_DVP_SP, 0x06,
|
|
]
|
|
)
|
|
|
|
# _ov2640_settings_jpeg3 = bytes([
|
|
# _BANK_SEL, _BANK_DSP,
|
|
# _RESET, _RESET_JPEG | _RESET_DVP,
|
|
# _IMAGE_MODE, _IMAGE_MODE_JPEG_EN | _IMAGE_MODE_HREF_VSYNC,
|
|
# 0xD7, 0x03,
|
|
# 0xE1, 0x77,
|
|
# 0xE5, 0x1F,
|
|
# 0xD9, 0x10,
|
|
# 0xDF, 0x80,
|
|
# 0x33, 0x80,
|
|
# 0x3C, 0x10,
|
|
# 0xEB, 0x30,
|
|
# 0xDD, 0x7F,
|
|
# _RESET, 0x00,
|
|
# ])
|
|
|
|
_ov2640_settings_yuv422 = bytes(
|
|
[
|
|
_BANK_SEL,
|
|
_BANK_DSP,
|
|
_RESET,
|
|
_RESET_DVP,
|
|
_IMAGE_MODE,
|
|
_IMAGE_MODE_YUV422,
|
|
0xD7,
|
|
0x01,
|
|
0xE1,
|
|
0x67,
|
|
_RESET,
|
|
0x00,
|
|
]
|
|
)
|
|
|
|
_ov2640_settings_rgb565 = bytes(
|
|
[
|
|
_BANK_SEL,
|
|
_BANK_DSP,
|
|
_RESET,
|
|
_RESET_DVP,
|
|
_IMAGE_MODE,
|
|
_IMAGE_MODE_RGB565,
|
|
0xD7,
|
|
0x03,
|
|
0xE1,
|
|
0x77,
|
|
_RESET,
|
|
0x00,
|
|
]
|
|
)
|
|
|
|
|
|
class _RegBits:
|
|
def __init__(self, bank, reg, shift, mask):
|
|
self.bank = bank
|
|
self.reg = reg
|
|
self.shift = shift
|
|
self.mask = mask
|
|
|
|
def __get__(self, obj, objtype=None):
|
|
reg_value = obj._read_bank_register(self.bank, self.reg)
|
|
return (reg_value >> self.shift) & self.mask
|
|
|
|
def __set__(self, obj, value):
|
|
if value & ~self.mask:
|
|
raise ValueError(
|
|
f"Value 0x{value:02x} does not fit in mask 0x{self.mask:02x}"
|
|
)
|
|
reg_value = obj._read_bank_register(self.bank, self.reg)
|
|
reg_value &= ~(self.mask << self.shift)
|
|
reg_value |= value << self.shift
|
|
obj._write_register(self.reg, reg_value)
|
|
|
|
|
|
class _SCCBCameraBase: # pylint: disable=too-few-public-methods
|
|
def __init__(self, i2c_bus, i2c_address):
|
|
self._i2c_device = I2CDevice(i2c_bus, i2c_address)
|
|
self._bank = None
|
|
|
|
def _get_reg_bits(self, bank, reg, shift, mask):
|
|
return (self._read_bank_register(bank, reg) >> shift) & mask
|
|
|
|
def _set_reg_bits(
|
|
self, bank, reg, shift, mask, value
|
|
): # pylint: disable=too-many-arguments
|
|
reg_value = self._read_bank_register(bank, reg)
|
|
reg_value &= ~(mask << shift)
|
|
reg_value |= value << shift
|
|
self._write_register(reg, reg_value)
|
|
|
|
def _write_list(self, reg_list):
|
|
for i in range(0, len(reg_list), 2):
|
|
self._write_register(reg_list[i], reg_list[i + 1])
|
|
time.sleep(0.001)
|
|
|
|
def _write_bank_register(self, bank, reg, value):
|
|
if self._bank != bank:
|
|
self._write_register(_BANK_SEL, bank)
|
|
self._write_register(reg, value)
|
|
|
|
def _read_bank_register(self, bank, reg):
|
|
if self._bank != bank:
|
|
self._write_register(_BANK_SEL, bank)
|
|
result = self._read_register(reg)
|
|
return result
|
|
|
|
def _write_register(self, reg, value):
|
|
if reg == _BANK_SEL:
|
|
if self._bank == value:
|
|
return
|
|
self._bank = value
|
|
# print(f"write_register {reg:02x} {value:02x}")
|
|
b = bytearray(2)
|
|
b[0] = reg
|
|
b[1] = value
|
|
with self._i2c_device as i2c:
|
|
i2c.write(b)
|
|
|
|
def _read_register(self, reg):
|
|
b = bytearray(1)
|
|
b[0] = reg
|
|
with self._i2c_device as i2c:
|
|
i2c.write(b)
|
|
i2c.readinto(b)
|
|
return b[0]
|
|
|
|
|
|
class OV2640(_SCCBCameraBase): # pylint: disable=too-many-instance-attributes
|
|
"""Library for the OV2640 digital camera"""
|
|
|
|
test_pattern = _RegBits(_BANK_SENSOR, _COM7, 1, 1)
|
|
gain_ceiling = _RegBits(_BANK_SENSOR, _COM9, 5, 7)
|
|
bpc = _RegBits(_BANK_DSP, _CTRL3, 7, 1)
|
|
wpc = _RegBits(_BANK_DSP, _CTRL3, 6, 1)
|
|
lenc = _RegBits(_BANK_DSP, _CTRL1, 1, 1)
|
|
|
|
def __init__(
|
|
self,
|
|
i2c_bus,
|
|
data_pins,
|
|
clock,
|
|
vsync,
|
|
href,
|
|
shutdown=None,
|
|
reset=None,
|
|
mclk=None,
|
|
mclk_frequency=20_000_000,
|
|
i2c_address=0x30,
|
|
size=OV2640_SIZE_QQVGA,
|
|
): # pylint: disable=too-many-arguments
|
|
"""
|
|
Args:
|
|
i2c_bus (busio.I2C): The I2C bus used to configure the OV2640
|
|
data_pins (List[microcontroller.Pin]): A list of 8 data pins, in order.
|
|
clock (microcontroller.Pin): The pixel clock from the OV2640.
|
|
vsync (microcontroller.Pin): The vsync signal from the OV2640.
|
|
href (microcontroller.Pin): The href signal from the OV2640, \
|
|
sometimes inaccurately called hsync.
|
|
shutdown (Optional[microcontroller.Pin]): If not None, the shutdown
|
|
signal to the camera, also called the powerdown or enable pin.
|
|
reset (Optional[microcontroller.Pin]): If not None, the reset signal
|
|
to the camera.
|
|
mclk (Optional[microcontroller.Pin]): The pin on which to create a
|
|
master clock signal, or None if the master clock signal is
|
|
already being generated.
|
|
mclk_frequency (int): The frequency of the master clock to generate, \
|
|
ignored if mclk is None, requred if it is specified
|
|
i2c_address (int): The I2C address of the camera.
|
|
"""
|
|
|
|
# Initialize the master clock
|
|
if mclk:
|
|
self._mclk_pwm = pwmio.PWMOut(mclk, frequency=mclk_frequency)
|
|
self._mclk_pwm.duty_cycle = 32768
|
|
else:
|
|
self._mclk_pwm = None
|
|
|
|
if shutdown:
|
|
self._shutdown = digitalio.DigitalInOut(shutdown)
|
|
self._shutdown.switch_to_output(True)
|
|
time.sleep(0.1)
|
|
self._shutdown.switch_to_output(False)
|
|
time.sleep(0.3)
|
|
else:
|
|
self._shutdown = None
|
|
|
|
if reset:
|
|
self._reset = digitalio.DigitalInOut(reset)
|
|
self._reset.switch_to_output(False)
|
|
time.sleep(0.1)
|
|
self._reset.switch_to_output(True)
|
|
time.sleep(0.1)
|
|
|
|
super().__init__(i2c_bus, i2c_address)
|
|
|
|
self._write_bank_register(_BANK_SENSOR, _COM7, _COM7_SRST)
|
|
time.sleep(0.001)
|
|
|
|
self._write_list(_ov2640_settings_cif)
|
|
|
|
self._colorspace = OV2640_COLOR_RGB
|
|
self._w = None
|
|
self._h = None
|
|
self._size = None
|
|
self._test_pattern = False
|
|
self.size = size
|
|
|
|
self._flip_x = False
|
|
self._flip_y = False
|
|
|
|
self.gain_ceiling = _COM9_AGC_GAIN_2x
|
|
self.bpc = False
|
|
self.wpc = True
|
|
self.lenc = True
|
|
|
|
# self._sensor_init()
|
|
|
|
self._imagecapture = imagecapture.ParallelImageCapture(
|
|
data_pins=data_pins, clock=clock, vsync=vsync, href=href
|
|
)
|
|
|
|
def capture(self, buf):
|
|
"""Capture an image into the buffer.
|
|
|
|
Args:
|
|
buf (Union[bytearray, memoryview]): A WritableBuffer to contain the \
|
|
captured image. Note that this can be a ulab array or a displayio Bitmap.
|
|
"""
|
|
self._imagecapture.capture(buf)
|
|
|
|
@property
|
|
def mclk_frequency(self):
|
|
"""Get the actual frequency the generated mclk, or None"""
|
|
return self._mclk_pwm.frequency if self._mclk_pwm else None
|
|
|
|
@property
|
|
def width(self):
|
|
"""Get the image width in pixels. A buffer of 2*width*height bytes \
|
|
stores a whole image."""
|
|
return self._w
|
|
|
|
@property
|
|
def height(self):
|
|
"""Get the image height in pixels. A buffer of 2*width*height bytes \
|
|
stores a whole image."""
|
|
return self._h
|
|
|
|
@property
|
|
def colorspace(self):
|
|
"""Get or set the colorspace, one of the ``OV2640_COLOR_`` constants."""
|
|
return self._colorspace
|
|
|
|
@colorspace.setter
|
|
def colorspace(self, colorspace):
|
|
self._colorspace = colorspace
|
|
self._write_list(
|
|
_ov2640_settings_rgb565
|
|
if colorspace == OV2640_COLOR_RGB
|
|
else _ov2640_settings_yuv422
|
|
)
|
|
# written twice?
|
|
self._write_list(
|
|
_ov2640_settings_rgb565
|
|
if colorspace == OV2640_COLOR_RGB
|
|
else _ov2640_settings_yuv422
|
|
)
|
|
time.sleep(0.01)
|
|
|
|
def deinit(self):
|
|
"""Deinitialize the camera"""
|
|
self._imagecapture.deinit()
|
|
if self._mclk_pwm:
|
|
self._mclk_pwm.deinit()
|
|
if self._shutdown:
|
|
self._shutdown.deinit()
|
|
if self._reset:
|
|
self._reset.deinit()
|
|
|
|
@property
|
|
def size(self):
|
|
"""Get or set the captured image size, one of the ``OV2640_SIZE_`` constants."""
|
|
return self._size
|
|
|
|
@size.setter
|
|
def size(self, size):
|
|
width, height, ratio = _resolution_info[size]
|
|
offset_x, offset_y, max_x, max_y = _ratio_table[ratio]
|
|
mode = _OV2640_MODE_UXGA
|
|
if size <= OV2640_SIZE_CIF:
|
|
mode = _OV2640_MODE_CIF
|
|
max_x //= 4
|
|
max_y //= 4
|
|
offset_x //= 4
|
|
offset_y //= 4
|
|
if max_y > 296:
|
|
max_y = 296
|
|
|
|
elif size <= OV2640_SIZE_SVGA:
|
|
mode = _OV2640_MODE_SVGA
|
|
max_x //= 2
|
|
max_y //= 2
|
|
offset_x //= 2
|
|
offset_y //= 2
|
|
|
|
self._set_window(mode, offset_x, offset_y, max_x, max_y, width, height)
|
|
self._size = size
|
|
|
|
def _set_flip(self):
|
|
bits = 0
|
|
if self._flip_x:
|
|
bits |= _REG04_HFLIP_IMG
|
|
if self._flip_y:
|
|
bits |= _REG04_VFLIP_IMG | _REG04_VREF_EN
|
|
self._write_bank_register(_BANK_SENSOR, _REG04, _REG04_SET(bits))
|
|
|
|
@property
|
|
def flip_x(self):
|
|
"""Get or set the X-flip flag"""
|
|
return self._flip_x
|
|
|
|
@flip_x.setter
|
|
def flip_x(self, value):
|
|
self._flip_x = bool(value)
|
|
self._set_flip()
|
|
|
|
@property
|
|
def flip_y(self):
|
|
"""Get or set the Y-flip flag"""
|
|
return self._flip_y
|
|
|
|
@flip_y.setter
|
|
def flip_y(self, value):
|
|
self._flip_y = bool(value)
|
|
self._set_flip()
|
|
|
|
@property
|
|
def product_id(self):
|
|
"""Get the product id (PID) register. The expected value is 0x26."""
|
|
return self._read_bank_register(_BANK_SENSOR, _REG_PID)
|
|
|
|
@property
|
|
def product_version(self):
|
|
"""Get the version (VER) register. The expected value is 0x4x."""
|
|
return self._read_bank_register(_BANK_SENSOR, _REG_VER)
|
|
|
|
def _set_window(
|
|
self, mode, offset_x, offset_y, max_x, max_y, width, height
|
|
): # pylint: disable=too-many-arguments, too-many-locals
|
|
self._w = width
|
|
self._h = height
|
|
|
|
max_x //= 4
|
|
max_y //= 4
|
|
width //= 4
|
|
height //= 4
|
|
|
|
win_regs = [
|
|
_BANK_SEL,
|
|
_BANK_DSP,
|
|
_HSIZE,
|
|
max_x & 0xFF,
|
|
_VSIZE,
|
|
max_y & 0xFF,
|
|
_XOFFL,
|
|
offset_x & 0xFF,
|
|
_YOFFL,
|
|
offset_y & 0xFF,
|
|
_VHYX,
|
|
((max_y >> 1) & 0x80)
|
|
| ((offset_y >> 4) & 0x70)
|
|
| ((max_x >> 5) & 0x08)
|
|
| ((offset_y >> 8) & 0x07),
|
|
_TEST,
|
|
(max_x >> 2) & 0x80,
|
|
_ZMOW,
|
|
(width) & 0xFF,
|
|
_ZMOH,
|
|
(height) & 0xFF,
|
|
_ZMHH,
|
|
((height >> 6) & 0x04) | ((width >> 8) & 0x03),
|
|
]
|
|
|
|
pclk_auto = 1
|
|
pclk_div = 8
|
|
clk_2x = 0
|
|
clk_div = 7
|
|
|
|
if mode == _OV2640_MODE_CIF:
|
|
regs = _ov2640_settings_to_cif
|
|
# if pixformat is not jpeg:
|
|
clk_div = 3
|
|
elif mode == _OV2640_MODE_SVGA:
|
|
regs = _ov2640_settings_to_svga
|
|
else:
|
|
regs = _ov2640_settings_to_uxga
|
|
pclk_div = 12
|
|
|
|
clk = clk_div | (clk_2x << 7)
|
|
pclk = pclk_div | (pclk_auto << 7)
|
|
|
|
self._write_bank_register(_BANK_DSP, _R_BYPASS, _R_BYPASS_DSP_BYPAS)
|
|
self._write_list(regs)
|
|
self._write_list(win_regs)
|
|
self._write_bank_register(_BANK_SENSOR, _CLKRC, clk)
|
|
self._write_bank_register(_BANK_DSP, _R_DVP_SP, pclk)
|
|
self._write_register(_R_BYPASS, _R_BYPASS_DSP_EN)
|
|
time.sleep(0.01)
|
|
|
|
# Reestablish colorspace
|
|
self.colorspace = self._colorspace
|
|
|
|
# Reestablish test pattern
|
|
if self._test_pattern:
|
|
self.test_pattern = self._test_pattern
|