# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries # SPDX-FileCopyrightText: Copyright (c) 2021 Jeff Epler for Adafruit Industries # # SPDX-License-Identifier: MIT """ `adafruit_ov5640` ================================================================================ CircuitPython driver for OV5640 Camera * Author(s): Jeff Epler Implementation Notes -------------------- **Hardware:** * ESP32-S2 Kaluga Dev Kit featuring ESP32-S2 WROVER **Software and Dependencies:** * Adafruit CircuitPython firmware for the supported boards: https://github.com/adafruit/circuitpython/releases """ # pylint: disable=too-many-lines # imports import time import imagecapture import pwmio from adafruit_bus_device.i2c_device import I2CDevice try: from typing import Optional, Sequence, List, Union from busio import I2C from microcontroller import Pin from digitalio import DigitalInOut except ImportError: pass __version__ = "0.0.0+auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ov5640.git" from micropython import const OV5640_COLOR_RGB = 0 OV5640_COLOR_YUV = 1 OV5640_COLOR_GRAYSCALE = 2 OV5640_COLOR_JPEG = 3 # fmt: off _SYSTEM_RESET00 = const(0x3000) # Reset for Individual Block # (0: enable block; 1: reset block) # Bit[7]: Reset BIST # Bit[6]: Reset MCU program memory # Bit[5]: Reset MCU # Bit[4]: Reset OTP # Bit[3]: Reset STB # Bit[2]: Reset d5060 # Bit[1]: Reset timing control # Bit[0]: Reset array control _SYSTEM_RESET02 = const(0x3002) # Reset for Individual Block # (0: enable block; 1: reset block) # Bit[7]: Reset VFIFO # Bit[5]: Reset format # Bit[4]: Reset JFIFO # Bit[3]: Reset SFIFO # Bit[2]: Reset JPG # Bit[1]: Reset format MUX # Bit[0]: Reset average _CLOCK_ENABLE02 = const(0x3006) # Clock Enable Control # (0: disable clock; 1: enable clock) # Bit[7]: Enable PSRAM clock # Bit[6]: Enable FMT clock # Bit[5]: Enable JPEG 2x clock # Bit[3]: Enable JPEG clock # Bit[1]: Enable format MUX clock # Bit[0]: Enable average clock _SYSTEM_CTROL0 = const(0x3008) # Bit[7]: Software reset # Bit[6]: Software power down # Bit[5]: Reserved # Bit[4]: SRB clock SYNC enable # Bit[3]: Isolation suspend select # Bit[2:0]: Not used _CHIP_ID_HIGH = const(0x300A) _DRIVE_CAPABILITY = const(0x302C) # Bit[7:6]: # 00: 1x # 01: 2x # 10: 3x # 11: 4x _SC_PLLS_CTRL0 = const(0x303A) # Bit[7]: PLLS bypass _SC_PLLS_CTRL1 = const(0x303B) # Bit[4:0]: PLLS multiplier _SC_PLLS_CTRL2 = const(0x303C) # Bit[6:4]: PLLS charge pump control # Bit[3:0]: PLLS system divider _SC_PLLS_CTRL3 = const(0x303D) # Bit[5:4]: PLLS pre-divider # 00: 1 # 01: 1.5 # 10: 2 # 11: 3 # Bit[2]: PLLS root-divider - 1 # Bit[1:0]: PLLS seld5 # 00: 1 # 01: 1 # 10: 2 # 11: 2.5 # AEC/AGC control functions _AEC_PK_MANUAL = const(0x3503) # AEC Manual Mode Control # Bit[7:6]: Reserved # Bit[5]: Gain delay option # Valid when 0x3503[4]=1’b0 # 0: Delay one frame latch # 1: One frame latch # Bit[4:2]: Reserved # Bit[1]: AGC manual # 0: Auto enable # 1: Manual enable # Bit[0]: AEC manual # 0: Auto enable # 1: Manual enable # gain = {0x350A[1:0], 0x350B[7:0]} / 16 _X_ADDR_ST_H = const(0x3800) # Bit[3:0]: X address start[11:8] _X_ADDR_ST_L = const(0x3801) # Bit[7:0]: X address start[7:0] _Y_ADDR_ST_H = const(0x3802) # Bit[2:0]: Y address start[10:8] _Y_ADDR_ST_L = const(0x3803) # Bit[7:0]: Y address start[7:0] _X_ADDR_END_H = const(0x3804) # Bit[3:0]: X address end[11:8] _X_ADDR_END_L = const(0x3805) # Bit[7:0]: _Y_ADDR_END_H = const(0x3806) # Bit[2:0]: Y address end[10:8] _Y_ADDR_END_L = const(0x3807) # Bit[7:0]: # Size after scaling _X_OUTPUT_SIZE_H = const(0x3808) # Bit[3:0]: DVP output horizontal width[11:8] _X_OUTPUT_SIZE_L = const(0x3809) # Bit[7:0]: _Y_OUTPUT_SIZE_H = const(0x380A) # Bit[2:0]: DVP output vertical height[10:8] _Y_OUTPUT_SIZE_L = const(0x380B) # Bit[7:0]: _X_TOTAL_SIZE_H = const(0x380C) # Bit[3:0]: Total horizontal size[11:8] _X_TOTAL_SIZE_L = const(0x380D) # Bit[7:0]: _Y_TOTAL_SIZE_H = const(0x380E) # Bit[7:0]: Total vertical size[15:8] _Y_TOTAL_SIZE_L = const(0x380F) # Bit[7:0]: _X_OFFSET_H = const(0x3810) # Bit[3:0]: ISP horizontal offset[11:8] _X_OFFSET_L = const(0x3811) # Bit[7:0]: _Y_OFFSET_H = const(0x3812) # Bit[2:0]: ISP vertical offset[10:8] _Y_OFFSET_L = const(0x3813) # Bit[7:0]: _X_INCREMENT = const(0x3814) # Bit[7:4]: Horizontal odd subsample increment # Bit[3:0]: Horizontal even subsample increment _Y_INCREMENT = const(0x3815) # Bit[7:4]: Vertical odd subsample increment # Bit[3:0]: Vertical even subsample increment # Size before scaling # X_INPUT_SIZE = const( (X_ADDR_END - X_ADDR_ST + 1 - (2 * X_OFFSET))) # Y_INPUT_SIZE = const( (Y_ADDR_END - Y_ADDR_ST + 1 - (2 * Y_OFFSET))) # mirror and flip registers _TIMING_TC_REG20 = const(0x3820) # Timing Control Register # Bit[2:1]: Vertical flip enable # 00: Normal # 11: Vertical flip # Bit[0]: Vertical binning enable _TIMING_TC_REG21 = const(0x3821) # Timing Control Register # Bit[5]: Compression Enable # Bit[2:1]: Horizontal mirror enable # 00: Normal # 11: Horizontal mirror # Bit[0]: Horizontal binning enable _PCLK_RATIO = const(0x3824) # Bit[4:0]: PCLK ratio manual # frame control registers _FRAME_CTRL01 = const( 0x4201 ) # Control Passed Frame Number When both ON and OFF number set to 0x00,frame # control is in bypass mode # Bit[7:4]: Not used # Bit[3:0]: Frame ON number _FRAME_CTRL02 = const( 0x4202 ) # Control Masked Frame Number When both ON and OFF number set to 0x00,frame # control is in bypass mode # Bit[7:4]: Not used # BIT[3:0]: Frame OFF number # format control registers _FORMAT_CTRL00 = const(0x4300) _CLOCK_POL_CONTROL = const(0x4740) # Bit[5]: PCLK polarity 0: active low # 1: active high # Bit[3]: Gate PCLK under VSYNC # Bit[2]: Gate PCLK under HREF # Bit[1]: HREF polarity # 0: active low # 1: active high # Bit[0] VSYNC polarity # 0: active low # 1: active high _ISP_CONTROL_01 = const(0x5001) # Bit[5]: Scale enable # 0: Disable # 1: Enable # output format control registers _FORMAT_CTRL = const(0x501F) # Format select # Bit[2:0]: # 000: YUV422 # 001: RGB # 010: Dither # 011: RAW after DPC # 101: RAW after CIP # ISP top control registers _PRE_ISP_TEST_SETTING_1 = const(0x503D) # Bit[7]: Test enable # 0: Test disable # 1: Color bar enable # Bit[6]: Rolling # Bit[5]: Transparent # Bit[4]: Square black and white # Bit[3:2]: Color bar style # 00: Standard 8 color bar # 01: Gradual change at vertical mode 1 # 10: Gradual change at horizontal # 11: Gradual change at vertical mode 2 # Bit[1:0]: Test select # 00: Color bar # 01: Random data # 10: Square data # 11: Black image # exposure = {0x3500[3:0], 0x3501[7:0], 0x3502[7:0]} / 16 × tROW _SCALE_CTRL_1 = const(0x5601) # Bit[6:4]: HDIV RW # DCW scale times # 000: DCW 1 time # 001: DCW 2 times # 010: DCW 4 times # 100: DCW 8 times # 101: DCW 16 times # Others: DCW 16 times # Bit[2:0]: VDIV RW # DCW scale times # 000: DCW 1 time # 001: DCW 2 times # 010: DCW 4 times # 100: DCW 8 times # 101: DCW 16 times # Others: DCW 16 times _SCALE_CTRL_2 = const(0x5602) # X_SCALE High Bits _SCALE_CTRL_3 = const(0x5603) # X_SCALE Low Bits _SCALE_CTRL_4 = const(0x5604) # Y_SCALE High Bits _SCALE_CTRL_5 = const(0x5605) # Y_SCALE Low Bits _SCALE_CTRL_6 = const(0x5606) # Bit[3:0]: V Offset _VFIFO_CTRL0C = const(0x460C) # Bit[1]: PCLK manual enable # 0: Auto # 1: Manual by PCLK_RATIO _VFIFO_X_SIZE_H = const(0x4602) _VFIFO_X_SIZE_L = const(0x4603) _VFIFO_Y_SIZE_H = const(0x4604) _VFIFO_Y_SIZE_L = const(0x4605) _COMPRESSION_CTRL00 = const(0x4400) _COMPRESSION_CTRL01 = const(0x4401) _COMPRESSION_CTRL02 = const(0x4402) _COMPRESSION_CTRL03 = const(0x4403) _COMPRESSION_CTRL04 = const(0x4404) _COMPRESSION_CTRL05 = const(0x4405) _COMPRESSION_CTRL06 = const(0x4406) _COMPRESSION_CTRL07 = const(0x4407) # Bit[5:0]: QS _COMPRESSION_ISI_CTRL = const(0x4408) _COMPRESSION_CTRL09 = const(0x4409) _COMPRESSION_CTRL0A = const(0x440A) _COMPRESSION_CTRL0B = const(0x440B) _COMPRESSION_CTRL0C = const(0x440C) _COMPRESSION_CTRL0D = const(0x440D) _COMPRESSION_CTRL0E = const(0x440E) _TEST_COLOR_BAR = const(0xC0) # Enable Color Bar roling Test _AEC_PK_MANUAL_AGC_MANUALEN = const(0x02) # Enable AGC Manual enable _AEC_PK_MANUAL_AEC_MANUALEN = const(0x01) # Enable AEC Manual enable _TIMING_TC_REG20_VFLIP = const(0x06) # Vertical flip enable _TIMING_TC_REG21_HMIRROR = const(0x06) # Horizontal mirror enable OV5640_SIZE_96X96 = 0 # 96x96 OV5640_SIZE_QQVGA = 1 # 160x120 OV5640_SIZE_QCIF = 2 # 176x144 OV5640_SIZE_HQVGA = 3 # 240x176 OV5640_SIZE_240X240 = 4 # 240x240 OV5640_SIZE_QVGA = 5 # 320x240 OV5640_SIZE_CIF = 6 # 400x296 OV5640_SIZE_HVGA = 7 # 480x320 OV5640_SIZE_VGA = 8 # 640x480 OV5640_SIZE_SVGA = 9 # 800x600 OV5640_SIZE_XGA = 10 # 1024x768 OV5640_SIZE_HD = 11 # 1280x720 OV5640_SIZE_SXGA = 12 # 1280x1024 OV5640_SIZE_UXGA = 13 # 1600x1200 OV5640_SIZE_QHDA = 14 # 2560x1440 OV5640_SIZE_WQXGA = 15 # 2560x1600 OV5640_SIZE_PFHD = 16 # 1088x1920 OV5640_SIZE_QSXGA = 17 # 2560x1920 _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 [2560, 1440, _ASPECT_RATIO_16X9], # QHD [2560, 1600, _ASPECT_RATIO_16X10], # WQXGA [1088, 1920, _ASPECT_RATIO_9X16], # Portrait FHD [2560, 1920, _ASPECT_RATIO_4X3], # QSXGA ] _ratio_table = [ # mw, mh, sx, sy, ex, ey, ox, oy, tx, ty [2560, 1920, 0, 0, 2623, 1951, 32, 16, 2844, 1968], # 4x3 [2560, 1704, 0, 110, 2623, 1843, 32, 16, 2844, 1752], # 3x2 [2560, 1600, 0, 160, 2623, 1791, 32, 16, 2844, 1648], # 16x10 [2560, 1536, 0, 192, 2623, 1759, 32, 16, 2844, 1584], # 5x3 [2560, 1440, 0, 240, 2623, 1711, 32, 16, 2844, 1488], # 16x9 [2560, 1080, 0, 420, 2623, 1531, 32, 16, 2844, 1128], # 21x9 [2400, 1920, 80, 0, 2543, 1951, 32, 16, 2684, 1968], # 5x4 [1920, 1920, 320, 0, 2543, 1951, 32, 16, 2684, 1968], # 1x1 [1088, 1920, 736, 0, 1887, 1951, 32, 16, 1884, 1968], # 9x16 ] _pll_pre_div2x_factors = [1, 1, 2, 3, 4, 1.5, 6, 2.5, 8] _pll_pclk_root_div_factors = [1,2,4,8] _REG_DLY = const(0xFFFF) _REGLIST_TAIL = const(0x0000) _sensor_default_regs = [ _SYSTEM_CTROL0, 0x82, # software reset _REG_DLY, 10, # delay 10ms _SYSTEM_CTROL0, 0x42, # power down # enable pll 0x3103, 0x13, # io direction 0x3017, 0xFF, 0x3018, 0xFF, _DRIVE_CAPABILITY, 0xC3, _CLOCK_POL_CONTROL, 0x21, 0x4713, 0x02, # jpg mode select _ISP_CONTROL_01, 0x83, # turn color matrix, awb and SDE # sys reset _SYSTEM_RESET00, 0x00, # enable all blocks _SYSTEM_RESET02, 0x1C, # reset jfifo, sfifo, jpg, fmux, avg # clock enable 0x3004, 0xFF, _CLOCK_ENABLE02, 0xC3, # isp control 0x5000, 0xA7, _ISP_CONTROL_01, 0xA3, # +scaling? 0x5003, 0x08, # special_effect # unknown 0x370C, 0x02, #!!IMPORTANT 0x3634, 0x40, #!!IMPORTANT # AEC/AGC 0x3A02, 0x03, 0x3A03, 0xD8, 0x3A08, 0x01, 0x3A09, 0x27, 0x3A0A, 0x00, 0x3A0B, 0xF6, 0x3A0D, 0x04, 0x3A0E, 0x03, 0x3A0F, 0x30, # ae_level 0x3A10, 0x28, # ae_level 0x3A11, 0x60, # ae_level 0x3A13, 0x43, 0x3A14, 0x03, 0x3A15, 0xD8, 0x3A18, 0x00, # gainceiling 0x3A19, 0xF8, # gainceiling 0x3A1B, 0x30, # ae_level 0x3A1E, 0x26, # ae_level 0x3A1F, 0x14, # ae_level # vcm debug 0x3600, 0x08, 0x3601, 0x33, # 50/60Hz 0x3C01, 0xA4, 0x3C04, 0x28, 0x3C05, 0x98, 0x3C06, 0x00, 0x3C07, 0x08, 0x3C08, 0x00, 0x3C09, 0x1C, 0x3C0A, 0x9C, 0x3C0B, 0x40, 0x460C, 0x22, # disable jpeg footer # BLC 0x4001, 0x02, 0x4004, 0x02, # AWB 0x5180, 0xFF, 0x5181, 0xF2, 0x5182, 0x00, 0x5183, 0x14, 0x5184, 0x25, 0x5185, 0x24, 0x5186, 0x09, 0x5187, 0x09, 0x5188, 0x09, 0x5189, 0x75, 0x518A, 0x54, 0x518B, 0xE0, 0x518C, 0xB2, 0x518D, 0x42, 0x518E, 0x3D, 0x518F, 0x56, 0x5190, 0x46, 0x5191, 0xF8, 0x5192, 0x04, 0x5193, 0x70, 0x5194, 0xF0, 0x5195, 0xF0, 0x5196, 0x03, 0x5197, 0x01, 0x5198, 0x04, 0x5199, 0x12, 0x519A, 0x04, 0x519B, 0x00, 0x519C, 0x06, 0x519D, 0x82, 0x519E, 0x38, # color matrix (Saturation) 0x5381, 0x1E, 0x5382, 0x5B, 0x5383, 0x08, 0x5384, 0x0A, 0x5385, 0x7E, 0x5386, 0x88, 0x5387, 0x7C, 0x5388, 0x6C, 0x5389, 0x10, 0x538A, 0x01, 0x538B, 0x98, # CIP control (Sharpness) 0x5300, 0x10, # sharpness 0x5301, 0x10, # sharpness 0x5302, 0x18, # sharpness 0x5303, 0x19, # sharpness 0x5304, 0x10, 0x5305, 0x10, 0x5306, 0x08, # denoise 0x5307, 0x16, 0x5308, 0x40, 0x5309, 0x10, # sharpness 0x530A, 0x10, # sharpness 0x530B, 0x04, # sharpness 0x530C, 0x06, # sharpness # GAMMA 0x5480, 0x01, 0x5481, 0x00, 0x5482, 0x1E, 0x5483, 0x3B, 0x5484, 0x58, 0x5485, 0x66, 0x5486, 0x71, 0x5487, 0x7D, 0x5488, 0x83, 0x5489, 0x8F, 0x548A, 0x98, 0x548B, 0xA6, 0x548C, 0xB8, 0x548D, 0xCA, 0x548E, 0xD7, 0x548F, 0xE3, 0x5490, 0x1D, # Special Digital Effects (SDE) (UV adjust) 0x5580, 0x06, # enable brightness and contrast 0x5583, 0x40, # special_effect 0x5584, 0x10, # special_effect 0x5586, 0x20, # contrast 0x5587, 0x00, # brightness 0x5588, 0x00, # brightness 0x5589, 0x10, 0x558A, 0x00, 0x558B, 0xF8, 0x501D, 0x40, # enable manual offset of contrast # power on 0x3008, 0x02, # 50Hz 0x3C00, 0x04, #_REG_DLY, 300, ] _reset_awb = [ _ISP_CONTROL_01, 0x83, # turn color matrix, awb and SDE # sys reset _SYSTEM_RESET00, 0x00, # enable all blocks _SYSTEM_RESET02, 0x1C, # reset jfifo, sfifo, jpg, fmux, avg # clock enable #0x3004, 0xFF, #_CLOCK_ENABLE02, 0xC3, # isp control 0x5000, 0xA7, _ISP_CONTROL_01, 0xA3, # +scaling? 0x5003, 0x08, # special_effect # unknown 0x370C, 0x02, #!!IMPORTANT 0x3634, 0x40, #!!IMPORTANT # AEC/AGC 0x3A02, 0x03, 0x3A03, 0xD8, 0x3A08, 0x01, 0x3A09, 0x27, 0x3A0A, 0x00, 0x3A0B, 0xF6, 0x3A0D, 0x04, 0x3A0E, 0x03, 0x3A0F, 0x30, # ae_level 0x3A10, 0x28, # ae_level 0x3A11, 0x60, # ae_level 0x3A13, 0x43, 0x3A14, 0x03, 0x3A15, 0xD8, 0x3A18, 0x00, # gainceiling 0x3A19, 0xF8, # gainceiling 0x3A1B, 0x30, # ae_level 0x3A1E, 0x26, # ae_level 0x3A1F, 0x14, # ae_level # vcm debug 0x3600, 0x08, 0x3601, 0x33, # 50/60Hz 0x3C01, 0xA4, 0x3C04, 0x28, 0x3C05, 0x98, 0x3C06, 0x00, 0x3C07, 0x08, 0x3C08, 0x00, 0x3C09, 0x1C, 0x3C0A, 0x9C, 0x3C0B, 0x40, 0x460C, 0x22, # disable jpeg footer # BLC 0x4001, 0x02, 0x4004, 0x02, # AWB 0x5180, 0xFF, 0x5181, 0xF2, 0x5182, 0x00, 0x5183, 0x14, 0x5184, 0x25, 0x5185, 0x24, 0x5186, 0x09, 0x5187, 0x09, 0x5188, 0x09, 0x5189, 0x75, 0x518A, 0x54, 0x518B, 0xE0, 0x518C, 0xB2, 0x518D, 0x42, 0x518E, 0x3D, 0x518F, 0x56, 0x5190, 0x46, 0x5191, 0xF8, 0x5192, 0x04, 0x5193, 0x70, 0x5194, 0xF0, 0x5195, 0xF0, 0x5196, 0x03, 0x5197, 0x01, 0x5198, 0x04, 0x5199, 0x12, 0x519A, 0x04, 0x519B, 0x00, 0x519C, 0x06, 0x519D, 0x82, 0x519E, 0x38, # color matrix (Saturation) 0x5381, 0x1E, 0x5382, 0x5B, 0x5383, 0x08, 0x5384, 0x0A, 0x5385, 0x7E, 0x5386, 0x88, 0x5387, 0x7C, 0x5388, 0x6C, 0x5389, 0x10, 0x538A, 0x01, 0x538B, 0x98, # CIP control (Sharpness) 0x5300, 0x10, # sharpness 0x5301, 0x10, # sharpness 0x5302, 0x18, # sharpness 0x5303, 0x19, # sharpness 0x5304, 0x10, 0x5305, 0x10, 0x5306, 0x08, # denoise 0x5307, 0x16, 0x5308, 0x40, 0x5309, 0x10, # sharpness 0x530A, 0x10, # sharpness 0x530B, 0x04, # sharpness 0x530C, 0x06, # sharpness # GAMMA 0x5480, 0x01, 0x5481, 0x00, 0x5482, 0x1E, 0x5483, 0x3B, 0x5484, 0x58, 0x5485, 0x66, 0x5486, 0x71, 0x5487, 0x7D, 0x5488, 0x83, 0x5489, 0x8F, 0x548A, 0x98, 0x548B, 0xA6, 0x548C, 0xB8, 0x548D, 0xCA, 0x548E, 0xD7, 0x548F, 0xE3, 0x5490, 0x1D, # Special Digital Effects (SDE) (UV adjust) 0x5580, 0x06, # enable brightness and contrast 0x5583, 0x40, # special_effect 0x5584, 0x10, # special_effect 0x5586, 0x20, # contrast 0x5587, 0x00, # brightness 0x5588, 0x00, # brightness 0x5589, 0x10, 0x558A, 0x00, 0x558B, 0xF8, 0x501D, 0x40, # enable manual offset of contrast ] _sensor_format_jpeg = [ _FORMAT_CTRL, 0x00, # YUV422 _FORMAT_CTRL00, 0x30, # YUYV _SYSTEM_RESET02, 0x00, # enable everything _CLOCK_ENABLE02, 0xFF, # enable all clocks 0x471C, 0x50, # 0xd0 to 0x50 !!! ] _sensor_format_raw = [ _FORMAT_CTRL, 0x03, # RAW (DPC) _FORMAT_CTRL00, 0x00, # RAW ] _sensor_format_grayscale = [ _FORMAT_CTRL, 0x00, # YUV422 _FORMAT_CTRL00, 0x10, # Y8 ] _sensor_format_yuv422 = [ _FORMAT_CTRL, 0x00, # YUV422 _FORMAT_CTRL00, 0x30, # YUYV ] _sensor_format_rgb565 = [ _FORMAT_CTRL, 0x01, # RGB _FORMAT_CTRL00, 0x61, # RGB565 (BGR) _SYSTEM_RESET02, 0x1C, # reset jfifo, sfifo, jpg, fmux, avg _CLOCK_ENABLE02, 0xC3, # reset to how it was before (no jpg clock) ] _ov5640_color_settings = { OV5640_COLOR_RGB: _sensor_format_rgb565, OV5640_COLOR_YUV: _sensor_format_yuv422, OV5640_COLOR_GRAYSCALE: _sensor_format_grayscale, OV5640_COLOR_JPEG: _sensor_format_jpeg, } _contrast_settings = [ [0x20, 0x00], # 0 [0x24, 0x10], # +1 [0x28, 0x18], # +2 [0x2c, 0x1c], # +3 [0x14, 0x14], # -3 [0x18, 0x18], # -2 [0x1c, 0x1c], # -1 ] _sensor_saturation_levels = [ [0x1D, 0x60, 0x03, 0x0C, 0x78, 0x84, 0x7D, 0x6B, 0x12, 0x01, 0x98], # 0 [0x1D, 0x60, 0x03, 0x0D, 0x84, 0x91, 0x8A, 0x76, 0x14, 0x01, 0x98], # +1 [0x1D, 0x60, 0x03, 0x0E, 0x90, 0x9E, 0x96, 0x80, 0x16, 0x01, 0x98], # +2 [0x1D, 0x60, 0x03, 0x10, 0x9C, 0xAC, 0xA2, 0x8B, 0x17, 0x01, 0x98], # +3 [0x1D, 0x60, 0x03, 0x11, 0xA8, 0xB9, 0xAF, 0x96, 0x19, 0x01, 0x98], # +4 [0x1D, 0x60, 0x03, 0x07, 0x48, 0x4F, 0x4B, 0x40, 0x0B, 0x01, 0x98], # -4 [0x1D, 0x60, 0x03, 0x08, 0x54, 0x5C, 0x58, 0x4B, 0x0D, 0x01, 0x98], # -3 [0x1D, 0x60, 0x03, 0x0A, 0x60, 0x6A, 0x64, 0x56, 0x0E, 0x01, 0x98], # -2 [0x1D, 0x60, 0x03, 0x0B, 0x6C, 0x77, 0x70, 0x60, 0x10, 0x01, 0x98], # -1 ] _sensor_ev_levels = [ [0x38, 0x30, 0x61, 0x38, 0x30, 0x10], # 0 [0x40, 0x38, 0x71, 0x40, 0x38, 0x10], # +1 [0x50, 0x48, 0x90, 0x50, 0x48, 0x20], # +2 [0x60, 0x58, 0xa0, 0x60, 0x58, 0x20], # +3 [0x10, 0x08, 0x10, 0x08, 0x20, 0x10], # -3 [0x20, 0x18, 0x41, 0x20, 0x18, 0x10], # -2 [0x30, 0x28, 0x61, 0x30, 0x28, 0x10], # -1 ] OV5640_WHITE_BALANCE_AUTO = 0 OV5640_WHITE_BALANCE_SUNNY = 1 OV5640_WHITE_BALANCE_FLUORESCENT = 2 OV5640_WHITE_BALANCE_CLOUDY = 3 OV5640_WHITE_BALANCE_INCANDESCENT = 4 _light_registers = [0x3406, 0x3400, 0x3401, 0x3402, 0x3403, 0x3404, 0x3405] _light_modes = [ [0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00], # auto [0x01, 0x06, 0x1c, 0x04, 0x00, 0x04, 0xf3], # sunny [0x01, 0x05, 0x48, 0x04, 0x00, 0x07, 0xcf], # office / fluorescent [0x01, 0x06, 0x48, 0x04, 0x00, 0x04, 0xd3], # cloudy [0x01, 0x04, 0x10, 0x04, 0x00, 0x08, 0x40], # home / incandescent ] OV5640_SPECIAL_EFFECT_NONE = 0 OV5640_SPECIAL_EFFECT_NEGATIVE = 1 OV5640_SPECIAL_EFFECT_GRAYSCALE = 2 OV5640_SPECIAL_EFFECT_RED_TINT = 3 OV5640_SPECIAL_EFFECT_GREEN_TINT = 4 OV5640_SPECIAL_EFFECT_BLUE_TINT = 5 OV5640_SPECIAL_EFFECT_SEPIA = 6 _sensor_special_effects = [ [0x06, 0x40, 0x10, 0x08], # Normal [0x46, 0x40, 0x28, 0x08], # Negative [0x1E, 0x80, 0x80, 0x08], # Grayscale [0x1E, 0x80, 0xC0, 0x08], # Red Tint [0x1E, 0x60, 0x60, 0x08], # Green Tint [0x1E, 0xA0, 0x40, 0x08], # Blue Tint [0x1E, 0x40, 0xA0, 0x08], # Sepia ] _sensor_regs_gamma0 = [ 0x5480, 0x01, 0x5481, 0x08, 0x5482, 0x14, 0x5483, 0x28, 0x5484, 0x51, 0x5485, 0x65, 0x5486, 0x71, 0x5487, 0x7D, 0x5488, 0x87, 0x5489, 0x91, 0x548A, 0x9A, 0x548B, 0xAA, 0x548C, 0xB8, 0x548D, 0xCD, 0x548E, 0xDD, 0x548F, 0xEA, 0x5490, 0x1D, ] sensor_regs_gamma1 = [ 0x5480, 0x1, 0x5481, 0x0, 0x5482, 0x1E, 0x5483, 0x3B, 0x5484, 0x58, 0x5485, 0x66, 0x5486, 0x71, 0x5487, 0x7D, 0x5488, 0x83, 0x5489, 0x8F, 0x548A, 0x98, 0x548B, 0xA6, 0x548C, 0xB8, 0x548D, 0xCA, 0x548E, 0xD7, 0x548F, 0xE3, 0x5490, 0x1D, ] sensor_regs_awb0 = [ 0x5180, 0xFF, 0x5181, 0xF2, 0x5182, 0x00, 0x5183, 0x14, 0x5184, 0x25, 0x5185, 0x24, 0x5186, 0x09, 0x5187, 0x09, 0x5188, 0x09, 0x5189, 0x75, 0x518A, 0x54, 0x518B, 0xE0, 0x518C, 0xB2, 0x518D, 0x42, 0x518E, 0x3D, 0x518F, 0x56, 0x5190, 0x46, 0x5191, 0xF8, 0x5192, 0x04, 0x5193, 0x70, 0x5194, 0xF0, 0x5195, 0xF0, 0x5196, 0x03, 0x5197, 0x01, 0x5198, 0x04, 0x5199, 0x12, 0x519A, 0x04, 0x519B, 0x00, 0x519C, 0x06, 0x519D, 0x82, 0x519E, 0x38, ] # fmt: on class _RegBits: def __init__(self, reg: int, shift: int, mask: int) -> None: self.reg = reg self.shift = shift self.mask = mask def __get__(self, obj: "OV5640", objtype: Optional[type] = None) -> int: reg_value = obj._read_register(self.reg) return (reg_value >> self.shift) & self.mask def __set__(self, obj: "OV5640", value: int) -> None: if value & ~self.mask: raise ValueError( f"Value 0x{value:02x} does not fit in mask 0x{self.mask:02x}" ) reg_value = obj._read_register(self.reg) reg_value &= ~(self.mask << self.shift) reg_value |= value << self.shift obj._write_register(self.reg, reg_value) class _RegBits16: def __init__(self, reg: int, shift: int, mask: int) -> None: self.reg = reg self.shift = shift self.mask = mask def __get__(self, obj: "OV5640", objtype: Optional[type] = None) -> int: reg_value = obj._read_register16(self.reg) return (reg_value >> self.shift) & self.mask def __set__(self, obj: "OV5640", value: int) -> None: if value & ~self.mask: raise ValueError( f"Value 0x{value:02x} does not fit in mask 0x{self.mask:02x}" ) reg_value = obj._read_register16(self.reg) reg_value &= ~(self.mask << self.shift) reg_value |= value << self.shift obj._write_register16(self.reg, reg_value) class _SCCB16CameraBase: # pylint: disable=too-few-public-methods def __init__(self, i2c_bus: I2C, i2c_address: int) -> None: self._i2c_device = I2CDevice(i2c_bus, i2c_address) self._bank = None def _write_register(self, reg: int, value: int) -> None: b = bytearray(3) b[0] = reg >> 8 b[1] = reg & 0xFF b[2] = value with self._i2c_device as i2c: i2c.write(b) def _write_addr_reg(self, reg: int, x_value: int, y_value: int) -> None: self._write_register16(reg, x_value) self._write_register16(reg + 2, y_value) def _write_register16(self, reg: int, value: int) -> None: self._write_register(reg, value >> 8) self._write_register(reg + 1, value & 0xFF) def _read_register(self, reg: int) -> int: b = bytearray(2) b[0] = reg >> 8 b[1] = reg & 0xFF with self._i2c_device as i2c: i2c.write(b) i2c.readinto(b, end=1) return b[0] def _read_register16(self, reg: int) -> int: high = self._read_register(reg) low = self._read_register(reg + 1) return (high << 8) | low def _write_list(self, reg_list: Sequence[int]) -> None: for i in range(0, len(reg_list), 2): register = reg_list[i] value = reg_list[i + 1] if register == _REG_DLY: time.sleep(value / 1000) else: self._write_register(register, value) def _write_reg_bits(self, reg: int, mask: int, enable: bool) -> None: val = val = self._read_register(reg) if enable: val |= mask else: val &= ~mask self._write_register(reg, val) class OV5640(_SCCB16CameraBase): # pylint: disable=too-many-instance-attributes """Control & Capture Images from an OV5640 Camera""" def __init__( self, i2c_bus: I2C, data_pins: List[Pin], clock: Pin, vsync: Pin, href: Pin, shutdown: Optional[DigitalInOut] = None, reset: Optional[DigitalInOut] = None, mclk: Optional[Pin] = None, mclk_frequency: int = 20_000_000, i2c_address: int = 0x3C, size: int = OV5640_SIZE_QQVGA, ): # pylint: disable=too-many-arguments """ Args: i2c_bus (busio.I2C): The I2C bus used to configure the OV5640 data_pins (List[microcontroller.Pin]): A list of 8 data pins, in order. clock (microcontroller.Pin): The pixel clock from the OV5640. vsync (microcontroller.Pin): The vsync signal from the OV5640. href (microcontroller.Pin): The href signal from the OV5640, \ sometimes inaccurately called hsync. shutdown (Optional[digitalio.DigitalInOut]): If not None, the shutdown signal to the camera, also called the powerdown or enable pin. reset (Optional[digitalio.DigitalInOut]): 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. Note that the OV5640 requires a very low jitter clock, so only specific (microcontroller-dependent) values may work reliably. On the ESP32-S2, a 20MHz clock can be generated with sufficiently low jitter. i2c_address (int): The I2C address of the camera. size (int): The captured image size """ # 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 reset: self._reset = reset self._reset.switch_to_output(False) else: self._reset = None if shutdown: self._shutdown = shutdown self._shutdown.switch_to_output(True) time.sleep(0.005) # t2, 5ms stability self._shutdown.switch_to_output(False) else: self._shutdown = None if self._reset: time.sleep(0.001) # t3, 1ms delay from pwdn self._reset.switch_to_output(True) time.sleep(0.02) # Now that the master clock is running, we can initialize i2c comms super().__init__(i2c_bus, i2c_address) self._write_list(_sensor_default_regs) self._imagecapture = imagecapture.ParallelImageCapture( data_pins=data_pins, clock=clock, vsync=vsync, href=href ) self._colorspace = OV5640_COLOR_RGB self._flip_x = False self._flip_y = False self._w = None self._h = None self._size = None self._test_pattern = False self._binning = False self._scale = False self._ev = 0 self._white_balance = 0 self.size = size chip_id = _RegBits16(_CHIP_ID_HIGH, 0, 0xFFFF) def capture(self, buf: Union[bytearray, memoryview]) -> None: """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) if self.colorspace == OV5640_COLOR_JPEG: eoi = buf.find(b"\xff\xd9") if eoi != -1: # terminate the JPEG data just after the EOI marker return memoryview(buf)[: eoi + 2] return None @property def capture_buffer_size(self) -> int: """Return the size of capture buffer to use with current resolution & colorspace settings""" if self.colorspace == OV5640_COLOR_JPEG: return self.width * self.height // self.quality if self.colorspace == OV5640_COLOR_GRAYSCALE: return self.width * self.height return self.width * self.height * 2 @property def mclk_frequency(self) -> Optional[int]: """Get the actual frequency the generated mclk, or None""" return self._mclk_pwm.frequency if self._mclk_pwm else None @property def width(self) -> int: """Get the image width in pixels.""" return self._w @property def height(self) -> int: """Get the image height in pixels.""" return self._h @property def colorspace(self) -> int: """Get or set the colorspace, one of the ``OV5640_COLOR_`` constants.""" return self._colorspace @colorspace.setter def colorspace(self, colorspace: int) -> None: self._colorspace = colorspace self._set_size_and_colorspace() def _set_image_options(self) -> None: # pylint: disable=too-many-branches reg20 = reg21 = reg4514 = reg4514_test = 0 if self.colorspace == OV5640_COLOR_JPEG: reg21 |= 0x20 if self._binning: reg20 |= 1 reg21 |= 1 reg4514_test |= 4 else: reg20 |= 0x40 if self._flip_y: reg20 |= 0x06 reg4514_test |= 1 if self._flip_x: reg21 |= 0x06 reg4514_test |= 2 if reg4514_test == 0: reg4514 = 0x88 elif reg4514_test == 1: reg4514 = 0x00 elif reg4514_test == 2: reg4514 = 0xBB elif reg4514_test == 3: reg4514 = 0x00 elif reg4514_test == 4: reg4514 = 0xAA elif reg4514_test == 5: reg4514 = 0xBB elif reg4514_test == 6: reg4514 = 0xBB elif reg4514_test == 7: reg4514 = 0xAA self._write_register(_TIMING_TC_REG20, reg20) self._write_register(_TIMING_TC_REG21, reg21) self._write_register(0x4514, reg4514) if self._binning: self._write_register(0x4520, 0x0B) self._write_register(_X_INCREMENT, 0x31) self._write_register(_Y_INCREMENT, 0x31) else: self._write_register(0x4520, 0x10) self._write_register(_X_INCREMENT, 0x11) self._write_register(_Y_INCREMENT, 0x11) def _set_colorspace(self) -> None: colorspace = self._colorspace settings = _ov5640_color_settings[colorspace] self._write_list(settings) def deinit(self) -> None: """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) -> int: """Get or set the captured image size, one of the ``OV5640_SIZE_`` constants.""" return self._size def _set_size_and_colorspace(self) -> None: # pylint: disable=too-many-locals size = self._size width, height, ratio = _resolution_info[size] self._w = width self._h = height ( max_width, max_height, start_x, start_y, end_x, end_y, offset_x, offset_y, total_x, total_y, ) = _ratio_table[ratio] self._binning = (width <= max_width // 2) and (height <= max_height // 2) self._scale = not ( (width == max_width and height == max_height) or (width == max_width // 2 and height == max_height // 2) ) self._write_addr_reg(_X_ADDR_ST_H, start_x, start_y) self._write_addr_reg(_X_ADDR_END_H, end_x, end_y) self._write_addr_reg(_X_OUTPUT_SIZE_H, width, height) if not self._binning: self._write_addr_reg(_X_TOTAL_SIZE_H, total_x, total_y) self._write_addr_reg(_X_OFFSET_H, offset_x, offset_y) else: if width > 920: self._write_addr_reg(_X_TOTAL_SIZE_H, total_x - 200, total_y // 2) else: self._write_addr_reg(_X_TOTAL_SIZE_H, 2060, total_y // 2) self._write_addr_reg(_X_OFFSET_H, offset_x // 2, offset_y // 2) self._write_reg_bits(_ISP_CONTROL_01, 0x20, self._scale) self._set_image_options() if self.colorspace == OV5640_COLOR_JPEG: sys_mul = 200 if size < OV5640_SIZE_QVGA: sys_mul = 160 if size < OV5640_SIZE_XGA: sys_mul = 180 self._set_pll(False, sys_mul, 4, 2, False, 2, True, 4) else: self._set_pll(False, 32, 1, 1, False, 1, True, 4) self._set_colorspace() def _set_pll( # pylint: disable=too-many-arguments self, bypass: bool, multiplier: int, sys_div: int, pre_div: int, root_2x: bool, pclk_root_div: int, pclk_manual: bool, pclk_div: int, ) -> None: if ( # pylint: disable=too-many-boolean-expressions multiplier > 252 or multiplier < 4 or sys_div > 15 or pre_div > 8 or pclk_div > 31 or pclk_root_div > 3 ): raise ValueError("Invalid argument to internal function") self._write_register(0x3039, 0x80 if bypass else 0) self._write_register(0x3034, 0x1A) self._write_register(0x3035, 1 | ((sys_div & 0xF) << 4)) self._write_register(0x3036, multiplier & 0xFF) self._write_register(0x3037, (pre_div & 0xF) | (0x10 if root_2x else 0)) self._write_register(0x3108, (pclk_root_div & 3) << 4 | 0x06) self._write_register(0x3824, pclk_div & 0x1F) self._write_register(0x460C, 0x22 if pclk_manual else 0x22) self._write_register(0x3103, 0x13) @size.setter def size(self, size: int) -> None: self._size = size self._set_size_and_colorspace() @property def flip_x(self) -> bool: """Get or set the X-flip flag""" return self._flip_x @flip_x.setter def flip_x(self, value: bool) -> None: self._flip_x = bool(value) self._set_image_options() @property def flip_y(self) -> bool: """Get or set the Y-flip flag""" return self._flip_y @flip_y.setter def flip_y(self, value: bool) -> None: self._flip_y = bool(value) self._set_image_options() @property def test_pattern(self) -> bool: """Set to True to enable a test pattern, False to enable normal image capture""" return self._test_pattern @test_pattern.setter def test_pattern(self, value: bool) -> None: self._test_pattern = value self._write_register(_PRE_ISP_TEST_SETTING_1, value << 7) @property def saturation(self) -> int: """Get or set the saturation value, from -4 to +4.""" return self._saturation @saturation.setter def saturation(self, value: int) -> None: if not -4 <= value <= 4: raise ValueError( "Invalid saturation {value}, use a value from -4..4 inclusive" ) for offset, reg_value in enumerate(_sensor_saturation_levels[value]): self._write_register(0x5381 + offset, reg_value) self._saturation = value @property def effect(self) -> int: """Get or set the special effect, one of the ``OV5640_SPECIAL_EFFECT_`` constants""" return self._effect @effect.setter def effect(self, value: int) -> None: for reg_addr, reg_value in zip( (0x5580, 0x5583, 0x5584, 0x5003), _sensor_special_effects[value] ): self._write_register(reg_addr, reg_value) self._effect = value @property def quality(self) -> int: """Controls the JPEG quality. Valid range is from 2..55 inclusive""" return self._read_register(_COMPRESSION_CTRL07) & 0x3F @quality.setter def quality(self, value: int) -> None: if not 2 <= value < 55: raise ValueError( f"Invalid quality value {value}, use a value from 2..55 inclusive" ) self._write_register(_COMPRESSION_CTRL07, value & 0x3F) def _write_group_3_settings(self, settings): self._write_register(0x3212, 0x3) # start group 3 self._write_list(settings) self._write_register(0x3212, 0x13) # end group 3 self._write_register(0x3212, 0xA3) # launch group 3 @property def brightness(self) -> int: """Sensor brightness adjustment, from -4 to 4 inclusive""" brightness_abs = self._read_register(0x5587) >> 4 brightness_neg = self._read_register(0x5588) & 8 if brightness_neg: return -brightness_abs return brightness_abs @brightness.setter def brightness(self, value: int) -> None: if not -4 <= value <= 4: raise ValueError( "Invalid brightness value {value}, use a value from -4..4 inclusive" ) self._write_group_3_settings( [0x5587, abs(value) << 4, 0x5588, 0x9 if value < 0 else 0x1] ) @property def contrast(self) -> int: """Sensor contrast adjustment, from -4 to 4 inclusive""" contrast_abs = self._read_register(0x5587) >> 4 contrast_neg = self._read_register(0x5588) & 8 if contrast_neg: return -contrast_abs return contrast_abs @contrast.setter def contrast(self, value: int) -> None: if not -3 <= value <= 3: raise ValueError( "Invalid contrast value {value}, use a value from -3..3 inclusive" ) setting = _contrast_settings[value] self._write_group_3_settings([0x5586, setting[0], 0x5585, setting[1]]) @property def exposure_value(self) -> int: """Sensor exposure (EV) adjustment, from -4 to 4 inclusive""" return self._ev @exposure_value.setter def exposure_value(self, value: int) -> None: if not -3 <= value <= 3: raise ValueError( "Invalid exposure value (EV) {value}, use a value from -4..4 inclusive" ) for offset, reg_value in enumerate(_sensor_ev_levels[value]): self._write_register(0x5381 + offset, reg_value) @property def white_balance(self) -> int: """The white balance setting, one of the ``OV5640_WHITE_BALANCE_*`` constants""" return self._white_balance @white_balance.setter def white_balance(self, value: int) -> None: if not OV5640_WHITE_BALANCE_AUTO <= value <= OV5640_WHITE_BALANCE_INCANDESCENT: raise ValueError( "Invalid exposure value (EV) {value}, " "use one of the OV5640_WHITE_BALANCE_* constants" ) self._write_register(0x3212, 0x3) # start group 3 for reg_addr, reg_value in zip(_light_registers, _light_modes[value]): self._write_register(reg_addr, reg_value) self._write_register(0x3212, 0x13) # end group 3 self._write_register(0x3212, 0xA3) # launch group 3 @property def night_mode(self) -> bool: """Enable or disable the night mode setting of the sensor""" return bool(self._read_register(0x3A00) & 0x04) @night_mode.setter def night_mode(self, value: bool) -> None: self._write_reg_bits(0x3A00, 0x04, value)