Adafruit_CircuitPython_OV5640/adafruit_ov5640.py
2022-08-16 18:09:14 -04:00

1446 lines
41 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 <https://www.adafruit.com/product/4729>
**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]=1b0
# 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)