From bb26370e2f9b6fb7546c684effae317bd6d45a0d Mon Sep 17 00:00:00 2001 From: Anatoli Arkhipenko Date: Wed, 3 Mar 2021 15:23:39 -0500 Subject: [PATCH] esp32 arduino core 1.0.5 + cam drivers update. vert flip --- esp32-cam-rtos-allframes/camera.c | 147 ++- .../esp32-cam-rtos-allframes.ino | 2 + .../esp32-camera-master.zip | Bin 99319 -> 0 bytes esp32-cam-rtos-allframes/img_converters.h | 3 +- esp32-cam-rtos-allframes/nt99141.c | 1032 +++++++++++++++++ esp32-cam-rtos-allframes/nt99141.h | 16 + esp32-cam-rtos-allframes/nt99141_regs.h | 211 ++++ esp32-cam-rtos-allframes/nt99141_settings.h | 825 +++++++++++++ esp32-cam-rtos-allframes/ov7670.c | 439 +++++++ esp32-cam-rtos-allframes/ov7670.h | 14 + esp32-cam-rtos-allframes/ov7670_regs.h | 354 ++++++ esp32-cam-rtos-allframes/sccb.c | 97 +- esp32-cam-rtos-allframes/sensor.h | 2 + esp32-cam-rtos-allframes/to_bmp.c | 11 + esp32-cam-rtos-allframes/twi.h | 2 +- esp32-cam-rtos/camera.c | 147 ++- esp32-cam-rtos/esp32-cam-rtos.ino | 4 +- esp32-cam-rtos/esp32-camera-master.zip | Bin 99319 -> 0 bytes esp32-cam-rtos/img_converters.h | 3 +- esp32-cam-rtos/nt99141.c | 1032 +++++++++++++++++ esp32-cam-rtos/nt99141.h | 16 + esp32-cam-rtos/nt99141_regs.h | 211 ++++ esp32-cam-rtos/nt99141_settings.h | 825 +++++++++++++ esp32-cam-rtos/ov7670.c | 439 +++++++ esp32-cam-rtos/ov7670.h | 14 + esp32-cam-rtos/ov7670_regs.h | 354 ++++++ esp32-cam-rtos/sccb.c | 97 +- esp32-cam-rtos/sensor.h | 2 + esp32-cam-rtos/to_bmp.c | 11 + esp32-cam-rtos/twi.h | 2 +- esp32-cam/camera.c | 166 ++- esp32-cam/esp32-cam.ino | 8 +- esp32-cam/esp32-camera-master.zip | Bin 97227 -> 0 bytes esp32-cam/img_converters.h | 3 +- esp32-cam/nt99141.c | 1032 +++++++++++++++++ esp32-cam/nt99141.h | 16 + esp32-cam/nt99141_regs.h | 211 ++++ esp32-cam/nt99141_settings.h | 825 +++++++++++++ esp32-cam/ov7670.c | 439 +++++++ esp32-cam/ov7670.h | 14 + esp32-cam/ov7670_regs.h | 354 ++++++ esp32-cam/ov7725.c | 305 +++-- esp32-cam/sccb.c | 99 +- esp32-cam/sensor.h | 2 + esp32-cam/to_bmp.c | 11 + esp32-cam/twi.h | 2 +- 46 files changed, 9323 insertions(+), 476 deletions(-) delete mode 100644 esp32-cam-rtos-allframes/esp32-camera-master.zip create mode 100644 esp32-cam-rtos-allframes/nt99141.c create mode 100644 esp32-cam-rtos-allframes/nt99141.h create mode 100644 esp32-cam-rtos-allframes/nt99141_regs.h create mode 100644 esp32-cam-rtos-allframes/nt99141_settings.h create mode 100644 esp32-cam-rtos-allframes/ov7670.c create mode 100644 esp32-cam-rtos-allframes/ov7670.h create mode 100644 esp32-cam-rtos-allframes/ov7670_regs.h delete mode 100644 esp32-cam-rtos/esp32-camera-master.zip create mode 100644 esp32-cam-rtos/nt99141.c create mode 100644 esp32-cam-rtos/nt99141.h create mode 100644 esp32-cam-rtos/nt99141_regs.h create mode 100644 esp32-cam-rtos/nt99141_settings.h create mode 100644 esp32-cam-rtos/ov7670.c create mode 100644 esp32-cam-rtos/ov7670.h create mode 100644 esp32-cam-rtos/ov7670_regs.h delete mode 100644 esp32-cam/esp32-camera-master.zip create mode 100644 esp32-cam/nt99141.c create mode 100644 esp32-cam/nt99141.h create mode 100644 esp32-cam/nt99141_regs.h create mode 100644 esp32-cam/nt99141_settings.h create mode 100644 esp32-cam/ov7670.c create mode 100644 esp32-cam/ov7670.h create mode 100644 esp32-cam/ov7670_regs.h diff --git a/esp32-cam-rtos-allframes/camera.c b/esp32-cam-rtos-allframes/camera.c index 0fc85f6..f0f3a2e 100644 --- a/esp32-cam-rtos-allframes/camera.c +++ b/esp32-cam-rtos-allframes/camera.c @@ -48,6 +48,12 @@ #if CONFIG_OV5640_SUPPORT #include "ov5640.h" #endif +#if CONFIG_NT99141_SUPPORT +#include "nt99141.h" +#endif +#if CONFIG_OV7670_SUPPORT +#include "ov7670.h" +#endif typedef enum { CAMERA_NONE = 0, @@ -56,6 +62,8 @@ typedef enum { CAMERA_OV2640 = 2640, CAMERA_OV3660 = 3660, CAMERA_OV5640 = 5640, + CAMERA_OV7670 = 7670, + CAMERA_NT99141 = 9141, } camera_model_t; #define REG_PID 0x0A @@ -369,12 +377,10 @@ static inline void IRAM_ATTR i2s_conf_reset() } } -static void i2s_init() +static void i2s_gpio_init(const camera_config_t* config) { - camera_config_t* config = &s_state->config; - // Configure input GPIOs - gpio_num_t pins[] = { + const gpio_num_t pins[] = { config->pin_d7, config->pin_d6, config->pin_d5, @@ -391,15 +397,21 @@ static void i2s_init() .mode = GPIO_MODE_INPUT, .pull_up_en = GPIO_PULLUP_ENABLE, .pull_down_en = GPIO_PULLDOWN_DISABLE, - .intr_type = GPIO_INTR_DISABLE + .intr_type = GPIO_INTR_DISABLE, + .pin_bit_mask = 0LL }; for (int i = 0; i < sizeof(pins) / sizeof(gpio_num_t); ++i) { if (rtc_gpio_is_valid_gpio(pins[i])) { rtc_gpio_deinit(pins[i]); } - conf.pin_bit_mask = 1LL << pins[i]; - gpio_config(&conf); + conf.pin_bit_mask |= 1LL << pins[i]; } + gpio_config(&conf); +} + +static void i2s_init() +{ + camera_config_t* config = &s_state->config; // Route input GPIOs to I2S peripheral using GPIO matrix gpio_matrix_in(config->pin_d0, I2S0I_DATA_IN0_IDX, false); @@ -738,7 +750,7 @@ static void IRAM_ATTR dma_filter_buffer(size_t buf_idx) if(s_state->sensor.pixformat == PIXFORMAT_JPEG) { uint32_t sig = *((uint32_t *)s_state->fb->buf) & 0xFFFFFF; if(sig != 0xffd8ff) { - ets_printf("bh 0x%08x\n", sig); + ESP_LOGD(TAG,"unexpected JPEG signature 0x%08x\n", sig); s_state->fb->bad = 1; return; } @@ -955,11 +967,15 @@ esp_err_t camera_probe(const camera_config_t* config, camera_model_t* out_camera return ESP_ERR_NO_MEM; } - ESP_LOGD(TAG, "Enabling XCLK output"); - camera_enable_out_clock(config); + if(config->pin_xclk >= 0) { + ESP_LOGD(TAG, "Enabling XCLK output"); + camera_enable_out_clock(config); + } - ESP_LOGD(TAG, "Initializing SSCB"); - SCCB_Init(config->pin_sscb_sda, config->pin_sscb_scl); + if (config->pin_sscb_sda != -1) { + ESP_LOGD(TAG, "Initializing SSCB"); + SCCB_Init(config->pin_sscb_sda, config->pin_sscb_scl); + } if(config->pin_pwdn >= 0) { ESP_LOGD(TAG, "Resetting camera by power down line"); @@ -1011,16 +1027,33 @@ esp_err_t camera_probe(const camera_config_t* config, camera_model_t* out_camera slv_addr = SCCB_Probe(); } #endif +#if CONFIG_NT99141_SUPPORT + if (slv_addr == 0x2a) + { + ESP_LOGD(TAG, "Resetting NT99141"); + SCCB_Write16(0x2a, 0x3008, 0x01);//bank sensor + } +#endif s_state->sensor.slv_addr = slv_addr; s_state->sensor.xclk_freq_hz = config->xclk_freq_hz; -#if (CONFIG_OV3660_SUPPORT || CONFIG_OV5640_SUPPORT) +#if (CONFIG_OV3660_SUPPORT || CONFIG_OV5640_SUPPORT || CONFIG_NT99141_SUPPORT) if(s_state->sensor.slv_addr == 0x3c){ id->PID = SCCB_Read16(s_state->sensor.slv_addr, REG16_CHIDH); id->VER = SCCB_Read16(s_state->sensor.slv_addr, REG16_CHIDL); vTaskDelay(10 / portTICK_PERIOD_MS); ESP_LOGD(TAG, "Camera PID=0x%02x VER=0x%02x", id->PID, id->VER); + } else if(s_state->sensor.slv_addr == 0x2a){ + id->PID = SCCB_Read16(s_state->sensor.slv_addr, 0x3000); + id->VER = SCCB_Read16(s_state->sensor.slv_addr, 0x3001); + vTaskDelay(10 / portTICK_PERIOD_MS); + ESP_LOGD(TAG, "Camera PID=0x%02x VER=0x%02x", id->PID, id->VER); + if(config->xclk_freq_hz > 10000000) + { + ESP_LOGE(TAG, "NT99141: only XCLK under 10MHz is supported, and XCLK is now set to 10M"); + s_state->sensor.xclk_freq_hz = 10000000; + } } else { #endif id->PID = SCCB_Read(s_state->sensor.slv_addr, REG_PID); @@ -1031,7 +1064,7 @@ esp_err_t camera_probe(const camera_config_t* config, camera_model_t* out_camera ESP_LOGD(TAG, "Camera PID=0x%02x VER=0x%02x MIDL=0x%02x MIDH=0x%02x", id->PID, id->VER, id->MIDH, id->MIDL); -#if (CONFIG_OV3660_SUPPORT || CONFIG_OV5640_SUPPORT) +#if (CONFIG_OV3660_SUPPORT || CONFIG_OV5640_SUPPORT || CONFIG_NT99141_SUPPORT) } #endif @@ -1060,6 +1093,18 @@ esp_err_t camera_probe(const camera_config_t* config, camera_model_t* out_camera *out_camera_model = CAMERA_OV5640; ov5640_init(&s_state->sensor); break; +#endif +#if CONFIG_OV7670_SUPPORT + case OV7670_PID: + *out_camera_model = CAMERA_OV7670; + ov7670_init(&s_state->sensor); + break; +#endif +#if CONFIG_NT99141_SUPPORT + case NT99141_PID: + *out_camera_model = CAMERA_NT99141; + NT99141_init(&s_state->sensor); + break; #endif default: id->PID = 0; @@ -1116,6 +1161,20 @@ esp_err_t camera_init(const camera_config_t* config) frame_size = FRAMESIZE_QSXGA; } break; +#endif +#if CONFIG_OV7670_SUPPORT + case OV7670_PID: + if (frame_size > FRAMESIZE_VGA) { + frame_size = FRAMESIZE_VGA; + } + break; +#endif +#if CONFIG_NT99141_SUPPORT + case NT99141_PID: + if (frame_size > FRAMESIZE_HD) { + frame_size = FRAMESIZE_HD; + } + break; #endif default: return ESP_ERR_CAMERA_NOT_SUPPORTED; @@ -1126,7 +1185,7 @@ esp_err_t camera_init(const camera_config_t* config) if (pix_format == PIXFORMAT_GRAYSCALE) { s_state->fb_size = s_state->width * s_state->height; - if (s_state->sensor.id.PID == OV3660_PID || s_state->sensor.id.PID == OV5640_PID) { + if (s_state->sensor.id.PID == OV3660_PID || s_state->sensor.id.PID == OV5640_PID || s_state->sensor.id.PID == NT99141_PID) { if (is_hs_mode()) { s_state->sampling_mode = SM_0A00_0B00; s_state->dma_filter = &dma_filter_yuyv_highspeed; @@ -1147,20 +1206,28 @@ esp_err_t camera_init(const camera_config_t* config) } s_state->fb_bytes_per_pixel = 1; // frame buffer stores Y8 } else if (pix_format == PIXFORMAT_YUV422 || pix_format == PIXFORMAT_RGB565) { - s_state->fb_size = s_state->width * s_state->height * 2; - if (is_hs_mode() && s_state->sensor.id.PID != OV7725_PID) { - s_state->sampling_mode = SM_0A00_0B00; - s_state->dma_filter = &dma_filter_yuyv_highspeed; - } else { - s_state->sampling_mode = SM_0A0B_0C0D; - s_state->dma_filter = &dma_filter_yuyv; - } - s_state->in_bytes_per_pixel = 2; // camera sends YU/YV - s_state->fb_bytes_per_pixel = 2; // frame buffer stores YU/YV/RGB565 + s_state->fb_size = s_state->width * s_state->height * 2; + if (is_hs_mode() && s_state->sensor.id.PID != OV7725_PID) { + if(s_state->sensor.id.PID == OV7670_PID) { + s_state->sampling_mode = SM_0A0B_0B0C; + }else{ + s_state->sampling_mode = SM_0A00_0B00; + } + s_state->dma_filter = &dma_filter_yuyv_highspeed; + } else { + s_state->sampling_mode = SM_0A0B_0C0D; + s_state->dma_filter = &dma_filter_yuyv; + } + s_state->in_bytes_per_pixel = 2; // camera sends YU/YV + s_state->fb_bytes_per_pixel = 2; // frame buffer stores YU/YV/RGB565 } else if (pix_format == PIXFORMAT_RGB888) { s_state->fb_size = s_state->width * s_state->height * 3; if (is_hs_mode()) { - s_state->sampling_mode = SM_0A00_0B00; + if(s_state->sensor.id.PID == OV7670_PID) { + s_state->sampling_mode = SM_0A0B_0B0C; + }else{ + s_state->sampling_mode = SM_0A00_0B00; + } s_state->dma_filter = &dma_filter_rgb888_highspeed; } else { s_state->sampling_mode = SM_0A0B_0C0D; @@ -1169,7 +1236,7 @@ esp_err_t camera_init(const camera_config_t* config) s_state->in_bytes_per_pixel = 2; // camera sends RGB565 s_state->fb_bytes_per_pixel = 3; // frame buffer stores RGB888 } else if (pix_format == PIXFORMAT_JPEG) { - if (s_state->sensor.id.PID != OV2640_PID && s_state->sensor.id.PID != OV3660_PID && s_state->sensor.id.PID != OV5640_PID) { + if (s_state->sensor.id.PID != OV2640_PID && s_state->sensor.id.PID != OV3660_PID && s_state->sensor.id.PID != OV5640_PID && s_state->sensor.id.PID != NT99141_PID) { ESP_LOGE(TAG, "JPEG format is only supported for ov2640, ov3660 and ov5640"); err = ESP_ERR_NOT_SUPPORTED; goto fail; @@ -1306,6 +1373,7 @@ fail: esp_err_t esp_camera_init(const camera_config_t* config) { camera_model_t camera_model = CAMERA_NONE; + i2s_gpio_init(config); esp_err_t err = camera_probe(config, &camera_model); if (err != ESP_OK) { ESP_LOGE(TAG, "Camera probe failed with error 0x%x", err); @@ -1324,6 +1392,10 @@ esp_err_t esp_camera_init(const camera_config_t* config) ESP_LOGI(TAG, "Detected OV3660 camera"); } else if (camera_model == CAMERA_OV5640) { ESP_LOGI(TAG, "Detected OV5640 camera"); + } else if (camera_model == CAMERA_OV7670) { + ESP_LOGI(TAG, "Detected OV7670 camera"); + } else if (camera_model == CAMERA_NT99141) { + ESP_LOGI(TAG, "Detected NT99141 camera"); } else { ESP_LOGI(TAG, "Camera not supported"); err = ESP_ERR_CAMERA_NOT_SUPPORTED; @@ -1370,9 +1442,12 @@ esp_err_t esp_camera_deinit() } dma_desc_deinit(); camera_fb_deinit(); + + if(s_state->config.pin_xclk >= 0) { + camera_disable_out_clock(); + } free(s_state); s_state = NULL; - camera_disable_out_clock(); periph_module_disable(PERIPH_I2S0_MODULE); return ESP_OK; } @@ -1430,11 +1505,11 @@ sensor_t * esp_camera_sensor_get() esp_err_t esp_camera_save_to_nvs(const char *key) { -#if ESP_IDF_VERSION_MAJOR > 3 - nvs_handle_t handle; -#else +//#if ESP_IDF_VERSION_MAJOR > 3 +// nvs_handle_t handle; +//#else nvs_handle handle; -#endif +//#endif esp_err_t ret = nvs_open(key,NVS_READWRITE,&handle); if (ret == ESP_OK) { @@ -1458,11 +1533,11 @@ esp_err_t esp_camera_save_to_nvs(const char *key) esp_err_t esp_camera_load_from_nvs(const char *key) { -#if ESP_IDF_VERSION_MAJOR > 3 - nvs_handle_t handle; -#else +//#if ESP_IDF_VERSION_MAJOR > 3 +// nvs_handle_t handle; +//#else nvs_handle handle; -#endif +//#endif uint8_t pf; esp_err_t ret = nvs_open(key,NVS_READWRITE,&handle); diff --git a/esp32-cam-rtos-allframes/esp32-cam-rtos-allframes.ino b/esp32-cam-rtos-allframes/esp32-cam-rtos-allframes.ino index c7e0d50..df59ac7 100644 --- a/esp32-cam-rtos-allframes/esp32-cam-rtos-allframes.ino +++ b/esp32-cam-rtos-allframes/esp32-cam-rtos-allframes.ino @@ -421,6 +421,8 @@ void setup() ESP.restart(); } + sensor_t* s = esp_camera_sensor_get(); + s->set_vflip(s, true); // Configure and connect to WiFi IPAddress ip; diff --git a/esp32-cam-rtos-allframes/esp32-camera-master.zip b/esp32-cam-rtos-allframes/esp32-camera-master.zip deleted file mode 100644 index e8118ae8cbde083aa2d898912ca0840cbb1ba02f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 99319 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%0DWVpqK+tQ4{dZyTI7z{pE?+UbMA=x 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

ccC}J~5Ws<*6rFR> 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 + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * NT99141 driver. + * + */ +#include +#include +#include +#include "sccb.h" +#include "nt99141.h" +#include "nt99141_regs.h" +#include "nt99141_settings.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#else +#include "esp_log.h" +static const char *TAG = "NT99141"; +#endif + +//#define REG_DEBUG_ON + +static int read_reg(uint8_t slv_addr, const uint16_t reg) +{ + int ret = SCCB_Read16(slv_addr, reg); +#ifdef REG_DEBUG_ON + + if (ret < 0) { + ESP_LOGE(TAG, "READ REG 0x%04x FAILED: %d", reg, ret); + } + +#endif + return ret; +} + +static int check_reg_mask(uint8_t slv_addr, uint16_t reg, uint8_t mask) +{ + return (read_reg(slv_addr, reg) & mask) == mask; +} + +static int read_reg16(uint8_t slv_addr, const uint16_t reg) +{ + int ret = 0, ret2 = 0; + ret = read_reg(slv_addr, reg); + + if (ret >= 0) { + ret = (ret & 0xFF) << 8; + ret2 = read_reg(slv_addr, reg + 1); + + if (ret2 < 0) { + ret = ret2; + } else { + ret |= ret2 & 0xFF; + } + } + + return ret; +} + + +static int write_reg(uint8_t slv_addr, const uint16_t reg, uint8_t value) +{ + int ret = 0; +#ifndef REG_DEBUG_ON + ret = SCCB_Write16(slv_addr, reg, value); +#else + int old_value = read_reg(slv_addr, reg); + + if (old_value < 0) { + return old_value; + } + + if ((uint8_t)old_value != value) { + ESP_LOGD(TAG, "NEW REG 0x%04x: 0x%02x to 0x%02x", reg, (uint8_t)old_value, value); + ret = SCCB_Write16(slv_addr, reg, value); + } else { + ESP_LOGD(TAG, "OLD REG 0x%04x: 0x%02x", reg, (uint8_t)old_value); + ret = SCCB_Write16(slv_addr, reg, value);//maybe not? + } + + if (ret < 0) { + ESP_LOGE(TAG, "WRITE REG 0x%04x FAILED: %d", reg, ret); + } + +#endif + return ret; +} + +static int set_reg_bits(uint8_t slv_addr, uint16_t reg, uint8_t offset, uint8_t mask, uint8_t value) +{ + int ret = 0; + uint8_t c_value, new_value; + ret = read_reg(slv_addr, reg); + + if (ret < 0) { + return ret; + } + + c_value = ret; + new_value = (c_value & ~(mask << offset)) | ((value & mask) << offset); + ret = write_reg(slv_addr, reg, new_value); + return ret; +} + +static int write_regs(uint8_t slv_addr, const uint16_t (*regs)[2]) +{ + int i = 0, ret = 0; + + while (!ret && regs[i][0] != REGLIST_TAIL) { + if (regs[i][0] == REG_DLY) { + vTaskDelay(regs[i][1] / portTICK_PERIOD_MS); + } else { + ret = write_reg(slv_addr, regs[i][0], regs[i][1]); + } + + i++; + } + + return ret; +} + +static int write_reg16(uint8_t slv_addr, const uint16_t reg, uint16_t value) +{ + if (write_reg(slv_addr, reg, value >> 8) || write_reg(slv_addr, reg + 1, value)) { + return -1; + } + + return 0; +} + +static int write_addr_reg(uint8_t slv_addr, const uint16_t reg, uint16_t x_value, uint16_t y_value) +{ + if (write_reg16(slv_addr, reg, x_value) || write_reg16(slv_addr, reg + 2, y_value)) { + return -1; + } + + return 0; +} + +#define write_reg_bits(slv_addr, reg, mask, enable) set_reg_bits(slv_addr, reg, 0, mask, enable?mask:0) + +static int calc_sysclk(int xclk, bool pll_bypass, int pll_multiplier, int pll_sys_div, int pll_pre_div, bool pll_root_2x, int pll_seld5, bool pclk_manual, int pclk_div) +{ + const int pll_pre_div2x_map[] = { 2, 3, 4, 6 };//values are multiplied by two to avoid floats + const int pll_seld52x_map[] = { 2, 2, 4, 5 }; + + if (!pll_sys_div) { + pll_sys_div = 1; + } + + int pll_pre_div2x = pll_pre_div2x_map[pll_pre_div]; + int pll_root_div = pll_root_2x ? 2 : 1; + int pll_seld52x = pll_seld52x_map[pll_seld5]; + + int VCO = (xclk / 1000) * pll_multiplier * pll_root_div * 2 / pll_pre_div2x; + int PLLCLK = pll_bypass ? (xclk) : (VCO * 1000 * 2 / pll_sys_div / pll_seld52x); + int PCLK = PLLCLK / 2 / ((pclk_manual && pclk_div) ? pclk_div : 1); + int SYSCLK = PLLCLK / 4; + + ESP_LOGD(TAG, "Calculated VCO: %d Hz, PLLCLK: %d Hz, SYSCLK: %d Hz, PCLK: %d Hz", VCO * 1000, PLLCLK, SYSCLK, PCLK); + return SYSCLK; +} + +static int set_pll(sensor_t *sensor, bool bypass, uint8_t multiplier, uint8_t sys_div, uint8_t pre_div, bool root_2x, uint8_t seld5, bool pclk_manual, uint8_t pclk_div) +{ + return -1; +} + +static int set_ae_level(sensor_t *sensor, int level); + +static int reset(sensor_t *sensor) +{ + + int ret = 0; + // Software Reset: clear all registers and reset them to their default values + ret = write_reg(sensor->slv_addr, SYSTEM_CTROL0, 0x01); + + if (ret) { + ESP_LOGE(TAG, "Software Reset FAILED!"); + return ret; + } + + vTaskDelay(100 / portTICK_PERIOD_MS); + ret = write_regs(sensor->slv_addr, sensor_default_regs); //re-initial + + if (ret == 0) { + ESP_LOGD(TAG, "Camera defaults loaded"); + ret = set_ae_level(sensor, 0); + vTaskDelay(100 / portTICK_PERIOD_MS); + } + + return ret; +} + +static int set_pixformat(sensor_t *sensor, pixformat_t pixformat) +{ + int ret = 0; + const uint16_t (*regs)[2]; + + switch (pixformat) { + case PIXFORMAT_YUV422: + regs = sensor_fmt_yuv422; + break; + + case PIXFORMAT_GRAYSCALE: + regs = sensor_fmt_grayscale; + break; + + case PIXFORMAT_RGB565: + case PIXFORMAT_RGB888: + regs = sensor_fmt_rgb565; + break; + + case PIXFORMAT_JPEG: + regs = sensor_fmt_jpeg; + break; + + case PIXFORMAT_RAW: + regs = sensor_fmt_raw; + break; + + default: + ESP_LOGE(TAG, "Unsupported pixformat: %u", pixformat); + return -1; + } + + ret = write_regs(sensor->slv_addr, regs); + + if (ret == 0) { + sensor->pixformat = pixformat; + ESP_LOGD(TAG, "Set pixformat to: %u", pixformat); + } + + return ret; +} + +static int set_image_options(sensor_t *sensor) +{ + int ret = 0; + uint8_t reg20 = 0; + uint8_t reg21 = 0; + uint8_t reg4514 = 0; + uint8_t reg4514_test = 0; + + // V-Flip + if (sensor->status.vflip) { + reg20 |= 0x01; + reg4514_test |= 1; + } + + // H-Mirror + if (sensor->status.hmirror) { + reg21 |= 0x02; + reg4514_test |= 2; + } + + switch (reg4514_test) { + + } + + if (write_reg(sensor->slv_addr, TIMING_TC_REG20, reg20 | reg21)) { + ESP_LOGE(TAG, "Setting Image Options Failed"); + ret = -1; + } + + ESP_LOGD(TAG, "Set Image Options: Compression: %u, Binning: %u, V-Flip: %u, H-Mirror: %u, Reg-4514: 0x%02x", + sensor->pixformat == PIXFORMAT_JPEG, sensor->status.binning, sensor->status.vflip, sensor->status.hmirror, reg4514); + return ret; +} + +static int set_framesize(sensor_t *sensor, framesize_t framesize) +{ + int ret = 0; + + sensor->status.framesize = framesize; + ret = write_regs(sensor->slv_addr, sensor_default_regs); + + if (framesize == FRAMESIZE_QVGA) { + ESP_LOGD(TAG, "Set FRAMESIZE_QVGA"); + ret = write_regs(sensor->slv_addr, sensor_framesize_QVGA); +#if CONFIG_NT99141_SUPPORT_XSKIP + ESP_LOGD(TAG, "Set FRAMESIZE_QVGA: xskip mode"); + ret = write_regs(sensor->slv_addr, sensor_framesize_QVGA_xskip); +#elif CONFIG_NT99141_SUPPORT_CROP + ESP_LOGD(TAG, "Set FRAMESIZE_QVGA: crop mode"); + ret = write_regs(sensor->slv_addr, sensor_framesize_QVGA_crop); +#endif + } else if (framesize == FRAMESIZE_VGA) { + ESP_LOGD(TAG, "Set FRAMESIZE_VGA"); + // ret = write_regs(sensor->slv_addr, sensor_framesize_VGA); + ret = write_regs(sensor->slv_addr, sensor_framesize_VGA_xyskip);// Resolution:640*360 This configuration is equally-scaled without deforming +#ifdef CONFIG_NT99141_SUPPORT_XSKIP + ESP_LOGD(TAG, "Set FRAMESIZE_QVGA: xskip mode"); + ret = write_regs(sensor->slv_addr, sensor_framesize_VGA_xskip); +#elif CONFIG_NT99141_SUPPORT_CROP + ESP_LOGD(TAG, "Set FRAMESIZE_QVGA: crop mode"); + ret = write_regs(sensor->slv_addr, sensor_framesize_VGA_crop); +#endif + } else if (framesize >= FRAMESIZE_HD) { + ESP_LOGD(TAG, "Set FRAMESIZE_HD"); + ret = write_regs(sensor->slv_addr, sensor_framesize_HD); + } else { + ESP_LOGD(TAG, "Dont suppost this size, Set FRAMESIZE_VGA"); + ret = write_regs(sensor->slv_addr, sensor_framesize_VGA); + } + + return 0; +} + +static int set_hmirror(sensor_t *sensor, int enable) +{ + int ret = 0; + sensor->status.hmirror = enable; + ret = set_image_options(sensor); + + if (ret == 0) { + ESP_LOGD(TAG, "Set h-mirror to: %d", enable); + } + + return ret; +} + +static int set_vflip(sensor_t *sensor, int enable) +{ + int ret = 0; + sensor->status.vflip = enable; + ret = set_image_options(sensor); + + if (ret == 0) { + ESP_LOGD(TAG, "Set v-flip to: %d", enable); + } + + return ret; +} + +static int set_quality(sensor_t *sensor, int qs) +{ + int ret = 0; + ret = write_reg(sensor->slv_addr, COMPRESSION_CTRL07, qs & 0x3f); + + if (ret == 0) { + sensor->status.quality = qs; + ESP_LOGD(TAG, "Set quality to: %d", qs); + } + + return ret; +} + +static int set_colorbar(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, PRE_ISP_TEST_SETTING_1, TEST_COLOR_BAR, enable); + + if (ret == 0) { + sensor->status.colorbar = enable; + ESP_LOGD(TAG, "Set colorbar to: %d", enable); + } + + return ret; +} + +static int set_gain_ctrl(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, 0x32bb, 0x87, enable); + + if (ret == 0) { + ESP_LOGD(TAG, "Set gain_ctrl to: %d", enable); + sensor->status.agc = enable; + } + + return ret; +} + +static int set_exposure_ctrl(sensor_t *sensor, int enable) +{ + int ret = 0; + int data = 0; + // ret = write_reg_bits(sensor->slv_addr, 0x32bb, 0x87, enable); + data = read_reg(sensor->slv_addr, 0x3201); + ESP_LOGD(TAG, "set_exposure_ctrl:enable"); + if (enable) { + ESP_LOGD(TAG, "set_exposure_ctrl:enable"); + ret = write_reg(sensor->slv_addr, 0x3201, (1 << 5) | data); + } else { + ESP_LOGD(TAG, "set_exposure_ctrl:disable"); + ret = write_reg(sensor->slv_addr, 0x3201, (~(1 << 5)) & data); + } + + if (ret == 0) { + ESP_LOGD(TAG, "Set exposure_ctrl to: %d", enable); + sensor->status.aec = enable; + } + + return ret; +} + +static int set_whitebal(sensor_t *sensor, int enable) +{ + int ret = 0; + + if (ret == 0) { + ESP_LOGD(TAG, "Set awb to: %d", enable); + sensor->status.awb = enable; + } + + return ret; +} + +//Advanced AWB +static int set_dcw_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + + if (ret == 0) { + ESP_LOGD(TAG, "Set dcw to: %d", enable); + sensor->status.dcw = enable; + } + + return ret; +} + +//night mode enable +static int set_aec2(sensor_t *sensor, int enable) +{ + int ret = 0; + + if (ret == 0) { + ESP_LOGD(TAG, "Set aec2 to: %d", enable); + sensor->status.aec2 = enable; + } + + return ret; +} + +static int set_bpc_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + + if (ret == 0) { + ESP_LOGD(TAG, "Set bpc to: %d", enable); + sensor->status.bpc = enable; + } + + return ret; +} + +static int set_wpc_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + + if (ret == 0) { + ESP_LOGD(TAG, "Set wpc to: %d", enable); + sensor->status.wpc = enable; + } + + return ret; +} + +//Gamma enable +static int set_raw_gma_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + + if (ret == 0) { + ESP_LOGD(TAG, "Set raw_gma to: %d", enable); + sensor->status.raw_gma = enable; + } + + return ret; +} + +static int set_lenc_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + + if (ret == 0) { + ESP_LOGD(TAG, "Set lenc to: %d", enable); + sensor->status.lenc = enable; + } + + return ret; +} + +static int get_agc_gain(sensor_t *sensor) +{ + ESP_LOGD(TAG, "get_agc_gain can not be configured at present"); + return 0; +} + +//real gain +static int set_agc_gain(sensor_t *sensor, int gain) +{ + ESP_LOGD(TAG, "set_agc_gain can not be configured at present"); + // ESP_LOGD(TAG, "GAIN = %d\n", gain); + int cnt = gain / 2; + + switch (cnt) { + case 0: + ESP_LOGD(TAG, "set_agc_gain: 1x"); + write_reg(sensor->slv_addr, 0X301D, 0X00); + break; + + case 1: + ESP_LOGD(TAG,"set_agc_gain: 2x"); + write_reg(sensor->slv_addr, 0X301D, 0X0F); + break; + + case 2: + ESP_LOGD(TAG,"set_agc_gain: 4x"); + write_reg(sensor->slv_addr, 0X301D, 0X2F); + break; + + case 3: + ESP_LOGD(TAG,"set_agc_gain: 6x"); + write_reg(sensor->slv_addr, 0X301D, 0X37); + break; + + case 4: + ESP_LOGD(TAG,"set_agc_gain: 8x"); + write_reg(sensor->slv_addr, 0X301D, 0X3F); + break; + + default: + ESP_LOGD(TAG,"fail set_agc_gain"); + break; + } + + return 0; +} + +static int get_aec_value(sensor_t *sensor) +{ + ESP_LOGD(TAG, "get_aec_value can not be configured at present"); + return 0; +} + +static int set_aec_value(sensor_t *sensor, int value) +{ + ESP_LOGD(TAG, "set_aec_value can not be configured at present"); + int ret = 0; + // ESP_LOGD(TAG, " set_aec_value to: %d", value); + ret = write_reg_bits(sensor->slv_addr, 0x3012, 0x00, (value >> 8) & 0xff); + ret = write_reg_bits(sensor->slv_addr, 0x3013, 0x01, value & 0xff); + + if (ret == 0) { + ESP_LOGD(TAG, " set_aec_value to: %d", value); + // sensor->status.aec = enable; + } + + return ret; +} + +static int set_ae_level(sensor_t *sensor, int level) +{ + ESP_LOGD(TAG, "set_ae_level can not be configured at present"); + int ret = 0; + + if (level < 0) { + level = 0; + } else if (level > 9) { + level = 9; + } + + for (int i = 0; i < 5; i++) { + ret += write_reg(sensor->slv_addr, sensor_ae_level[ 5 * level + i ][0], sensor_ae_level[5 * level + i ][1]); + } + + if (ret) { + ESP_LOGE(TAG, " fail to set ae level: %d", ret); + } + + return 0; +} + +static int set_wb_mode(sensor_t *sensor, int mode) +{ + int ret = 0; + + if (mode < 0 || mode > 4) { + return -1; + } + + ret = write_reg(sensor->slv_addr, 0x3201, (mode != 0)); + + if (ret) { + return ret; + } + + switch (mode) { + case 1://Sunny + ret = write_reg16(sensor->slv_addr, 0x3290, 0x01) + || write_reg16(sensor->slv_addr, 0x3291, 0x38) + || write_reg16(sensor->slv_addr, 0x3296, 0x01) + || write_reg16(sensor->slv_addr, 0x3297, 0x68) + || write_reg16(sensor->slv_addr, 0x3060, 0x01); + + break; + + case 2://Cloudy + + ret = write_reg16(sensor->slv_addr, 0x3290, 0x01) + || write_reg16(sensor->slv_addr, 0x3291, 0x51) + || write_reg16(sensor->slv_addr, 0x3296, 0x01) + || write_reg16(sensor->slv_addr, 0x3297, 0x00) + || write_reg16(sensor->slv_addr, 0x3060, 0x01); + break; + + case 3://INCANDESCENCE] + ret = write_reg16(sensor->slv_addr, 0x3290, 0x01) + || write_reg16(sensor->slv_addr, 0x3291, 0x30) + || write_reg16(sensor->slv_addr, 0x3296, 0x01) + || write_reg16(sensor->slv_addr, 0x3297, 0xCB) + || write_reg16(sensor->slv_addr, 0x3060, 0x01); + break; + + case 4://FLUORESCENT + ret = write_reg16(sensor->slv_addr, 0x3290, 0x01) + || write_reg16(sensor->slv_addr, 0x3291, 0x70) + || write_reg16(sensor->slv_addr, 0x3296, 0x01) + || write_reg16(sensor->slv_addr, 0x3297, 0xFF) + || write_reg16(sensor->slv_addr, 0x3060, 0x01); + break; + + default://AUTO + break; + } + + if (ret == 0) { + ESP_LOGD(TAG, "Set wb_mode to: %d", mode); + sensor->status.wb_mode = mode; + } + + return ret; +} + +static int set_awb_gain_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + int old_mode = sensor->status.wb_mode; + int mode = enable ? old_mode : 0; + + ret = set_wb_mode(sensor, mode); + + if (ret == 0) { + sensor->status.wb_mode = old_mode; + ESP_LOGD(TAG, "Set awb_gain to: %d", enable); + sensor->status.awb_gain = enable; + } + + return ret; +} + +static int set_special_effect(sensor_t *sensor, int effect) +{ + int ret = 0; + + if (effect < 0 || effect > 6) { + return -1; + } + + uint8_t *regs = (uint8_t *)sensor_special_effects[effect]; + ret = write_reg(sensor->slv_addr, 0x32F1, regs[0]) + || write_reg(sensor->slv_addr, 0x32F4, regs[1]) + || write_reg(sensor->slv_addr, 0x32F5, regs[2]) + || write_reg(sensor->slv_addr, 0x3060, regs[3]); + + if (ret == 0) { + ESP_LOGD(TAG, "Set special_effect to: %d", effect); + sensor->status.special_effect = effect; + } + + return ret; +} + +static int set_brightness(sensor_t *sensor, int level) +{ + int ret = 0; + uint8_t value = 0; + bool negative = false; + + switch (level) { + case 3: + value = 0xA0; + break; + + case 2: + value = 0x90; + break; + + case 1: + value = 0x88; + break; + + case -1: + value = 0x78; + negative = true; + break; + + case -2: + value = 0x70; + negative = true; + break; + + case -3: + value = 0x60; + negative = true; + break; + + default: // 0 + break; + } + + ret = write_reg(sensor->slv_addr, 0x32F2, value); + + if (ret == 0) { + ESP_LOGD(TAG, "Set brightness to: %d", level); + sensor->status.brightness = level; + } + + return ret; +} + +static int set_contrast(sensor_t *sensor, int level) +{ + int ret = 0; + uint8_t value1 = 0, value2 = 0 ; + bool negative = false; + + switch (level) { + case 3: + value1 = 0xD0; + value2 = 0xB0; + break; + + case 2: + value1 = 0xE0; + value2 = 0xA0; + break; + + case 1: + value1 = 0xF0; + value2 = 0x90; + break; + + case 0: + value1 = 0x00; + value2 = 0x80; + break; + + case -1: + value1 = 0x10; + value2 = 0x70; + break; + + case -2: + value1 = 0x20; + value2 = 0x60; + break; + + case -3: + value1 = 0x30; + value2 = 0x50; + break; + + default: // 0 + break; + } + + ret = write_reg(sensor->slv_addr, 0x32FC, value1); + ret = write_reg(sensor->slv_addr, 0x32F2, value2); + ret = write_reg(sensor->slv_addr, 0x3060, 0x01); + + if (ret == 0) { + ESP_LOGD(TAG, "Set contrast to: %d", level); + sensor->status.contrast = level; + } + + return ret; +} + +static int set_saturation(sensor_t *sensor, int level) +{ + int ret = 0; + + if (level > 4 || level < -4) { + return -1; + } + + uint8_t *regs = (uint8_t *)sensor_saturation_levels[level + 4]; + { + ret = write_reg(sensor->slv_addr, 0x32F3, regs[0]); + + if (ret) { + return ret; + } + } + + if (ret == 0) { + ESP_LOGD(TAG, "Set saturation to: %d", level); + sensor->status.saturation = level; + } + + return ret; +} + +static int set_sharpness(sensor_t *sensor, int level) +{ + int ret = 0; + + if (level > 3 || level < -3) { + return -1; + } + + uint8_t mt_offset_2 = (level + 3) * 8; + uint8_t mt_offset_1 = mt_offset_2 + 1; + + ret = write_reg_bits(sensor->slv_addr, 0x5308, 0x40, false)//0x40 means auto + || write_reg(sensor->slv_addr, 0x5300, 0x10) + || write_reg(sensor->slv_addr, 0x5301, 0x10) + || write_reg(sensor->slv_addr, 0x5302, mt_offset_1) + || write_reg(sensor->slv_addr, 0x5303, mt_offset_2) + || write_reg(sensor->slv_addr, 0x5309, 0x10) + || write_reg(sensor->slv_addr, 0x530a, 0x10) + || write_reg(sensor->slv_addr, 0x530b, 0x04) + || write_reg(sensor->slv_addr, 0x530c, 0x06); + + if (ret == 0) { + ESP_LOGD(TAG, "Set sharpness to: %d", level); + sensor->status.sharpness = level; + } + + return ret; +} + +static int set_gainceiling(sensor_t *sensor, gainceiling_t level) +{ + ESP_LOGD(TAG, "set_gainceiling can not be configured at present"); + return 0; +} + +static int get_denoise(sensor_t *sensor) +{ + + return (read_reg(sensor->slv_addr, 0x5306) / 4) + 1; +} + +static int set_denoise(sensor_t *sensor, int level) +{ + ESP_LOGD(TAG, "set_denoise can not be configured at present"); + return 0; +} + +static int get_reg(sensor_t *sensor, int reg, int mask) +{ + int ret = 0, ret2 = 0; + + if (mask > 0xFF) { + ret = read_reg16(sensor->slv_addr, reg); + + if (ret >= 0 && mask > 0xFFFF) { + ret2 = read_reg(sensor->slv_addr, reg + 2); + + if (ret2 >= 0) { + ret = (ret << 8) | ret2 ; + } else { + ret = ret2; + } + } + } else { + ret = read_reg(sensor->slv_addr, reg); + } + + if (ret > 0) { + ret &= mask; + } + + return ret; +} + +static int set_reg(sensor_t *sensor, int reg, int mask, int value) +{ + int ret = 0, ret2 = 0; + + if (mask > 0xFF) { + ret = read_reg16(sensor->slv_addr, reg); + + if (ret >= 0 && mask > 0xFFFF) { + ret2 = read_reg(sensor->slv_addr, reg + 2); + + if (ret2 >= 0) { + ret = (ret << 8) | ret2 ; + } else { + ret = ret2; + } + } + } else { + ret = read_reg(sensor->slv_addr, reg); + } + + if (ret < 0) { + return ret; + } + + value = (ret & ~mask) | (value & mask); + + if (mask > 0xFFFF) { + ret = write_reg16(sensor->slv_addr, reg, value >> 8); + + if (ret >= 0) { + ret = write_reg(sensor->slv_addr, reg + 2, value & 0xFF); + } + } else if (mask > 0xFF) { + ret = write_reg16(sensor->slv_addr, reg, value); + } else { + ret = write_reg(sensor->slv_addr, reg, value); + } + + return ret; +} + +static int set_res_raw(sensor_t *sensor, int startX, int startY, int endX, int endY, int offsetX, int offsetY, int totalX, int totalY, int outputX, int outputY, bool scale, bool binning) +{ + int ret = 0; + ret = write_addr_reg(sensor->slv_addr, X_ADDR_ST_H, startX, startY) + || write_addr_reg(sensor->slv_addr, X_ADDR_END_H, endX, endY) + || write_addr_reg(sensor->slv_addr, X_OFFSET_H, offsetX, offsetY) + || write_addr_reg(sensor->slv_addr, X_TOTAL_SIZE_H, totalX, totalY) + || write_addr_reg(sensor->slv_addr, X_OUTPUT_SIZE_H, outputX, outputY); + + if (!ret) { + sensor->status.scale = scale; + sensor->status.binning = binning; + ret = set_image_options(sensor); + } + + return ret; +} + +static int _set_pll(sensor_t *sensor, int bypass, int multiplier, int sys_div, int root_2x, int pre_div, int seld5, int pclk_manual, int pclk_div) +{ + return set_pll(sensor, bypass > 0, multiplier, sys_div, pre_div, root_2x > 0, seld5, pclk_manual > 0, pclk_div); +} + +esp_err_t xclk_timer_conf(int ledc_timer, int xclk_freq_hz); +static int set_xclk(sensor_t *sensor, int timer, int xclk) +{ + int ret = 0; + if (xclk > 10) + { + ESP_LOGE(TAG, "only XCLK under 10MHz is supported, and XCLK is now set to 10M"); + xclk = 10; + } + sensor->xclk_freq_hz = xclk * 1000000U; + ret = xclk_timer_conf(timer, sensor->xclk_freq_hz); + return ret; +} + +static int init_status(sensor_t *sensor) +{ + sensor->status.brightness = 0; + sensor->status.contrast = 0; + sensor->status.saturation = 0; + sensor->status.sharpness = (read_reg(sensor->slv_addr, 0x3301)); + sensor->status.denoise = get_denoise(sensor); + sensor->status.ae_level = 0; + sensor->status.gainceiling = read_reg16(sensor->slv_addr, 0x32F0) & 0xFF; + sensor->status.awb = check_reg_mask(sensor->slv_addr, ISP_CONTROL_01, 0x10); + sensor->status.dcw = !check_reg_mask(sensor->slv_addr, 0x5183, 0x80); + sensor->status.agc = !check_reg_mask(sensor->slv_addr, AEC_PK_MANUAL, AEC_PK_MANUAL_AGC_MANUALEN); + sensor->status.aec = !check_reg_mask(sensor->slv_addr, AEC_PK_MANUAL, AEC_PK_MANUAL_AEC_MANUALEN); + sensor->status.hmirror = check_reg_mask(sensor->slv_addr, TIMING_TC_REG21, TIMING_TC_REG21_HMIRROR); + sensor->status.vflip = check_reg_mask(sensor->slv_addr, TIMING_TC_REG20, TIMING_TC_REG20_VFLIP); + sensor->status.colorbar = check_reg_mask(sensor->slv_addr, PRE_ISP_TEST_SETTING_1, TEST_COLOR_BAR); + sensor->status.bpc = check_reg_mask(sensor->slv_addr, 0x5000, 0x04); + sensor->status.wpc = check_reg_mask(sensor->slv_addr, 0x5000, 0x02); + sensor->status.raw_gma = check_reg_mask(sensor->slv_addr, 0x5000, 0x20); + sensor->status.lenc = check_reg_mask(sensor->slv_addr, 0x5000, 0x80); + sensor->status.quality = read_reg(sensor->slv_addr, COMPRESSION_CTRL07) & 0x3f; + sensor->status.special_effect = 0; + sensor->status.wb_mode = 0; + sensor->status.awb_gain = check_reg_mask(sensor->slv_addr, 0x3000, 0x01); + sensor->status.agc_gain = get_agc_gain(sensor); + sensor->status.aec_value = get_aec_value(sensor); + sensor->status.aec2 = check_reg_mask(sensor->slv_addr, 0x3000, 0x04); + return 0; +} + +int NT99141_init(sensor_t *sensor) +{ + sensor->reset = reset; + sensor->set_pixformat = set_pixformat; + sensor->set_framesize = set_framesize; + sensor->set_contrast = set_contrast; + sensor->set_brightness = set_brightness; + sensor->set_saturation = set_saturation; + sensor->set_sharpness = set_sharpness; + sensor->set_gainceiling = set_gainceiling; + sensor->set_quality = set_quality; + sensor->set_colorbar = set_colorbar; + sensor->set_gain_ctrl = set_gain_ctrl; + sensor->set_exposure_ctrl = set_exposure_ctrl; + sensor->set_whitebal = set_whitebal; + sensor->set_hmirror = set_hmirror; + sensor->set_vflip = set_vflip; + sensor->init_status = init_status; + sensor->set_aec2 = set_aec2; + sensor->set_aec_value = set_aec_value; + sensor->set_special_effect = set_special_effect; + sensor->set_wb_mode = set_wb_mode; + sensor->set_ae_level = set_ae_level; + sensor->set_dcw = set_dcw_dsp; + sensor->set_bpc = set_bpc_dsp; + sensor->set_wpc = set_wpc_dsp; + sensor->set_awb_gain = set_awb_gain_dsp; + sensor->set_agc_gain = set_agc_gain; + sensor->set_raw_gma = set_raw_gma_dsp; + sensor->set_lenc = set_lenc_dsp; + sensor->set_denoise = set_denoise; + + sensor->get_reg = get_reg; + sensor->set_reg = set_reg; + sensor->set_res_raw = set_res_raw; + sensor->set_pll = _set_pll; + sensor->set_xclk = set_xclk; + return 0; +} diff --git a/esp32-cam-rtos-allframes/nt99141.h b/esp32-cam-rtos-allframes/nt99141.h new file mode 100644 index 0000000..287a742 --- /dev/null +++ b/esp32-cam-rtos-allframes/nt99141.h @@ -0,0 +1,16 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * NT99141 driver. + * + */ +#ifndef __NT99141_H__ +#define __NT99141_H__ + +#include "sensor.h" + +int NT99141_init(sensor_t *sensor); + +#endif // __NT99141_H__ diff --git a/esp32-cam-rtos-allframes/nt99141_regs.h b/esp32-cam-rtos-allframes/nt99141_regs.h new file mode 100644 index 0000000..8301db9 --- /dev/null +++ b/esp32-cam-rtos-allframes/nt99141_regs.h @@ -0,0 +1,211 @@ +/* + * NT99141 register definitions. + */ +#ifndef __NT99141_REG_REGS_H__ +#define __NT99141_REG_REGS_H__ + +/* system control registers */ +#define SYSTEM_CTROL0 0x3021 // 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 + +/* output format control registers */ +#define FORMAT_CTRL 0x501F // Format select + // Bit[2:0]: + // 000: YUV422 + // 001: RGB + // 010: Dither + // 011: RAW after DPC + // 101: RAW after CIP + +/* format control registers */ +#define FORMAT_CTRL00 0x4300 + +/* frame control registers */ +#define FRAME_CTRL01 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 +#define FRAME_CTRL02 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 + +/* ISP top control registers */ +#define PRE_ISP_TEST_SETTING_1 0x3025 // 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 + +/* AEC/AGC control functions */ +#define AEC_PK_MANUAL 0x3201 // 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 + +/* mirror and flip registers */ +#define TIMING_TC_REG20 0x3022 // Timing Control Register + // Bit[2:1]: Vertical flip enable + // 00: Normal + // 11: Vertical flip + // Bit[0]: Vertical binning enable +#define TIMING_TC_REG21 0x3022 // Timing Control Register + // Bit[5]: Compression Enable + // Bit[2:1]: Horizontal mirror enable + // 00: Normal + // 11: Horizontal mirror + // Bit[0]: Horizontal binning enable + +#define CLOCK_POL_CONTROL 0x3024// 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 +#define DRIVE_CAPABILITY 0x306a // Bit[7:6]: + // 00: 1x + // 01: 2x + // 10: 3x + // 11: 4x + + +#define X_ADDR_ST_H 0x3800 //Bit[3:0]: X address start[11:8] +#define X_ADDR_ST_L 0x3801 //Bit[7:0]: X address start[7:0] +#define Y_ADDR_ST_H 0x3802 //Bit[2:0]: Y address start[10:8] +#define Y_ADDR_ST_L 0x3803 //Bit[7:0]: Y address start[7:0] +#define X_ADDR_END_H 0x3804 //Bit[3:0]: X address end[11:8] +#define X_ADDR_END_L 0x3805 //Bit[7:0]: +#define Y_ADDR_END_H 0x3806 //Bit[2:0]: Y address end[10:8] +#define Y_ADDR_END_L 0x3807 //Bit[7:0]: +// Size after scaling +#define X_OUTPUT_SIZE_H 0x3808 //Bit[3:0]: DVP output horizontal width[11:8] +#define X_OUTPUT_SIZE_L 0x3809 //Bit[7:0]: +#define Y_OUTPUT_SIZE_H 0x380a //Bit[2:0]: DVP output vertical height[10:8] +#define Y_OUTPUT_SIZE_L 0x380b //Bit[7:0]: +#define X_TOTAL_SIZE_H 0x380c //Bit[3:0]: Total horizontal size[11:8] +#define X_TOTAL_SIZE_L 0x380d //Bit[7:0]: +#define Y_TOTAL_SIZE_H 0x380e //Bit[7:0]: Total vertical size[15:8] +#define Y_TOTAL_SIZE_L 0x380f //Bit[7:0]: +#define X_OFFSET_H 0x3810 //Bit[3:0]: ISP horizontal offset[11:8] +#define X_OFFSET_L 0x3811 //Bit[7:0]: +#define Y_OFFSET_H 0x3812 //Bit[2:0]: ISP vertical offset[10:8] +#define Y_OFFSET_L 0x3813 //Bit[7:0]: +#define X_INCREMENT 0x3814 //Bit[7:4]: Horizontal odd subsample increment + //Bit[3:0]: Horizontal even subsample increment +#define Y_INCREMENT 0x3815 //Bit[7:4]: Vertical odd subsample increment + //Bit[3:0]: Vertical even subsample increment +// Size before scaling +//#define X_INPUT_SIZE (X_ADDR_END - X_ADDR_ST + 1 - (2 * X_OFFSET)) +//#define Y_INPUT_SIZE (Y_ADDR_END - Y_ADDR_ST + 1 - (2 * Y_OFFSET)) + +#define ISP_CONTROL_01 0x3021 // Bit[5]: Scale enable + // 0: Disable + // 1: Enable + +#define SCALE_CTRL_1 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 + +#define SCALE_CTRL_2 0x5602 // X_SCALE High Bits +#define SCALE_CTRL_3 0x5603 // X_SCALE Low Bits +#define SCALE_CTRL_4 0x5604 // Y_SCALE High Bits +#define SCALE_CTRL_5 0x5605 // Y_SCALE Low Bits +#define SCALE_CTRL_6 0x5606 // Bit[3:0]: V Offset + +#define PCLK_RATIO 0x3824 // Bit[4:0]: PCLK ratio manual +#define VFIFO_CTRL0C 0x460C // Bit[1]: PCLK manual enable + // 0: Auto + // 1: Manual by PCLK_RATIO + +#define VFIFO_X_SIZE_H 0x4602 +#define VFIFO_X_SIZE_L 0x4603 +#define VFIFO_Y_SIZE_H 0x4604 +#define VFIFO_Y_SIZE_L 0x4605 + +#define SC_PLLS_CTRL0 0x303a // Bit[7]: PLLS bypass +#define SC_PLLS_CTRL1 0x303b // Bit[4:0]: PLLS multiplier +#define SC_PLLS_CTRL2 0x303c // Bit[6:4]: PLLS charge pump control + // Bit[3:0]: PLLS system divider +#define SC_PLLS_CTRL3 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 + +#define COMPRESSION_CTRL00 0x4400 // +#define COMPRESSION_CTRL01 0x4401 // +#define COMPRESSION_CTRL02 0x4402 // +#define COMPRESSION_CTRL03 0x4403 // +#define COMPRESSION_CTRL04 0x4404 // +#define COMPRESSION_CTRL05 0x4405 // +#define COMPRESSION_CTRL06 0x4406 // +#define COMPRESSION_CTRL07 0x3401 // Bit[5:0]: QS +#define COMPRESSION_ISI_CTRL 0x4408 // +#define COMPRESSION_CTRL09 0x4409 // +#define COMPRESSION_CTRL0a 0x440a // +#define COMPRESSION_CTRL0b 0x440b // +#define COMPRESSION_CTRL0c 0x440c // +#define COMPRESSION_CTRL0d 0x440d // +#define COMPRESSION_CTRL0E 0x440e // + +/** + * @brief register value + */ +#define TEST_COLOR_BAR 0x02 /* Enable Color Bar roling Test */ + +#define AEC_PK_MANUAL_AGC_MANUALEN 0x02 /* Enable AGC Manual enable */ +#define AEC_PK_MANUAL_AEC_MANUALEN 0x01 /* Enable AEC Manual enable */ + +#define TIMING_TC_REG20_VFLIP 0x01 /* Vertical flip enable */ +#define TIMING_TC_REG21_HMIRROR 0x02 /* Horizontal mirror enable */ + +#endif // __NT99141_REG_REGS_H__ diff --git a/esp32-cam-rtos-allframes/nt99141_settings.h b/esp32-cam-rtos-allframes/nt99141_settings.h new file mode 100644 index 0000000..1ffec20 --- /dev/null +++ b/esp32-cam-rtos-allframes/nt99141_settings.h @@ -0,0 +1,825 @@ +#ifndef _NT99141_SETTINGS_H_ +#define _NT99141_SETTINGS_H_ + +#include +#include +#include "esp_attr.h" +#include "nt99141_regs.h" + +static const ratio_settings_t ratio_table[] = { + // mw, mh, sx, sy, ex, ey, ox, oy, tx, ty + { 1280, 720, 0, 4, 1283, 723, 0, 4, 1660, 963 }, + +}; + +#define REG_DLY 0xffff +#define REGLIST_TAIL 0x0000 + +static const DRAM_ATTR uint16_t sensor_default_regs[][2] = { + //initial +{0x3021, 0x00}, +{REG_DLY, 100}, // delay 100ms +{0x3109, 0x04}, +{0x3040, 0x04}, +{0x3041, 0x02}, +{0x3042, 0xFF}, +{0x3043, 0x08}, +{0x3052, 0xE0}, +{0x305F, 0x33}, +{0x3100, 0x07}, +{0x3106, 0x03}, +{0x3105, 0x01}, +{0x3108, 0x05}, +{0x3110, 0x22}, +{0x3111, 0x57}, +{0x3112, 0x22}, +{0x3113, 0x55}, +{0x3114, 0x05}, +{0x3135, 0x00}, +{0x32F0, 0x01}, +{0x3290, 0x01}, +{0x3291, 0x80}, +{0x3296, 0x01}, +{0x3297, 0x73}, +{0x3250, 0x80}, +{0x3251, 0x03}, +{0x3252, 0xFF}, +{0x3253, 0x00}, +{0x3254, 0x03}, +{0x3255, 0xFF}, +{0x3256, 0x00}, +{0x3257, 0x50}, +{0x3270, 0x00}, +{0x3271, 0x0C}, +{0x3272, 0x18}, +{0x3273, 0x32}, +{0x3274, 0x44}, +{0x3275, 0x54}, +{0x3276, 0x70}, +{0x3277, 0x88}, +{0x3278, 0x9D}, +{0x3279, 0xB0}, +{0x327A, 0xCF}, +{0x327B, 0xE2}, +{0x327C, 0xEF}, +{0x327D, 0xF7}, +{0x327E, 0xFF}, +{0x3302, 0x00}, +{0x3303, 0x40}, +{0x3304, 0x00}, +{0x3305, 0x96}, +{0x3306, 0x00}, +{0x3307, 0x29}, +{0x3308, 0x07}, +{0x3309, 0xBA}, +{0x330A, 0x06}, +{0x330B, 0xF5}, +{0x330C, 0x01}, +{0x330D, 0x51}, +{0x330E, 0x01}, +{0x330F, 0x30}, +{0x3310, 0x07}, +{0x3311, 0x16}, +{0x3312, 0x07}, +{0x3313, 0xBA}, +{0x3326, 0x02}, +{0x32F6, 0x0F}, +{0x32F9, 0x42}, +{0x32FA, 0x24}, +{0x3325, 0x4A}, +{0x3330, 0x00}, +{0x3331, 0x0A}, +{0x3332, 0xFF}, +{0x3338, 0x30}, +{0x3339, 0x84}, +{0x333A, 0x48}, +{0x333F, 0x07}, +{0x3360, 0x10}, +{0x3361, 0x18}, +{0x3362, 0x1f}, +{0x3363, 0x37}, +{0x3364, 0x80}, +{0x3365, 0x80}, +{0x3366, 0x68}, +{0x3367, 0x60}, +{0x3368, 0x30}, +{0x3369, 0x28}, +{0x336A, 0x20}, +{0x336B, 0x10}, +{0x336C, 0x00}, +{0x336D, 0x20}, +{0x336E, 0x1C}, +{0x336F, 0x18}, +{0x3370, 0x10}, +{0x3371, 0x38}, +{0x3372, 0x3C}, +{0x3373, 0x3F}, +{0x3374, 0x3F}, +{0x338A, 0x34}, +{0x338B, 0x7F}, +{0x338C, 0x10}, +{0x338D, 0x23}, +{0x338E, 0x7F}, +{0x338F, 0x14}, +{0x3375, 0x08}, +{0x3376, 0x0C}, +{0x3377, 0x18}, +{0x3378, 0x20}, +{0x3012, 0x02}, +{0x3013, 0xD0}, +{0x3025, 0x02}, //colorbar +{REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_fmt_jpeg[][2] = { + {0x32F0, 0x70}, // YUV422 + {REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_fmt_raw[][2] = { + {0x32F0, 0x50}, // RAW + {REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_fmt_grayscale[][2] = { + {0x32F1, 0x01}, + {REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_fmt_yuv422[][2] = { + {0x32F0, 0x00}, // YUV422 + {REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_fmt_rgb565[][2] = { + {0x32F0, 0x01}, // RGB + {REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint8_t sensor_saturation_levels[9][1] = { + {0x60},//-4 + {0x68},//-3 + {0x70},//-2 + {0x78},//-1 + {0x80},//0 + {0x88},//+1 + {0x90},//+2 + {0x98},//+3 + {0xA0},//+4 +}; + +static const DRAM_ATTR uint8_t sensor_special_effects[7][4] = { + {0x00, 0x80, 0x80, 0x01},//Normal + {0x03, 0x80, 0x80, 0x01},//Negative + {0x01, 0x80, 0x80, 0x01},//Grayscale + {0x05, 0x2A, 0xF0, 0x01},//Red Tint + {0x05, 0x60, 0x20, 0x01},//Green Tint + {0x05, 0xF0, 0x80, 0x01},//Blue Tint + {0x02, 0x80, 0x80, 0x01},//Sepia + +}; + +// AE LEVEL +static const DRAM_ATTR uint16_t sensor_ae_level[][2] = { + +// 1. [AE_Target : 0x24] +// Set_Device_Format = FORMAT_16_8 +// SET_Device_Addr = 0x54 + {0x32B8, 0x29 }, + {0x32B9, 0x1F }, + {0x32BC, 0x24 }, + {0x32BD, 0x27 }, + {0x32BE, 0x21 }, +//------------------------------------------------------------------------ +// 2. [AE_Target : 0x28] +// Set_Device_Format = FORMAT_16_8 +// SET_Device_Addr = 0x54 + {0x32B8, 0x2D }, + {0x32B9, 0x23 }, + {0x32BC, 0x28 }, + {0x32BD, 0x2B }, + {0x32BE, 0x25 }, +//------------------------------------------------------------------------ +// 3. [AE_Target : 0x2C] +// Set_Device_Format = FORMAT_16_8 +// SET_Device_Addr = 0x54 + {0x32B8, 0x32 }, + {0x32B9, 0x26 }, + {0x32BC, 0x2C }, + {0x32BD, 0x2F }, + {0x32BE, 0x29 }, +//------------------------------------------------------------------------ +// 4, [AE_Target : 0x30] +// Set_Device_Format = FORMAT_16_8 +// SET_Device_Addr = 0x54 + {0x32B8, 0x36 }, + {0x32B9, 0x2A }, + {0x32BC, 0x30 }, + {0x32BD, 0x33 }, + {0x32BE, 0x2D }, +//------------------------------------------------------------------------ +// 5. [AE_Target : 0x34] +// Set_Device_Format = FORMAT_16_8 +// SET_Device_Addr = 0x54 + {0x32B8, 0x3B }, + {0x32B9, 0x2D }, + {0x32BC, 0x34 }, + {0x32BD, 0x38 }, + {0x32BE, 0x30 }, +//------------------------------------------------------------------------ +// 6. [AE_Target : 0x38] +// Set_Device_Format = FORMAT_16_8 +// SET_Device_Addr = 0x54 + {0x32B8, 0x3F }, + {0x32B9, 0x31 }, + {0x32BC, 0x38 }, + {0x32BD, 0x3C }, + {0x32BE, 0x34 }, +//------------------------------------------------------------------------ +// 7. [AE_Target : 0x3D] +// Set_Device_Format = FORMAT_16_8 +// SET_Device_Addr = 0x54 + {0x32B8, 0x44 }, + {0x32B9, 0x34 }, + {0x32BC, 0x3C }, + {0x32BD, 0x40 }, + {0x32BE, 0x38 }, +//------------------------------------------------------------------------ +// 8. [AE_Target : 0x40] +// Set_Device_Format = FORMAT_16_8 +// SET_Device_Addr = 0x54 + {0x32B8, 0x48 }, + {0x32B9, 0x38 }, + {0x32BC, 0x40 }, + {0x32BD, 0x44 }, + {0x32BE, 0x3C }, +//------------------------------------------------------------------------ +// 9. [AE_Target : 0x44] +// Set_Device_Format = FORMAT_16_8 +// SET_Device_Addr = 0x54 + {0x32B8, 0x4D }, + {0x32B9, 0x3B }, + {0x32BC, 0x44 }, + {0x32BD, 0x49 }, + {0x32BE, 0x3F }, +}; + +static const DRAM_ATTR uint16_t sensor_framesize_HD[][2] = { +//[JPEG_1280x720_8.18_8.18_Fps] +{0x3021, 0x00}, +{REG_DLY, 100}, // delay 100ms +{0x32BF, 0x60}, +{0x32C0, 0x5A}, +{0x32C1, 0x5A}, +{0x32C2, 0x5A}, +{0x32C3, 0x00}, +{0x32C4, 0x20}, +{0x32C5, 0x20}, +{0x32C6, 0x20}, +{0x32C7, 0x00}, +{0x32C8, 0x3C}, +{0x32C9, 0x5A}, +{0x32CA, 0x7A}, +{0x32CB, 0x7A}, +{0x32CC, 0x7A}, +{0x32CD, 0x7A}, +{0x32DB, 0x5E}, +{0x32F0, 0x70}, +{0x3400, 0x08}, +{0x3400, 0x00}, +{0x3401, 0x4E}, +{0x3404, 0x00}, +{0x3405, 0x00}, +{0x3410, 0x00}, +{0x3200, 0x3E}, +{0x3201, 0x0F}, +{0x3028, 0x0F}, +{0x3029, 0x00}, +{0x302A, 0x08}, +{0x3022, 0x24}, +{0x3023, 0x24}, +{0x3002, 0x00}, +{0x3003, 0x04}, +{0x3004, 0x00}, +{0x3005, 0x04}, +{0x3006, 0x05}, +{0x3007, 0x03}, +{0x3008, 0x02}, +{0x3009, 0xD3}, +{0x300A, 0x06}, +{0x300B, 0x7C}, +{0x300C, 0x02}, +{0x300D, 0xE0}, +{0x300E, 0x05}, +{0x300F, 0x00}, +{0x3010, 0x02}, +{0x3011, 0xD0}, +{0x32B8, 0x3F}, +{0x32B9, 0x31}, +{0x32BB, 0x87}, +{0x32BC, 0x38}, +{0x32BD, 0x3C}, +{0x32BE, 0x34}, +{0x3201, 0x3F}, +{0x3021, 0x06}, +{0x3025, 0x00}, //normal +{0x3400, 0x01}, +{0x3060, 0x01}, +{REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_framesize_VGA[][2] = { +//[JPEG_640x480_10.14_10.14_Fps] +{0x3021, 0x00}, +{REG_DLY, 100}, // delay 100ms +{0x32BF, 0x60}, +{0x32C0, 0x5A}, +{0x32C1, 0x5A}, +{0x32C2, 0x5A}, +{0x32C3, 0x00}, +{0x32C4, 0x20}, +{0x32C5, 0x20}, +{0x32C6, 0x20}, +{0x32C7, 0x00}, +{0x32C8, 0x4B}, +{0x32C9, 0x5A}, +{0x32CA, 0x7A}, +{0x32CB, 0x7A}, +{0x32CC, 0x7A}, +{0x32CD, 0x7A}, +{0x32DB, 0x62}, +{0x32F0, 0x70}, +{0x3400, 0x08}, +{0x3400, 0x00}, +{0x3401, 0x4E}, +{0x3404, 0x00}, +{0x3405, 0x00}, +{0x3410, 0x00}, +{0x32E0, 0x02}, +{0x32E1, 0x80}, +{0x32E2, 0x01}, +{0x32E3, 0xE0}, +{0x32E4, 0x00}, +{0x32E5, 0x80}, +{0x32E6, 0x00}, +{0x32E7, 0x80}, +{0x3200, 0x3E}, +{0x3201, 0x0F}, +{0x3028, 0x0F}, +{0x3029, 0x00}, +{0x302A, 0x08}, +{0x3022, 0x24}, +{0x3023, 0x24}, +{0x3002, 0x00}, +{0x3003, 0xA4}, +{0x3004, 0x00}, +{0x3005, 0x04}, +{0x3006, 0x04}, +{0x3007, 0x63}, +{0x3008, 0x02}, +{0x3009, 0xD3}, +{0x300A, 0x05}, +{0x300B, 0x3C}, +{0x300C, 0x02}, +{0x300D, 0xE0}, +{0x300E, 0x03}, +{0x300F, 0xC0}, +{0x3010, 0x02}, +{0x3011, 0xD0}, +{0x32B8, 0x3F}, +{0x32B9, 0x31}, +{0x32BB, 0x87}, +{0x32BC, 0x38}, +{0x32BD, 0x3C}, +{0x32BE, 0x34}, +{0x3201, 0x7F}, +{0x3021, 0x06}, +{0x3025, 0x00}, //normal +{0x3400, 0x01}, +{0x3060, 0x01}, +{REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_framesize_QVGA[][2] = { +//[JPEG_320x240_10.14_10.14_Fps] +{0x3021, 0x00}, +{REG_DLY, 100}, // delay 100ms +{0x32BF, 0x60}, +{0x32C0, 0x5A}, +{0x32C1, 0x5A}, +{0x32C2, 0x5A}, +{0x32C3, 0x00}, +{0x32C4, 0x20}, +{0x32C5, 0x20}, +{0x32C6, 0x20}, +{0x32C7, 0x00}, +{0x32C8, 0x4B}, +{0x32C9, 0x5A}, +{0x32CA, 0x7A}, +{0x32CB, 0x7A}, +{0x32CC, 0x7A}, +{0x32CD, 0x7A}, +{0x32DB, 0x62}, +{0x32F0, 0x70}, +{0x3400, 0x08}, +{0x3400, 0x00}, +{0x3401, 0x4E}, +{0x3404, 0x00}, +{0x3405, 0x00}, +{0x3410, 0x00}, +{0x32E0, 0x01}, +{0x32E1, 0x40}, +{0x32E2, 0x00}, +{0x32E3, 0xF0}, +{0x32E4, 0x02}, +{0x32E5, 0x02}, +{0x32E6, 0x02}, +{0x32E7, 0x03}, +{0x3200, 0x3E}, +{0x3201, 0x0F}, +{0x3028, 0x0F}, +{0x3029, 0x00}, +{0x302A, 0x08}, +{0x3022, 0x24}, +{0x3023, 0x24}, +{0x3002, 0x00}, +{0x3003, 0xA4}, +{0x3004, 0x00}, +{0x3005, 0x04}, +{0x3006, 0x04}, +{0x3007, 0x63}, +{0x3008, 0x02}, +{0x3009, 0xD3}, +{0x300A, 0x05}, +{0x300B, 0x3C}, +{0x300C, 0x02}, +{0x300D, 0xE0}, +{0x300E, 0x03}, +{0x300F, 0xC0}, +{0x3010, 0x02}, +{0x3011, 0xD0}, +{0x32B8, 0x3F}, +{0x32B9, 0x31}, +{0x32BB, 0x87}, +{0x32BC, 0x38}, +{0x32BD, 0x3C}, +{0x32BE, 0x34}, +{0x3201, 0x7F}, +{0x3021, 0x06}, +{0x3025, 0x00}, //normal +{0x3400, 0x01}, +{0x3060, 0x01}, +{REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_framesize_VGA_xyskip[][2] = { +// [JPEG_640x360_20.00_25.01_Fps_XY_Skip] +// Set_Device_Format = FORMAT_16_8 +// SET_Device_Addr = 0x54 +{0x3021, 0x00}, +{REG_DLY, 100}, // delay 100ms +{0x32BF, 0x60 }, +{0x320A, 0xB2 }, +{0x32C0, 0x64 }, +{0x32C1, 0x64 }, +{0x32C2, 0x64 }, +{0x32C3, 0x00 }, +{0x32C4, 0x20 }, +{0x32C5, 0x20 }, +{0x32C6, 0x20 }, +{0x32C7, 0x00 }, +{0x32C8, 0x62 }, +{0x32C9, 0x64 }, +{0x32CA, 0x84 }, +{0x32CB, 0x84 }, +{0x32CC, 0x84 }, +{0x32CD, 0x84 }, +{0x32DB, 0x68 }, +{0x32F0, 0x70 }, +{0x3400, 0x08 }, +{0x3400, 0x00 }, +{0x3401, 0x4E }, +{0x3404, 0x00 }, +{0x3405, 0x00 }, +{0x3410, 0x00 }, +{0x3200, 0x3E }, +{0x3201, 0x0F }, +{0x3028, 0x0F }, +{0x3029, 0x00 }, +{0x302A, 0x08 }, +{0x3022, 0x24 }, +{0x3023, 0x6C }, +{0x3002, 0x00 }, +{0x3003, 0x04 }, +{0x3004, 0x00 }, +{0x3005, 0x04 }, +{0x3006, 0x05 }, +{0x3007, 0x03 }, +{0x3008, 0x02 }, +{0x3009, 0xD3 }, +{0x300A, 0x03 }, +{0x300B, 0xFC }, +{0x300C, 0x01 }, +{0x300D, 0x88 }, +{0x300E, 0x02 }, +{0x300F, 0x80 }, +{0x3010, 0x01 }, +{0x3011, 0x68 }, +{0x32B8, 0x3F }, +{0x32B9, 0x31 }, +{0x32BB, 0x87 }, +{0x32BC, 0x38 }, +{0x32BD, 0x3C }, +{0x32BE, 0x34 }, +{0x3201, 0x3F }, +{0x3025, 0x00 }, //normal +{0x3021, 0x06 }, +{0x3400, 0x01 }, +{0x3060, 0x01 }, +{REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_framesize_VGA_xskip[][2] = { +//[JPEG_640x480_Xskip_13.32_13.32_Fps] +{0x3021, 0x00}, +{REG_DLY, 100}, // delay 100ms +{0x32BF, 0x60}, +{0x32C0, 0x5A}, +{0x32C1, 0x5A}, +{0x32C2, 0x5A}, +{0x32C3, 0x00}, +{0x32C4, 0x20}, +{0x32C5, 0x20}, +{0x32C6, 0x20}, +{0x32C7, 0x00}, +{0x32C8, 0x62}, +{0x32C9, 0x5A}, +{0x32CA, 0x7A}, +{0x32CB, 0x7A}, +{0x32CC, 0x7A}, +{0x32CD, 0x7A}, +{0x32DB, 0x68}, +{0x32F0, 0x70}, +{0x3400, 0x08}, +{0x3400, 0x00}, +{0x3401, 0x4E}, +{0x3404, 0x00}, +{0x3405, 0x00}, +{0x3410, 0x00}, +{0x32E0, 0x02}, +{0x32E1, 0x80}, +{0x32E2, 0x01}, +{0x32E3, 0xE0}, +{0x32E4, 0x00}, +{0x32E5, 0x00}, +{0x32E6, 0x00}, +{0x32E7, 0x80}, +{0x3200, 0x3E}, +{0x3201, 0x0F}, +{0x3028, 0x0F}, +{0x3029, 0x00}, +{0x302A, 0x08}, +{0x3022, 0x24}, +{0x3023, 0x2C}, +{0x3002, 0x00}, +{0x3003, 0x04}, +{0x3004, 0x00}, +{0x3005, 0x04}, +{0x3006, 0x05}, +{0x3007, 0x03}, +{0x3008, 0x02}, +{0x3009, 0xD3}, +{0x300A, 0x03}, +{0x300B, 0xFC}, +{0x300C, 0x02}, +{0x300D, 0xE0}, +{0x300E, 0x02}, +{0x300F, 0x80}, +{0x3010, 0x02}, +{0x3011, 0xD0}, +{0x32B8, 0x3F}, +{0x32B9, 0x31}, +{0x32BB, 0x87}, +{0x32BC, 0x38}, +{0x32BD, 0x3C}, +{0x32BE, 0x34}, +{0x3201, 0x7F}, +{0x3021, 0x06}, +{0x3025, 0x00}, //normal +{0x3400, 0x01}, +{0x3060, 0x01}, +{REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_framesize_QVGA_xskip[][2] = { +{0x3021, 0x00}, +{REG_DLY, 100}, // delay 100ms +//[JPEG_320x240_Xskip_13.32_13.32_Fps] +{0x32BF, 0x60}, +{0x32C0, 0x5A}, +{0x32C1, 0x5A}, +{0x32C2, 0x5A}, +{0x32C3, 0x00}, +{0x32C4, 0x20}, +{0x32C5, 0x20}, +{0x32C6, 0x20}, +{0x32C7, 0x00}, +{0x32C8, 0x62}, +{0x32C9, 0x5A}, +{0x32CA, 0x7A}, +{0x32CB, 0x7A}, +{0x32CC, 0x7A}, +{0x32CD, 0x7A}, +{0x32DB, 0x68}, +{0x32F0, 0x70}, +{0x3400, 0x08}, +{0x3400, 0x00}, +{0x3401, 0x4E}, +{0x3404, 0x00}, +{0x3405, 0x00}, +{0x3410, 0x00}, +{0x32E0, 0x01}, +{0x32E1, 0x40}, +{0x32E2, 0x00}, +{0x32E3, 0xF0}, +{0x32E4, 0x01}, +{0x32E5, 0x01}, +{0x32E6, 0x02}, +{0x32E7, 0x03}, +{0x3200, 0x3E}, +{0x3201, 0x0F}, +{0x3028, 0x0F}, +{0x3029, 0x00}, +{0x302A, 0x08}, +{0x3022, 0x24}, +{0x3023, 0x2C}, +{0x3002, 0x00}, +{0x3003, 0x04}, +{0x3004, 0x00}, +{0x3005, 0x04}, +{0x3006, 0x05}, +{0x3007, 0x03}, +{0x3008, 0x02}, +{0x3009, 0xD3}, +{0x300A, 0x03}, +{0x300B, 0xFC}, +{0x300C, 0x02}, +{0x300D, 0xE0}, +{0x300E, 0x02}, +{0x300F, 0x80}, +{0x3010, 0x02}, +{0x3011, 0xD0}, +{0x32B8, 0x3F}, +{0x32B9, 0x31}, +{0x32BB, 0x87}, +{0x32BC, 0x38}, +{0x32BD, 0x3C}, +{0x32BE, 0x34}, +{0x3201, 0x7F}, +{0x3021, 0x06}, +{0x3025, 0x00}, //normal +{0x3400, 0x01}, +{0x3060, 0x01}, +{REGLIST_TAIL, 0x00}, // tail +}; + + +static const DRAM_ATTR uint16_t sensor_framesize_VGA_crop[][2] = { +//[JPEG_640x480_Crop_19.77_19.77_Fps] +{0x3021, 0x00}, +{REG_DLY, 100}, // delay 100ms +{0x32BF, 0x60}, +{0x32C0, 0x5A}, +{0x32C1, 0x5A}, +{0x32C2, 0x5A}, +{0x32C3, 0x00}, +{0x32C4, 0x20}, +{0x32C5, 0x20}, +{0x32C6, 0x20}, +{0x32C7, 0x00}, +{0x32C8, 0x62}, +{0x32C9, 0x5A}, +{0x32CA, 0x7A}, +{0x32CB, 0x7A}, +{0x32CC, 0x7A}, +{0x32CD, 0x7A}, +{0x32DB, 0x68}, +{0x32F0, 0x70}, +{0x3400, 0x08}, +{0x3400, 0x00}, +{0x3401, 0x4E}, +{0x3404, 0x00}, +{0x3405, 0x00}, +{0x3410, 0x00}, +{0x3200, 0x3E}, +{0x3201, 0x0F}, +{0x3028, 0x0F}, +{0x3029, 0x00}, +{0x302A, 0x08}, +{0x3022, 0x24}, +{0x3023, 0x24}, +{0x3002, 0x01}, +{0x3003, 0x44}, +{0x3004, 0x00}, +{0x3005, 0x7C}, +{0x3006, 0x03}, +{0x3007, 0xC3}, +{0x3008, 0x02}, +{0x3009, 0x5B}, +{0x300A, 0x03}, +{0x300B, 0xFC}, +{0x300C, 0x01}, +{0x300D, 0xF0}, +{0x300E, 0x02}, +{0x300F, 0x80}, +{0x3010, 0x01}, +{0x3011, 0xE0}, +{0x32B8, 0x3F}, +{0x32B9, 0x31}, +{0x32BB, 0x87}, +{0x32BC, 0x38}, +{0x32BD, 0x3C}, +{0x32BE, 0x34}, +{0x3201, 0x3F}, +{0x3021, 0x06}, +{0x3025, 0x00}, //normal +{0x3400, 0x01}, +{0x3060, 0x01}, +{REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_framesize_QVGA_crop[][2] = { +//[JPEG_320x240_Crop_19.77_19.77_Fps] +{0x3021, 0x00}, +{REG_DLY, 100}, // delay 100ms +{0x32BF, 0x60}, +{0x32C0, 0x5A}, +{0x32C1, 0x5A}, +{0x32C2, 0x5A}, +{0x32C3, 0x00}, +{0x32C4, 0x20}, +{0x32C5, 0x20}, +{0x32C6, 0x20}, +{0x32C7, 0x00}, +{0x32C8, 0x62}, +{0x32C9, 0x5A}, +{0x32CA, 0x7A}, +{0x32CB, 0x7A}, +{0x32CC, 0x7A}, +{0x32CD, 0x7A}, +{0x32DB, 0x68}, +{0x32F0, 0x70}, +{0x3400, 0x08}, +{0x3400, 0x00}, +{0x3401, 0x4E}, +{0x3404, 0x00}, +{0x3405, 0x00}, +{0x3410, 0x00}, +{0x32E0, 0x01}, +{0x32E1, 0x40}, +{0x32E2, 0x00}, +{0x32E3, 0xF0}, +{0x32E4, 0x01}, +{0x32E5, 0x01}, +{0x32E6, 0x01}, +{0x32E7, 0x02}, +{0x3200, 0x3E}, +{0x3201, 0x0F}, +{0x3028, 0x0F}, +{0x3029, 0x00}, +{0x302A, 0x08}, +{0x3022, 0x24}, +{0x3023, 0x24}, +{0x3002, 0x01}, +{0x3003, 0x44}, +{0x3004, 0x00}, +{0x3005, 0x7C}, +{0x3006, 0x03}, +{0x3007, 0xC3}, +{0x3008, 0x02}, +{0x3009, 0x5B}, +{0x300A, 0x03}, +{0x300B, 0xFC}, +{0x300C, 0x01}, +{0x300D, 0xF0}, +{0x300E, 0x02}, +{0x300F, 0x80}, +{0x3010, 0x01}, +{0x3011, 0xE0}, +{0x32B8, 0x3F}, +{0x32B9, 0x31}, +{0x32BB, 0x87}, +{0x32BC, 0x38}, +{0x32BD, 0x3C}, +{0x32BE, 0x34}, +{0x3201, 0x7F}, +{0x3021, 0x06}, +{0x3025, 0x00}, //normal +{0x3400, 0x01}, +{0x3060, 0x01}, +{REGLIST_TAIL, 0x00}, // tail +}; + +#endif + + diff --git a/esp32-cam-rtos-allframes/ov7670.c b/esp32-cam-rtos-allframes/ov7670.c new file mode 100644 index 0000000..285fe13 --- /dev/null +++ b/esp32-cam-rtos-allframes/ov7670.c @@ -0,0 +1,439 @@ +/* + * This file is part of the OpenMV project. + * author: Juan Schiavoni + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * OV7725 driver. + * + */ +#include +#include +#include +#include "sccb.h" +#include "ov7670.h" +#include "ov7670_regs.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#else +#include "esp_log.h" +static const char* TAG = "ov7760"; +#endif + +static int ov7670_clkrc = 0x01; + +/* + * The default register settings, as obtained from OmniVision. There + * is really no making sense of most of these - lots of "reserved" values + * and such. + * + * These settings give VGA YUYV. + */ +struct regval_list { + uint8_t reg_num; + uint8_t value; +}; + +static struct regval_list ov7670_default_regs[] = { + /* Sensor automatically sets output window when resolution changes. */ + {TSLB, 0x04}, + + /* Frame rate 30 fps at 12 Mhz clock */ + {CLKRC, 0x00}, + {DBLV, 0x4A}, + + {COM10, COM10_VSYNC_NEG | COM10_PCLK_MASK}, + + /* Improve white balance */ + {COM4, 0x40}, + + /* Improve color */ + {RSVD_B0, 0x84}, + + /* Enable 50/60 Hz auto detection */ + {COM11, COM11_EXP|COM11_HZAUTO}, + + /* Disable some delays */ + {HSYST, 0}, + {HSYEN, 0}, + + {MVFP, MVFP_SUN}, + + /* More reserved magic, some of which tweaks white balance */ + {AWBC1, 0x0a}, + {AWBC2, 0xf0}, + {AWBC3, 0x34}, + {AWBC4, 0x58}, + {AWBC5, 0x28}, + {AWBC6, 0x3a}, + + {AWBCTR3, 0x0a}, + {AWBCTR2, 0x55}, + {AWBCTR1, 0x11}, + {AWBCTR0, 0x9e}, + + {COM8, COM8_FAST_AUTO|COM8_STEP_UNLIMIT|COM8_AGC_EN|COM8_AEC_EN|COM8_AWB_EN}, + + /* End marker is FF because in ov7670 the address of GAIN 0 and default value too. */ + {0xFF, 0xFF}, +}; + +static struct regval_list ov7670_fmt_yuv422[] = { + { COM7, 0x0 }, /* Selects YUV mode */ + { RGB444, 0 }, /* No RGB444 please */ + { COM1, 0 }, /* CCIR601 */ + { COM15, COM15_R00FF }, + { MVFP, MVFP_SUN }, + { COM9, 0x6A }, /* 128x gain ceiling; 0x8 is reserved bit */ + { MTX1, 0x80 }, /* "matrix coefficient 1" */ + { MTX2, 0x80 }, /* "matrix coefficient 2" */ + { MTX3, 0 }, /* vb */ + { MTX4, 0x22 }, /* "matrix coefficient 4" */ + { MTX5, 0x5e }, /* "matrix coefficient 5" */ + { MTX6, 0x80 }, /* "matrix coefficient 6" */ + { COM13, COM13_UVSAT }, + { 0xff, 0xff }, /* END MARKER */ +}; + +static struct regval_list ov7670_fmt_rgb565[] = { + { COM7, COM7_FMT_RGB565 }, /* Selects RGB mode */ + { RGB444, 0 }, /* No RGB444 please */ + { COM1, 0x0 }, /* CCIR601 */ + { COM15, COM15_RGB565 |COM15_R00FF }, + { MVFP, MVFP_SUN }, + { COM9, 0x6A }, /* 128x gain ceiling; 0x8 is reserved bit */ + { MTX1, 0xb3 }, /* "matrix coefficient 1" */ + { MTX2, 0xb3 }, /* "matrix coefficient 2" */ + { MTX3, 0 }, /* vb */ + { MTX4, 0x3d }, /* "matrix coefficient 4" */ + { MTX5, 0xa7 }, /* "matrix coefficient 5" */ + { MTX6, 0xe4 }, /* "matrix coefficient 6" */ + { COM13, COM13_UVSAT }, + { 0xff, 0xff }, /* END MARKER */ +}; + + +static struct regval_list ov7670_vga[] = { + { COM3, 0x00 }, + { COM14, 0x00 }, + { SCALING_XSC, 0x3A }, + { SCALING_YSC, 0x35 }, + { SCALING_DCWCTR, 0x11 }, + { SCALING_PCLK_DIV, 0xF0 }, + { SCALING_PCLK_DELAY, 0x02 }, + { 0xff, 0xff }, +}; + +static struct regval_list ov7670_qvga[] = { + { COM3, 0x04 }, + { COM14, 0x19 }, + { SCALING_XSC, 0x3A }, + { SCALING_YSC, 0x35 }, + { SCALING_DCWCTR, 0x11 }, + { SCALING_PCLK_DIV, 0xF1 }, + { SCALING_PCLK_DELAY, 0x02 }, + { 0xff, 0xff }, +}; + +static struct regval_list ov7670_qqvga[] = { + { COM3, 0x04 }, //DCW enable + { COM14, 0x1a }, //pixel clock divided by 4, manual scaling enable, DCW and PCLK controlled by register + { SCALING_XSC, 0x3a }, + { SCALING_YSC, 0x35 }, + { SCALING_DCWCTR, 0x22 }, //downsample by 4 + { SCALING_PCLK_DIV, 0xf2 }, //pixel clock divided by 4 + { SCALING_PCLK_DELAY, 0x02 }, + { 0xff, 0xff }, +}; + +/* + * Write a list of register settings; ff/ff stops the process. + */ +static int ov7670_write_array(sensor_t *sensor, struct regval_list *vals) +{ +int ret = 0; + + while ( (vals->reg_num != 0xff || vals->value != 0xff) && (ret == 0) ) { + ret = SCCB_Write(sensor->slv_addr, vals->reg_num, vals->value); + + ESP_LOGD(TAG, "reset reg %02X, W(%02X) R(%02X)", vals->reg_num, + vals->value, SCCB_Read(sensor->slv_addr, vals->reg_num) ); + + vals++; + } + + return ret; +} + +/* + * Calculate the frame control registers. + */ +static int ov7670_frame_control(sensor_t *sensor, int hstart, int hstop, int vstart, int vstop) +{ +struct regval_list frame[7]; + + frame[0].reg_num = HSTART; + frame[0].value = (hstart >> 3); + + frame[1].reg_num = HSTOP; + frame[1].value = (hstop >> 3); + + frame[2].reg_num = HREF; + frame[2].value = (((hstop & 0x07) << 3) | (hstart & 0x07)); + + frame[3].reg_num = VSTART; + frame[3].value = (vstart >> 2); + + frame[4].reg_num = VSTOP; + frame[4].value = (vstop >> 2); + + frame[5].reg_num = VREF; + frame[5].value = (((vstop & 0x02) << 2) | (vstart & 0x02)); + + /* End mark */ + frame[5].reg_num = 0xFF; + frame[5].value = 0xFF; + + return ov7670_write_array(sensor, frame); +} + +static int reset(sensor_t *sensor) +{ + int ret; + + // Reset all registers + SCCB_Write(sensor->slv_addr, COM7, COM7_RESET); + + // Delay 10 ms + vTaskDelay(10 / portTICK_PERIOD_MS); + + ret = ov7670_write_array(sensor, ov7670_default_regs); + + // Delay + vTaskDelay(30 / portTICK_PERIOD_MS); + + return ret; +} + +static int set_pixformat(sensor_t *sensor, pixformat_t pixformat) +{ +int ret; + + switch (pixformat) { + case PIXFORMAT_RGB565: + case PIXFORMAT_RGB888: + ret = ov7670_write_array(sensor, ov7670_fmt_rgb565); + break; + + case PIXFORMAT_YUV422: + case PIXFORMAT_GRAYSCALE: + default: + ret = ov7670_write_array(sensor, ov7670_fmt_yuv422); + break; + } + + vTaskDelay(30 / portTICK_PERIOD_MS); + + /* + * If we're running RGB565, we must rewrite clkrc after setting + * the other parameters or the image looks poor. If we're *not* + * doing RGB565, we must not rewrite clkrc or the image looks + * *really* poor. + * + * (Update) Now that we retain clkrc state, we should be able + * to write it unconditionally, and that will make the frame + * rate persistent too. + */ + if (pixformat == PIXFORMAT_RGB565) { + ret = SCCB_Write(sensor->slv_addr, CLKRC, ov7670_clkrc); + } + + return ret; +} + +static int set_framesize(sensor_t *sensor, framesize_t framesize) +{ + int ret; + + // store clkrc before changing window settings... + ov7670_clkrc = SCCB_Read(sensor->slv_addr, CLKRC); + + switch (framesize){ + case FRAMESIZE_VGA: + if( (ret = ov7670_write_array(sensor, ov7670_vga)) == 0 ) { + /* These values from Omnivision */ + ret = ov7670_frame_control(sensor, 158, 14, 10, 490); + } + break; + case FRAMESIZE_QVGA: + if( (ret = ov7670_write_array(sensor, ov7670_qvga)) == 0 ) { + /* These values from Omnivision */ + ret = ov7670_frame_control(sensor, 158, 14, 10, 490); + } + break; + case FRAMESIZE_QQVGA: + if( (ret = ov7670_write_array(sensor, ov7670_qqvga)) == 0 ) { + /* These values from Omnivision */ + ret = ov7670_frame_control(sensor, 158, 14, 10, 490); + } + break; + + default: + ret = -1; + } + + vTaskDelay(30 / portTICK_PERIOD_MS); + + if (ret == 0) { + sensor->status.framesize = framesize; + } + + return ret; +} + +static int set_colorbar(sensor_t *sensor, int enable) +{ + uint8_t ret = 0; + // Read register scaling_xsc + uint8_t reg = SCCB_Read(sensor->slv_addr, SCALING_XSC); + + // Pattern to set color bar bit[0]=0 in every case + reg = SCALING_XSC_CBAR(reg); + + // Write pattern to SCALING_XSC + ret = SCCB_Write(sensor->slv_addr, SCALING_XSC, reg); + + // Read register scaling_ysc + reg = SCCB_Read(sensor->slv_addr, SCALING_YSC); + + // Pattern to set color bar bit[0]=0 in every case + reg = SCALING_YSC_CBAR(reg, enable); + + // Write pattern to SCALING_YSC + ret = ret | SCCB_Write(sensor->slv_addr, SCALING_YSC, reg); + + // return 0 or 0xFF + return ret; +} + +static int set_whitebal(sensor_t *sensor, int enable) +{ + // Read register COM8 + uint8_t reg = SCCB_Read(sensor->slv_addr, COM8); + + // Set white bal on/off + reg = COM8_SET_AWB(reg, enable); + + // Write back register COM8 + return SCCB_Write(sensor->slv_addr, COM8, reg); +} + +static int set_gain_ctrl(sensor_t *sensor, int enable) +{ + // Read register COM8 + uint8_t reg = SCCB_Read(sensor->slv_addr, COM8); + + // Set white bal on/off + reg = COM8_SET_AGC(reg, enable); + + // Write back register COM8 + return SCCB_Write(sensor->slv_addr, COM8, reg); +} + +static int set_exposure_ctrl(sensor_t *sensor, int enable) +{ + // Read register COM8 + uint8_t reg = SCCB_Read(sensor->slv_addr, COM8); + + // Set white bal on/off + reg = COM8_SET_AEC(reg, enable); + + // Write back register COM8 + return SCCB_Write(sensor->slv_addr, COM8, reg); +} + +static int set_hmirror(sensor_t *sensor, int enable) +{ + // Read register MVFP + uint8_t reg = SCCB_Read(sensor->slv_addr, MVFP); + + // Set mirror on/off + reg = MVFP_SET_MIRROR(reg, enable); + + // Write back register MVFP + return SCCB_Write(sensor->slv_addr, MVFP, reg); +} + +static int set_vflip(sensor_t *sensor, int enable) +{ + // Read register MVFP + uint8_t reg = SCCB_Read(sensor->slv_addr, MVFP); + + // Set mirror on/off + reg = MVFP_SET_FLIP(reg, enable); + + // Write back register MVFP + return SCCB_Write(sensor->slv_addr, MVFP, reg); +} + +static int init_status(sensor_t *sensor) +{ + sensor->status.awb = 0; + sensor->status.aec = 0; + sensor->status.agc = 0; + sensor->status.hmirror = 0; + sensor->status.vflip = 0; + sensor->status.colorbar = 0; + return 0; +} + +static int set_dummy(sensor_t *sensor, int val){ return -1; } +static int set_gainceiling_dummy(sensor_t *sensor, gainceiling_t val){ return -1; } + +int ov7670_init(sensor_t *sensor) +{ + // Set function pointers + sensor->reset = reset; + sensor->init_status = init_status; + sensor->set_pixformat = set_pixformat; + sensor->set_framesize = set_framesize; + sensor->set_colorbar = set_colorbar; + sensor->set_whitebal = set_whitebal; + sensor->set_gain_ctrl = set_gain_ctrl; + sensor->set_exposure_ctrl = set_exposure_ctrl; + sensor->set_hmirror = set_hmirror; + sensor->set_vflip = set_vflip; + + //not supported + sensor->set_brightness= set_dummy; + sensor->set_saturation= set_dummy; + sensor->set_quality = set_dummy; + sensor->set_gainceiling = set_gainceiling_dummy; + sensor->set_aec2 = set_dummy; + sensor->set_aec_value = set_dummy; + sensor->set_special_effect = set_dummy; + sensor->set_wb_mode = set_dummy; + sensor->set_ae_level = set_dummy; + sensor->set_dcw = set_dummy; + sensor->set_bpc = set_dummy; + sensor->set_wpc = set_dummy; + sensor->set_awb_gain = set_dummy; + sensor->set_agc_gain = set_dummy; + sensor->set_raw_gma = set_dummy; + sensor->set_lenc = set_dummy; + sensor->set_sharpness = set_dummy; + sensor->set_denoise = set_dummy; + + // Retrieve sensor's signature + sensor->id.MIDH = SCCB_Read(sensor->slv_addr, REG_MIDH); + sensor->id.MIDL = SCCB_Read(sensor->slv_addr, REG_MIDL); + sensor->id.PID = SCCB_Read(sensor->slv_addr, REG_PID); + sensor->id.VER = SCCB_Read(sensor->slv_addr, REG_VER); + + ESP_LOGD(TAG, "OV7670 Attached"); + + return 0; +} diff --git a/esp32-cam-rtos-allframes/ov7670.h b/esp32-cam-rtos-allframes/ov7670.h new file mode 100644 index 0000000..cdf845c --- /dev/null +++ b/esp32-cam-rtos-allframes/ov7670.h @@ -0,0 +1,14 @@ +/* + * This file is part of the OpenMV project. + * author: Juan Schiavoni + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * OV7670 driver. + * + */ +#ifndef __OV7670_H__ +#define __OV7670_H__ +#include "sensor.h" + +int ov7670_init(sensor_t *sensor); +#endif // __OV7670_H__ diff --git a/esp32-cam-rtos-allframes/ov7670_regs.h b/esp32-cam-rtos-allframes/ov7670_regs.h new file mode 100644 index 0000000..6993548 --- /dev/null +++ b/esp32-cam-rtos-allframes/ov7670_regs.h @@ -0,0 +1,354 @@ +/* + * This file is for the OpenMV project so the OV7670 can be used + * author: Juan Schiavoni + * + * OV7670 register definitions. + */ +#ifndef __OV7670_REG_REGS_H__ +#define __OV7670_REG_REGS_H__ +#define GAIN 0x00 /* AGC – Gain control gain setting */ +#define BLUE 0x01 /* AWB – Blue channel gain setting */ +#define RED 0x02 /* AWB – Red channel gain setting */ +#define VREF 0x03 /* AWB – Green channel gain setting */ +#define COM1 0x04 /* Common Control 1 */ +#define BAVG 0x05 /* U/B Average Level */ +#define GAVG 0x06 /* Y/Gb Average Level */ +#define AECH 0x07 /* Exposure VAlue - AEC MSB 5 bits */ +#define RAVG 0x08 /* V/R Average Level */ + +#define COM2 0x09 /* Common Control 2 */ +#define COM2_SOFT_SLEEP 0x10 /* Soft sleep mode */ +#define COM2_OUT_DRIVE_1x 0x00 /* Output drive capability 1x */ +#define COM2_OUT_DRIVE_2x 0x01 /* Output drive capability 2x */ +#define COM2_OUT_DRIVE_3x 0x02 /* Output drive capability 3x */ +#define COM2_OUT_DRIVE_4x 0x03 /* Output drive capability 4x */ + +#define REG_PID 0x0A /* Product ID Number MSB */ +#define REG_VER 0x0B /* Product ID Number LSB */ + +#define COM3 0x0C /* Common Control 3 */ +#define COM3_SWAP_OUT 0x40 /* Output data MSB/LSB swap */ +#define COM3_TRI_CLK 0x20 /* Tri-state output clock */ +#define COM3_TRI_DATA 0x10 /* Tri-state option output */ +#define COM3_SCALE_EN 0x08 /* Scale enable */ +#define COM3_DCW 0x04 /* DCW enable */ + +#define COM4 0x0D /* Common Control 4 */ +#define COM4_PLL_BYPASS 0x00 /* Bypass PLL */ +#define COM4_PLL_4x 0x40 /* PLL frequency 4x */ +#define COM4_PLL_6x 0x80 /* PLL frequency 6x */ +#define COM4_PLL_8x 0xc0 /* PLL frequency 8x */ +#define COM4_AEC_FULL 0x00 /* AEC evaluate full window */ +#define COM4_AEC_1_2 0x10 /* AEC evaluate 1/2 window */ +#define COM4_AEC_1_4 0x20 /* AEC evaluate 1/4 window */ +#define COM4_AEC_2_3 0x30 /* AEC evaluate 2/3 window */ + +#define COM5 0x0E /* Common Control 5 */ +#define COM5_AFR 0x80 /* Auto frame rate control ON/OFF selection (night mode) */ +#define COM5_AFR_SPEED 0x40 /* Auto frame rate control speed selection */ +#define COM5_AFR_0 0x00 /* No reduction of frame rate */ +#define COM5_AFR_1_2 0x10 /* Max reduction to 1/2 frame rate */ +#define COM5_AFR_1_4 0x20 /* Max reduction to 1/4 frame rate */ +#define COM5_AFR_1_8 0x30 /* Max reduction to 1/8 frame rate */ +#define COM5_AFR_4x 0x04 /* Add frame when AGC reaches 4x gain */ +#define COM5_AFR_8x 0x08 /* Add frame when AGC reaches 8x gain */ +#define COM5_AFR_16x 0x0c /* Add frame when AGC reaches 16x gain */ +#define COM5_AEC_NO_LIMIT 0x01 /* No limit to AEC increase step */ + +#define COM6 0x0F /* Common Control 6 */ +#define COM6_AUTO_WINDOW 0x01 /* Auto window setting ON/OFF selection when format changes */ + +#define AEC 0x10 /* AEC[7:0] (see register AECH for AEC[15:8]) */ +#define CLKRC 0x11 /* Internal Clock */ + +#define COM7 0x12 /* Common Control 7 */ +#define COM7_RESET 0x80 /* SCCB Register Reset */ +#define COM7_RES_VGA 0x00 /* Resolution VGA */ +#define COM7_RES_QVGA 0x40 /* Resolution QVGA */ +#define COM7_BT656 0x20 /* BT.656 protocol ON/OFF */ +#define COM7_SENSOR_RAW 0x10 /* Sensor RAW */ +#define COM7_FMT_GBR422 0x00 /* RGB output format GBR422 */ +#define COM7_FMT_RGB565 0x04 /* RGB output format RGB565 */ +#define COM7_FMT_RGB555 0x08 /* RGB output format RGB555 */ +#define COM7_FMT_RGB444 0x0C /* RGB output format RGB444 */ +#define COM7_FMT_YUV 0x00 /* Output format YUV */ +#define COM7_FMT_P_BAYER 0x01 /* Output format Processed Bayer RAW */ +#define COM7_FMT_RGB 0x04 /* Output format RGB */ +#define COM7_FMT_R_BAYER 0x03 /* Output format Bayer RAW */ +#define COM7_SET_FMT(r, x) ((r&0xFC)|((x&0x5)<<0)) + +#define COM8 0x13 /* Common Control 8 */ +#define COM8_FAST_AUTO 0x80 /* Enable fast AGC/AEC algorithm */ +#define COM8_STEP_VSYNC 0x00 /* AEC - Step size limited to vertical blank */ +#define COM8_STEP_UNLIMIT 0x40 /* AEC - Step size unlimited step size */ +#define COM8_BANDF_EN 0x20 /* Banding filter ON/OFF */ +#define COM8_AEC_BANDF 0x10 /* Enable AEC below banding value */ +#define COM8_AEC_FINE_EN 0x08 /* Fine AEC ON/OFF control */ +#define COM8_AGC_EN 0x04 /* AGC Enable */ +#define COM8_AWB_EN 0x02 /* AWB Enable */ +#define COM8_AEC_EN 0x01 /* AEC Enable */ +#define COM8_SET_AGC(r, x) ((r&0xFB)|((x&0x1)<<2)) +#define COM8_SET_AWB(r, x) ((r&0xFD)|((x&0x1)<<1)) +#define COM8_SET_AEC(r, x) ((r&0xFE)|((x&0x1)<<0)) + +#define COM9 0x14 /* Common Control 9 */ +#define COM9_HISTO_AVG 0x80 /* Histogram or average based AEC/AGC selection */ +#define COM9_AGC_GAIN_2x 0x00 /* Automatic Gain Ceiling 2x */ +#define COM9_AGC_GAIN_4x 0x10 /* Automatic Gain Ceiling 4x */ +#define COM9_AGC_GAIN_8x 0x20 /* Automatic Gain Ceiling 8x */ +#define COM9_AGC_GAIN_16x 0x30 /* Automatic Gain Ceiling 16x */ +#define COM9_AGC_GAIN_32x 0x40 /* Automatic Gain Ceiling 32x */ +#define COM9_DROP_VSYNC 0x04 /* Drop VSYNC output of corrupt frame */ +#define COM9_DROP_HREF 0x02 /* Drop HREF output of corrupt frame */ +#define COM9_SET_AGC(r, x) ((r&0x8F)|((x&0x07)<<4)) + +#define COM10 0x15 /* Common Control 10 */ +#define COM10_NEGATIVE 0x80 /* Output negative data */ +#define COM10_HSYNC_EN 0x40 /* HREF changes to HSYNC */ +#define COM10_PCLK_FREE 0x00 /* PCLK output option: free running PCLK */ +#define COM10_PCLK_MASK 0x20 /* PCLK output option: masked during horizontal blank */ +#define COM10_PCLK_REV 0x10 /* PCLK reverse */ +#define COM10_HREF_REV 0x08 /* HREF reverse */ +#define COM10_VSYNC_FALLING 0x00 /* VSYNC changes on falling edge of PCLK */ +#define COM10_VSYNC_RISING 0x04 /* VSYNC changes on rising edge of PCLK */ +#define COM10_VSYNC_NEG 0x02 /* VSYNC negative */ +#define COM10_OUT_RANGE_8 0x01 /* Output data range: Full range */ +#define COM10_OUT_RANGE_10 0x00 /* Output data range: Data from [10] to [F0] (8 MSBs) */ + +#define RSVD_16 0x16 /* Reserved register */ + +#define HSTART 0x17 /* Horizontal Frame (HREF column) Start high 8-bit(low 3 bits are at HREF[2:0]) */ +#define HSTOP 0x18 /* Horizontal Frame (HREF column) end high 8-bit (low 3 bits are at HREF[5:3]) */ +#define VSTART 0x19 /* Vertical Frame (row) Start high 8-bit (low 2 bits are at VREF[1:0]) */ +#define VSTOP 0x1A /* Vertical Frame (row) End high 8-bit (low 2 bits are at VREF[3:2]) */ +#define PSHFT 0x1B /* Data Format - Pixel Delay Select */ +#define REG_MIDH 0x1C /* Manufacturer ID Byte – High */ +#define REG_MIDL 0x1D /* Manufacturer ID Byte – Low */ + +#define MVFP 0x1E /* Mirror/Vflip Enable */ +#define MVFP_MIRROR 0x20 /* Mirror image */ +#define MVFP_FLIP 0x10 /* Vertical flip */ +#define MVFP_SUN 0x02 /* Black sun enable */ +#define MVFP_SET_MIRROR(r,x) ((r&0xDF)|((x&1)<<5)) /* change only bit5 according to x */ +#define MVFP_SET_FLIP(r,x) ((r&0xEF)|((x&1)<<4)) /* change only bit4 according to x */ + +#define LAEC 0x1F /* Fine AEC Value - defines exposure value less than one row period (Reserved?) */ +#define ADCCTR0 0x20 /* ADC control */ +#define ADCCTR1 0x21 /* reserved */ +#define ADCCTR2 0x22 /* reserved */ +#define ADCCTR3 0x23 /* reserved */ +#define AEW 0x24 /* AGC/AEC - Stable Operating Region (Upper Limit) */ +#define AEB 0x25 /* AGC/AEC - Stable Operating Region (Lower Limit) */ +#define VPT 0x26 /* AGC/AEC Fast Mode Operating Region */ +#define BBIAS 0x27 /* B channel signal output bias (effective only when COM6[3]=1) */ +#define GbBIAS 0x28 /* Gb channel signal output bias (effective only when COM6[3]=1) */ +#define RSVD_29 0x29 /* reserved */ +#define EXHCH 0x2A /* Dummy Pixel Insert MSB */ +#define EXHCL 0x2B /* Dummy Pixel Insert LSB */ +#define RBIAS 0x2C /* R channel signal output bias (effective only when COM6[3]=1) */ +#define ADVFL 0x2D /* LSB of Insert Dummy Rows in Vertical Sync (1 bit equals 1 row) */ +#define ADVFH 0x2E /* MSB of Insert Dummy Rows in Vertical Sync */ +#define YAVE 0x2F /* Y/G Channel Average Value */ +#define HSYST 0x30 /* HSync rising edge delay */ +#define HSYEN 0x31 /* HSync falling edge delay */ +#define HREF 0x32 /* Image Start and Size Control DIFFERENT CONTROL SEQUENCE */ +#define CHLF 0x33 /* Array Current control */ +#define ARBLM 0x34 /* Array reference control */ +#define RSVD_35 0x35 /* Reserved */ +#define RSVD_36 0x36 /* Reserved */ +#define ADC 0x37 /* ADC control */ +#define ACOM 0x38 /* ADC and analog common mode control */ +#define OFON 0x39 /* ADC offset control */ +#define TSLB 0x3A /* Line buffer test option */ + +#define COM11 0x3B /* Common control 11 */ +#define COM11_EXP 0x02 +#define COM11_HZAUTO 0x10 /* Auto detect 50/60 Hz */ + +#define COM12 0x3C /* Common control 12 */ + +#define COM13 0x3D /* Common control 13 */ +#define COM13_GAMMA 0x80 /* Gamma enable */ +#define COM13_UVSAT 0x40 /* UV saturation auto adjustment */ + +#define COM14 0x3E /* Common Control 14 */ + +#define EDGE 0x3F /* edge enhancement adjustment */ +#define COM15 0x40 /* Common Control 15 DIFFERENT CONTROLS */ +#define COM15_SET_RGB565(r,x) ((r&0xEF)|((x&1)<<4)) /* set rgb565 mode */ +#define COM15_RGB565 0x10 /* RGB565 output */ +#define COM15_R00FF 0xC0 /* Output range: [00] to [FF] */ + +#define COM16 0x41 /* Common Control 16 DIFFERENT CONTROLS */ +#define COM16_AWBGAIN 0x08 /* AWB gain enable */ +#define COM17 0x42 /* Common Control 17 */ + +#define AWBC1 0x43 /* Reserved */ +#define AWBC2 0x44 /* Reserved */ +#define AWBC3 0x45 /* Reserved */ +#define AWBC4 0x46 /* Reserved */ +#define AWBC5 0x47 /* Reserved */ +#define AWBC6 0x48 /* Reserved */ + +#define RSVD_49 0x49 /* Reserved */ +#define RSVD_4A 0x4A /* Reserved */ + +#define REG4B 0x4B /* Register 4B */ +#define DNSTH 0x4C /* Denoise strength */ + +#define RSVD_4D 0x4D /* Reserved */ +#define RSVD_4E 0x4E /* Reserved */ + +#define MTX1 0x4F /* Matrix coefficient 1 */ +#define MTX2 0x50 /* Matrix coefficient 2 */ +#define MTX3 0x51 /* Matrix coefficient 3 */ +#define MTX4 0x52 /* Matrix coefficient 4 */ +#define MTX5 0x53 /* Matrix coefficient 5 */ +#define MTX6 0x54 /* Matrix coefficient 6 */ +#define BRIGHTNESS 0x55 /* Brightness control */ +#define CONTRAST 0x56 /* Contrast control */ +#define CONTRASCENTER 0x57 /* Contrast center */ +#define MTXS 0x58 /* Matrix coefficient sign for coefficient 5 to 0*/ + +#define RSVD_59 0x59 /* Reserved */ +#define RSVD_5A 0x5A /* Reserved */ +#define RSVD_5B 0x5B /* Reserved */ +#define RSVD_5C 0x5C /* Reserved */ +#define RSVD_5D 0x5D /* Reserved */ +#define RSVD_5E 0x5E /* Reserved */ +#define RSVD_5F 0x5F /* Reserved */ +#define RSVD_60 0x60 /* Reserved */ +#define RSVD_61 0x61 /* Reserved */ + +#define LCC1 0x62 /* Lens correction option 1 */ + +#define LCC2 0x63 /* Lens correction option 2 */ +#define LCC3 0x64 /* Lens correction option 3 */ +#define LCC4 0x65 /* Lens correction option 4 */ +#define LCC5 0x66 /* Lens correction option 5 */ + +#define MANU 0x67 /* Manual U Value */ +#define MANV 0x68 /* Manual V Value */ +#define GFIX 0x69 /* Fix gain control */ +#define GGAIN 0x6A /* G channel AWB gain */ + +#define DBLV 0x6B /* PLL and clock ? */ + +#define AWBCTR3 0x6C /* AWB Control 3 */ +#define AWBCTR2 0x6D /* AWB Control 2 */ +#define AWBCTR1 0x6E /* AWB Control 1 */ +#define AWBCTR0 0x6F /* AWB Control 0 */ +#define SCALING_XSC 0x70 /* test pattern and horizontal scaling factor */ +#define SCALING_XSC_CBAR(r) (r&0x7F) /* make sure bit7 is 0 for color bar */ +#define SCALING_YSC 0x71 /* test pattern and vertical scaling factor */ +#define SCALING_YSC_CBAR(r,x) ((r&0x7F)|((x&1)<<7)) /* change bit7 for color bar on/off */ +#define SCALING_DCWCTR 0x72 /* DCW control */ +#define SCALING_PCLK_DIV 0x73 /* */ +#define REG74 0x74 /* */ +#define REG75 0x75 /* */ +#define REG76 0x76 /* */ +#define REG77 0x77 /* */ + +#define RSVD_78 0x78 /* Reserved */ +#define RSVD_79 0x79 /* Reserved */ + +#define SLOP 0x7A /* Gamma curve highest segment slope */ +#define GAM1 0x7B /* Gamma Curve 1st Segment Input End Point 0x04 Output Value */ +#define GAM2 0x7C /* Gamma Curve 2nd Segment Input End Point 0x08 Output Value */ +#define GAM3 0x7D /* Gamma Curve 3rd Segment Input End Point 0x10 Output Value */ +#define GAM4 0x7E /* Gamma Curve 4th Segment Input End Point 0x20 Output Value */ +#define GAM5 0x7F /* Gamma Curve 5th Segment Input End Point 0x28 Output Value */ +#define GAM6 0x80 /* Gamma Curve 6rd Segment Input End Point 0x30 Output Value */ +#define GAM7 0x81 /* Gamma Curve 7th Segment Input End Point 0x38 Output Value */ +#define GAM8 0x82 /* Gamma Curve 8th Segment Input End Point 0x40 Output Value */ +#define GAM9 0x83 /* Gamma Curve 9th Segment Input End Point 0x48 Output Value */ +#define GAM10 0x84 /* Gamma Curve 10th Segment Input End Point 0x50 Output Value */ +#define GAM11 0x85 /* Gamma Curve 11th Segment Input End Point 0x60 Output Value */ +#define GAM12 0x86 /* Gamma Curve 12th Segment Input End Point 0x70 Output Value */ +#define GAM13 0x87 /* Gamma Curve 13th Segment Input End Point 0x90 Output Value */ +#define GAM14 0x88 /* Gamma Curve 14th Segment Input End Point 0xB0 Output Value */ +#define GAM15 0x89 /* Gamma Curve 15th Segment Input End Point 0xD0 Output Value */ + +#define RSVD_8A 0x8A /* Reserved */ +#define RSVD_8B 0x8B /* Reserved */ + +#define RGB444 0x8C /* */ + +#define RSVD_8D 0x8D /* Reserved */ +#define RSVD_8E 0x8E /* Reserved */ +#define RSVD_8F 0x8F /* Reserved */ +#define RSVD_90 0x90 /* Reserved */ +#define RSVD_91 0x91 /* Reserved */ + +#define DM_LNL 0x92 /* Dummy line low 8 bit */ +#define DM_LNH 0x93 /* Dummy line high 8 bit */ +#define LCC6 0x94 /* Lens correction option 6 */ +#define LCC7 0x95 /* Lens correction option 7 */ + +#define RSVD_96 0x96 /* Reserved */ +#define RSVD_97 0x97 /* Reserved */ +#define RSVD_98 0x98 /* Reserved */ +#define RSVD_99 0x99 /* Reserved */ +#define RSVD_9A 0x9A /* Reserved */ +#define RSVD_9B 0x9B /* Reserved */ +#define RSVD_9C 0x9C /* Reserved */ + +#define BD50ST 0x9D /* 50 Hz banding filter value */ +#define BD60ST 0x9E /* 60 Hz banding filter value */ +#define HAECC1 0x9F /* Histogram-based AEC/AGC control 1 */ +#define HAECC2 0xA0 /* Histogram-based AEC/AGC control 2 */ + +#define RSVD_A1 0xA1 /* Reserved */ + +#define SCALING_PCLK_DELAY 0xA2 /* Pixel clock delay */ + +#define RSVD_A3 0xA3 /* Reserved */ + +#define NT_CNTRL 0xA4 /* */ +#define BD50MAX 0xA5 /* 50 Hz banding step limit */ +#define HAECC3 0xA6 /* Histogram-based AEC/AGC control 3 */ +#define HAECC4 0xA7 /* Histogram-based AEC/AGC control 4 */ +#define HAECC5 0xA8 /* Histogram-based AEC/AGC control 5 */ +#define HAECC6 0xA9 /* Histogram-based AEC/AGC control 6 */ + +#define HAECC7 0xAA /* Histogram-based AEC/AGC control 7 */ +#define HAECC_EN 0x80 /* Histogram-based AEC algorithm enable */ + +#define BD60MAX 0xAB /* 60 Hz banding step limit */ + +#define STR_OPT 0xAC /* Register AC */ +#define STR_R 0xAD /* R gain for led output frame */ +#define STR_G 0xAE /* G gain for led output frame */ +#define STR_B 0xAF /* B gain for led output frame */ +#define RSVD_B0 0xB0 /* Reserved */ +#define ABLC1 0xB1 /* */ +#define RSVD_B2 0xB2 /* Reserved */ +#define THL_ST 0xB3 /* ABLC target */ +#define THL_DLT 0xB5 /* ABLC stable range */ + +#define RSVD_B6 0xB6 /* Reserved */ +#define RSVD_B7 0xB7 /* Reserved */ +#define RSVD_B8 0xB8 /* Reserved */ +#define RSVD_B9 0xB9 /* Reserved */ +#define RSVD_BA 0xBA /* Reserved */ +#define RSVD_BB 0xBB /* Reserved */ +#define RSVD_BC 0xBC /* Reserved */ +#define RSVD_BD 0xBD /* Reserved */ + +#define AD_CHB 0xBE /* blue channel black level compensation */ +#define AD_CHR 0xBF /* Red channel black level compensation */ +#define AD_CHGb 0xC0 /* Gb channel black level compensation */ +#define AD_CHGr 0xC1 /* Gr channel black level compensation */ + +#define RSVD_C2 0xC2 /* Reserved */ +#define RSVD_C3 0xC3 /* Reserved */ +#define RSVD_C4 0xC4 /* Reserved */ +#define RSVD_C5 0xC5 /* Reserved */ +#define RSVD_C6 0xC6 /* Reserved */ +#define RSVD_C7 0xC7 /* Reserved */ +#define RSVD_C8 0xC8 /* Reserved */ + +#define SATCTR 0xC9 /* Saturation control */ +#define SET_REG(reg, x) (##reg_DEFAULT|x) + +#endif //__OV7670_REG_REGS_H__ diff --git a/esp32-cam-rtos-allframes/sccb.c b/esp32-cam-rtos-allframes/sccb.c index d2f5fb9..cb615bb 100644 --- a/esp32-cam-rtos-allframes/sccb.c +++ b/esp32-cam-rtos-allframes/sccb.c @@ -7,6 +7,7 @@ * */ #include +#include #include #include #include "sccb.h" @@ -19,11 +20,8 @@ static const char* TAG = "sccb"; #endif -//#undef CONFIG_SCCB_HARDWARE_I2C - #define LITTLETOBIG(x) ((x<<8)|(x>>8)) -#ifdef CONFIG_SCCB_HARDWARE_I2C #include "driver/i2c.h" #define SCCB_FREQ 100000 /*!< I2C master frequency*/ @@ -39,16 +37,13 @@ const int SCCB_I2C_PORT = 1; const int SCCB_I2C_PORT = 0; #endif static uint8_t ESP_SLAVE_ADDR = 0x3c; -#else -#include "twi.h" -#endif int SCCB_Init(int pin_sda, int pin_scl) { ESP_LOGI(TAG, "pin_sda %d pin_scl %d\n", pin_sda, pin_scl); -#ifdef CONFIG_SCCB_HARDWARE_I2C //log_i("SCCB_Init start"); i2c_config_t conf; + memset(&conf, 0, sizeof(i2c_config_t)); conf.mode = I2C_MODE_MASTER; conf.sda_io_num = pin_sda; conf.sda_pullup_en = GPIO_PULLUP_ENABLE; @@ -58,15 +53,11 @@ int SCCB_Init(int pin_sda, int pin_scl) i2c_param_config(SCCB_I2C_PORT, &conf); i2c_driver_install(SCCB_I2C_PORT, conf.mode, 0, 0, 0); -#else - twi_init(pin_sda, pin_scl); -#endif return 0; } uint8_t SCCB_Probe() { -#ifdef CONFIG_SCCB_HARDWARE_I2C uint8_t slave_addr = 0x0; while(slave_addr < 0x7f) { i2c_cmd_handle_t cmd = i2c_cmd_link_create(); @@ -82,28 +73,10 @@ uint8_t SCCB_Probe() slave_addr++; } return ESP_SLAVE_ADDR; -#else - uint8_t reg = 0x00; - uint8_t slv_addr = 0x00; - - ESP_LOGI(TAG, "SCCB_Probe start"); - for (uint8_t i = 0; i < 127; i++) { - if (twi_writeTo(i, ®, 1, true) == 0) { - slv_addr = i; - break; - } - - if (i!=126) { - vTaskDelay(10 / portTICK_PERIOD_MS); // Necessary for OV7725 camera (not for OV2640). - } - } - return slv_addr; -#endif } uint8_t SCCB_Read(uint8_t slv_addr, uint8_t reg) { -#ifdef CONFIG_SCCB_HARDWARE_I2C uint8_t data=0; esp_err_t ret = ESP_FAIL; i2c_cmd_handle_t cmd = i2c_cmd_link_create(); @@ -125,28 +98,10 @@ uint8_t SCCB_Read(uint8_t slv_addr, uint8_t reg) ESP_LOGE(TAG, "SCCB_Read Failed addr:0x%02x, reg:0x%02x, data:0x%02x, ret:%d", slv_addr, reg, data, ret); } return data; -#else - uint8_t data=0; - - int rc = twi_writeTo(slv_addr, ®, 1, true); - if (rc != 0) { - data = 0xff; - } else { - rc = twi_readFrom(slv_addr, &data, 1, true); - if (rc != 0) { - data=0xFF; - } - } - if (rc != 0) { - ESP_LOGE(TAG, "SCCB_Read [%02x] failed rc=%d\n", reg, rc); - } - return data; -#endif } uint8_t SCCB_Write(uint8_t slv_addr, uint8_t reg, uint8_t data) { -#ifdef CONFIG_SCCB_HARDWARE_I2C esp_err_t ret = ESP_FAIL; i2c_cmd_handle_t cmd = i2c_cmd_link_create(); i2c_master_start(cmd); @@ -160,23 +115,10 @@ uint8_t SCCB_Write(uint8_t slv_addr, uint8_t reg, uint8_t data) ESP_LOGE(TAG, "SCCB_Write Failed addr:0x%02x, reg:0x%02x, data:0x%02x, ret:%d", slv_addr, reg, data, ret); } return ret == ESP_OK ? 0 : -1; -#else - uint8_t ret=0; - uint8_t buf[] = {reg, data}; - - if(twi_writeTo(slv_addr, buf, 2, true) != 0) { - ret=0xFF; - } - if (ret != 0) { - ESP_LOGE(TAG, "SCCB_Write [%02x]=%02x failed\n", reg, data); - } - return ret; -#endif } uint8_t SCCB_Read16(uint8_t slv_addr, uint16_t reg) { -#ifdef CONFIG_SCCB_HARDWARE_I2C uint8_t data=0; esp_err_t ret = ESP_FAIL; uint16_t reg_htons = LITTLETOBIG(reg); @@ -201,32 +143,11 @@ uint8_t SCCB_Read16(uint8_t slv_addr, uint16_t reg) ESP_LOGE(TAG, "W [%04x]=%02x fail\n", reg, data); } return data; -#else - uint8_t data=0; - uint16_t reg_htons = LITTLETOBIG(reg); - uint8_t *reg_u8 = (uint8_t *)®_htons; - uint8_t buf[] = {reg_u8[0], reg_u8[1]}; - - int rc = twi_writeTo(slv_addr, buf, 2, true); - if (rc != 0) { - data = 0xff; - } else { - rc = twi_readFrom(slv_addr, &data, 1, true); - if (rc != 0) { - data=0xFF; - } - } - if (rc != 0) { - ESP_LOGE(TAG, "R [%04x] fail rc=%d\n", reg, rc); - } - return data; -#endif } uint8_t SCCB_Write16(uint8_t slv_addr, uint16_t reg, uint8_t data) { static uint16_t i = 0; -#ifdef CONFIG_SCCB_HARDWARE_I2C esp_err_t ret = ESP_FAIL; uint16_t reg_htons = LITTLETOBIG(reg); uint8_t *reg_u8 = (uint8_t *)®_htons; @@ -243,18 +164,4 @@ uint8_t SCCB_Write16(uint8_t slv_addr, uint16_t reg, uint8_t data) ESP_LOGE(TAG, "W [%04x]=%02x %d fail\n", reg, data, i++); } return ret == ESP_OK ? 0 : -1; -#else - uint8_t ret=0; - uint16_t reg_htons = LITTLETOBIG(reg); - uint8_t *reg_u8 = (uint8_t *)®_htons; - uint8_t buf[] = {reg_u8[0], reg_u8[1], data}; - - if(twi_writeTo(slv_addr, buf, 3, true) != 0) { - ret = 0xFF; - } - if (ret != 0) { - ESP_LOGE(TAG, "W [%04x]=%02x %d fail\n", reg, data, i++); - } - return ret; -#endif } diff --git a/esp32-cam-rtos-allframes/sensor.h b/esp32-cam-rtos-allframes/sensor.h index 3ea7e2c..ad6cd89 100644 --- a/esp32-cam-rtos-allframes/sensor.h +++ b/esp32-cam-rtos-allframes/sensor.h @@ -11,11 +11,13 @@ #include #include +#define NT99141_PID (0x14) #define OV9650_PID (0x96) #define OV7725_PID (0x77) #define OV2640_PID (0x26) #define OV3660_PID (0x36) #define OV5640_PID (0x56) +#define OV7670_PID (0x76) typedef enum { PIXFORMAT_RGB565, // 2BPP/RGB565 diff --git a/esp32-cam-rtos-allframes/to_bmp.c b/esp32-cam-rtos-allframes/to_bmp.c index 59455de..85f9c88 100644 --- a/esp32-cam-rtos-allframes/to_bmp.c +++ b/esp32-cam-rtos-allframes/to_bmp.c @@ -20,6 +20,17 @@ #include "sdkconfig.h" #include "esp_jpg_decode.h" +#include "esp_system.h" +#if ESP_IDF_VERSION_MAJOR >= 4 // IDF 4+ +#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 +#include "esp32/spiram.h" +#else +#error Target CONFIG_IDF_TARGET is not supported +#endif +#else // ESP32 Before IDF 4.0 +#include "esp_spiram.h" +#endif + #if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) #include "esp32-hal-log.h" #define TAG "" diff --git a/esp32-cam-rtos-allframes/twi.h b/esp32-cam-rtos-allframes/twi.h index 60624f8..71f9907 100644 --- a/esp32-cam-rtos-allframes/twi.h +++ b/esp32-cam-rtos-allframes/twi.h @@ -35,4 +35,4 @@ uint8_t twi_readFrom(unsigned char address, unsigned char * buf, unsigned int le } #endif -#endif \ No newline at end of file +#endif diff --git a/esp32-cam-rtos/camera.c b/esp32-cam-rtos/camera.c index 0fc85f6..f0f3a2e 100644 --- a/esp32-cam-rtos/camera.c +++ b/esp32-cam-rtos/camera.c @@ -48,6 +48,12 @@ #if CONFIG_OV5640_SUPPORT #include "ov5640.h" #endif +#if CONFIG_NT99141_SUPPORT +#include "nt99141.h" +#endif +#if CONFIG_OV7670_SUPPORT +#include "ov7670.h" +#endif typedef enum { CAMERA_NONE = 0, @@ -56,6 +62,8 @@ typedef enum { CAMERA_OV2640 = 2640, CAMERA_OV3660 = 3660, CAMERA_OV5640 = 5640, + CAMERA_OV7670 = 7670, + CAMERA_NT99141 = 9141, } camera_model_t; #define REG_PID 0x0A @@ -369,12 +377,10 @@ static inline void IRAM_ATTR i2s_conf_reset() } } -static void i2s_init() +static void i2s_gpio_init(const camera_config_t* config) { - camera_config_t* config = &s_state->config; - // Configure input GPIOs - gpio_num_t pins[] = { + const gpio_num_t pins[] = { config->pin_d7, config->pin_d6, config->pin_d5, @@ -391,15 +397,21 @@ static void i2s_init() .mode = GPIO_MODE_INPUT, .pull_up_en = GPIO_PULLUP_ENABLE, .pull_down_en = GPIO_PULLDOWN_DISABLE, - .intr_type = GPIO_INTR_DISABLE + .intr_type = GPIO_INTR_DISABLE, + .pin_bit_mask = 0LL }; for (int i = 0; i < sizeof(pins) / sizeof(gpio_num_t); ++i) { if (rtc_gpio_is_valid_gpio(pins[i])) { rtc_gpio_deinit(pins[i]); } - conf.pin_bit_mask = 1LL << pins[i]; - gpio_config(&conf); + conf.pin_bit_mask |= 1LL << pins[i]; } + gpio_config(&conf); +} + +static void i2s_init() +{ + camera_config_t* config = &s_state->config; // Route input GPIOs to I2S peripheral using GPIO matrix gpio_matrix_in(config->pin_d0, I2S0I_DATA_IN0_IDX, false); @@ -738,7 +750,7 @@ static void IRAM_ATTR dma_filter_buffer(size_t buf_idx) if(s_state->sensor.pixformat == PIXFORMAT_JPEG) { uint32_t sig = *((uint32_t *)s_state->fb->buf) & 0xFFFFFF; if(sig != 0xffd8ff) { - ets_printf("bh 0x%08x\n", sig); + ESP_LOGD(TAG,"unexpected JPEG signature 0x%08x\n", sig); s_state->fb->bad = 1; return; } @@ -955,11 +967,15 @@ esp_err_t camera_probe(const camera_config_t* config, camera_model_t* out_camera return ESP_ERR_NO_MEM; } - ESP_LOGD(TAG, "Enabling XCLK output"); - camera_enable_out_clock(config); + if(config->pin_xclk >= 0) { + ESP_LOGD(TAG, "Enabling XCLK output"); + camera_enable_out_clock(config); + } - ESP_LOGD(TAG, "Initializing SSCB"); - SCCB_Init(config->pin_sscb_sda, config->pin_sscb_scl); + if (config->pin_sscb_sda != -1) { + ESP_LOGD(TAG, "Initializing SSCB"); + SCCB_Init(config->pin_sscb_sda, config->pin_sscb_scl); + } if(config->pin_pwdn >= 0) { ESP_LOGD(TAG, "Resetting camera by power down line"); @@ -1011,16 +1027,33 @@ esp_err_t camera_probe(const camera_config_t* config, camera_model_t* out_camera slv_addr = SCCB_Probe(); } #endif +#if CONFIG_NT99141_SUPPORT + if (slv_addr == 0x2a) + { + ESP_LOGD(TAG, "Resetting NT99141"); + SCCB_Write16(0x2a, 0x3008, 0x01);//bank sensor + } +#endif s_state->sensor.slv_addr = slv_addr; s_state->sensor.xclk_freq_hz = config->xclk_freq_hz; -#if (CONFIG_OV3660_SUPPORT || CONFIG_OV5640_SUPPORT) +#if (CONFIG_OV3660_SUPPORT || CONFIG_OV5640_SUPPORT || CONFIG_NT99141_SUPPORT) if(s_state->sensor.slv_addr == 0x3c){ id->PID = SCCB_Read16(s_state->sensor.slv_addr, REG16_CHIDH); id->VER = SCCB_Read16(s_state->sensor.slv_addr, REG16_CHIDL); vTaskDelay(10 / portTICK_PERIOD_MS); ESP_LOGD(TAG, "Camera PID=0x%02x VER=0x%02x", id->PID, id->VER); + } else if(s_state->sensor.slv_addr == 0x2a){ + id->PID = SCCB_Read16(s_state->sensor.slv_addr, 0x3000); + id->VER = SCCB_Read16(s_state->sensor.slv_addr, 0x3001); + vTaskDelay(10 / portTICK_PERIOD_MS); + ESP_LOGD(TAG, "Camera PID=0x%02x VER=0x%02x", id->PID, id->VER); + if(config->xclk_freq_hz > 10000000) + { + ESP_LOGE(TAG, "NT99141: only XCLK under 10MHz is supported, and XCLK is now set to 10M"); + s_state->sensor.xclk_freq_hz = 10000000; + } } else { #endif id->PID = SCCB_Read(s_state->sensor.slv_addr, REG_PID); @@ -1031,7 +1064,7 @@ esp_err_t camera_probe(const camera_config_t* config, camera_model_t* out_camera ESP_LOGD(TAG, "Camera PID=0x%02x VER=0x%02x MIDL=0x%02x MIDH=0x%02x", id->PID, id->VER, id->MIDH, id->MIDL); -#if (CONFIG_OV3660_SUPPORT || CONFIG_OV5640_SUPPORT) +#if (CONFIG_OV3660_SUPPORT || CONFIG_OV5640_SUPPORT || CONFIG_NT99141_SUPPORT) } #endif @@ -1060,6 +1093,18 @@ esp_err_t camera_probe(const camera_config_t* config, camera_model_t* out_camera *out_camera_model = CAMERA_OV5640; ov5640_init(&s_state->sensor); break; +#endif +#if CONFIG_OV7670_SUPPORT + case OV7670_PID: + *out_camera_model = CAMERA_OV7670; + ov7670_init(&s_state->sensor); + break; +#endif +#if CONFIG_NT99141_SUPPORT + case NT99141_PID: + *out_camera_model = CAMERA_NT99141; + NT99141_init(&s_state->sensor); + break; #endif default: id->PID = 0; @@ -1116,6 +1161,20 @@ esp_err_t camera_init(const camera_config_t* config) frame_size = FRAMESIZE_QSXGA; } break; +#endif +#if CONFIG_OV7670_SUPPORT + case OV7670_PID: + if (frame_size > FRAMESIZE_VGA) { + frame_size = FRAMESIZE_VGA; + } + break; +#endif +#if CONFIG_NT99141_SUPPORT + case NT99141_PID: + if (frame_size > FRAMESIZE_HD) { + frame_size = FRAMESIZE_HD; + } + break; #endif default: return ESP_ERR_CAMERA_NOT_SUPPORTED; @@ -1126,7 +1185,7 @@ esp_err_t camera_init(const camera_config_t* config) if (pix_format == PIXFORMAT_GRAYSCALE) { s_state->fb_size = s_state->width * s_state->height; - if (s_state->sensor.id.PID == OV3660_PID || s_state->sensor.id.PID == OV5640_PID) { + if (s_state->sensor.id.PID == OV3660_PID || s_state->sensor.id.PID == OV5640_PID || s_state->sensor.id.PID == NT99141_PID) { if (is_hs_mode()) { s_state->sampling_mode = SM_0A00_0B00; s_state->dma_filter = &dma_filter_yuyv_highspeed; @@ -1147,20 +1206,28 @@ esp_err_t camera_init(const camera_config_t* config) } s_state->fb_bytes_per_pixel = 1; // frame buffer stores Y8 } else if (pix_format == PIXFORMAT_YUV422 || pix_format == PIXFORMAT_RGB565) { - s_state->fb_size = s_state->width * s_state->height * 2; - if (is_hs_mode() && s_state->sensor.id.PID != OV7725_PID) { - s_state->sampling_mode = SM_0A00_0B00; - s_state->dma_filter = &dma_filter_yuyv_highspeed; - } else { - s_state->sampling_mode = SM_0A0B_0C0D; - s_state->dma_filter = &dma_filter_yuyv; - } - s_state->in_bytes_per_pixel = 2; // camera sends YU/YV - s_state->fb_bytes_per_pixel = 2; // frame buffer stores YU/YV/RGB565 + s_state->fb_size = s_state->width * s_state->height * 2; + if (is_hs_mode() && s_state->sensor.id.PID != OV7725_PID) { + if(s_state->sensor.id.PID == OV7670_PID) { + s_state->sampling_mode = SM_0A0B_0B0C; + }else{ + s_state->sampling_mode = SM_0A00_0B00; + } + s_state->dma_filter = &dma_filter_yuyv_highspeed; + } else { + s_state->sampling_mode = SM_0A0B_0C0D; + s_state->dma_filter = &dma_filter_yuyv; + } + s_state->in_bytes_per_pixel = 2; // camera sends YU/YV + s_state->fb_bytes_per_pixel = 2; // frame buffer stores YU/YV/RGB565 } else if (pix_format == PIXFORMAT_RGB888) { s_state->fb_size = s_state->width * s_state->height * 3; if (is_hs_mode()) { - s_state->sampling_mode = SM_0A00_0B00; + if(s_state->sensor.id.PID == OV7670_PID) { + s_state->sampling_mode = SM_0A0B_0B0C; + }else{ + s_state->sampling_mode = SM_0A00_0B00; + } s_state->dma_filter = &dma_filter_rgb888_highspeed; } else { s_state->sampling_mode = SM_0A0B_0C0D; @@ -1169,7 +1236,7 @@ esp_err_t camera_init(const camera_config_t* config) s_state->in_bytes_per_pixel = 2; // camera sends RGB565 s_state->fb_bytes_per_pixel = 3; // frame buffer stores RGB888 } else if (pix_format == PIXFORMAT_JPEG) { - if (s_state->sensor.id.PID != OV2640_PID && s_state->sensor.id.PID != OV3660_PID && s_state->sensor.id.PID != OV5640_PID) { + if (s_state->sensor.id.PID != OV2640_PID && s_state->sensor.id.PID != OV3660_PID && s_state->sensor.id.PID != OV5640_PID && s_state->sensor.id.PID != NT99141_PID) { ESP_LOGE(TAG, "JPEG format is only supported for ov2640, ov3660 and ov5640"); err = ESP_ERR_NOT_SUPPORTED; goto fail; @@ -1306,6 +1373,7 @@ fail: esp_err_t esp_camera_init(const camera_config_t* config) { camera_model_t camera_model = CAMERA_NONE; + i2s_gpio_init(config); esp_err_t err = camera_probe(config, &camera_model); if (err != ESP_OK) { ESP_LOGE(TAG, "Camera probe failed with error 0x%x", err); @@ -1324,6 +1392,10 @@ esp_err_t esp_camera_init(const camera_config_t* config) ESP_LOGI(TAG, "Detected OV3660 camera"); } else if (camera_model == CAMERA_OV5640) { ESP_LOGI(TAG, "Detected OV5640 camera"); + } else if (camera_model == CAMERA_OV7670) { + ESP_LOGI(TAG, "Detected OV7670 camera"); + } else if (camera_model == CAMERA_NT99141) { + ESP_LOGI(TAG, "Detected NT99141 camera"); } else { ESP_LOGI(TAG, "Camera not supported"); err = ESP_ERR_CAMERA_NOT_SUPPORTED; @@ -1370,9 +1442,12 @@ esp_err_t esp_camera_deinit() } dma_desc_deinit(); camera_fb_deinit(); + + if(s_state->config.pin_xclk >= 0) { + camera_disable_out_clock(); + } free(s_state); s_state = NULL; - camera_disable_out_clock(); periph_module_disable(PERIPH_I2S0_MODULE); return ESP_OK; } @@ -1430,11 +1505,11 @@ sensor_t * esp_camera_sensor_get() esp_err_t esp_camera_save_to_nvs(const char *key) { -#if ESP_IDF_VERSION_MAJOR > 3 - nvs_handle_t handle; -#else +//#if ESP_IDF_VERSION_MAJOR > 3 +// nvs_handle_t handle; +//#else nvs_handle handle; -#endif +//#endif esp_err_t ret = nvs_open(key,NVS_READWRITE,&handle); if (ret == ESP_OK) { @@ -1458,11 +1533,11 @@ esp_err_t esp_camera_save_to_nvs(const char *key) esp_err_t esp_camera_load_from_nvs(const char *key) { -#if ESP_IDF_VERSION_MAJOR > 3 - nvs_handle_t handle; -#else +//#if ESP_IDF_VERSION_MAJOR > 3 +// nvs_handle_t handle; +//#else nvs_handle handle; -#endif +//#endif uint8_t pf; esp_err_t ret = nvs_open(key,NVS_READWRITE,&handle); diff --git a/esp32-cam-rtos/esp32-cam-rtos.ino b/esp32-cam-rtos/esp32-cam-rtos.ino index f7c97c6..4479e21 100644 --- a/esp32-cam-rtos/esp32-cam-rtos.ino +++ b/esp32-cam-rtos/esp32-cam-rtos.ino @@ -480,7 +480,9 @@ void setup() ESP.restart(); } - + sensor_t* s = esp_camera_sensor_get(); + s->set_vflip(s, true); + // Configure and connect to WiFi IPAddress ip; diff --git a/esp32-cam-rtos/esp32-camera-master.zip b/esp32-cam-rtos/esp32-camera-master.zip deleted file mode 100644 index e8118ae8cbde083aa2d898912ca0840cbb1ba02f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 99319 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%0DWVpqK+tQ4{dZyTI7z{pE?+UbMA=x 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

ccC}J~5Ws<*6rFR> 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 + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * NT99141 driver. + * + */ +#include +#include +#include +#include "sccb.h" +#include "nt99141.h" +#include "nt99141_regs.h" +#include "nt99141_settings.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#else +#include "esp_log.h" +static const char *TAG = "NT99141"; +#endif + +//#define REG_DEBUG_ON + +static int read_reg(uint8_t slv_addr, const uint16_t reg) +{ + int ret = SCCB_Read16(slv_addr, reg); +#ifdef REG_DEBUG_ON + + if (ret < 0) { + ESP_LOGE(TAG, "READ REG 0x%04x FAILED: %d", reg, ret); + } + +#endif + return ret; +} + +static int check_reg_mask(uint8_t slv_addr, uint16_t reg, uint8_t mask) +{ + return (read_reg(slv_addr, reg) & mask) == mask; +} + +static int read_reg16(uint8_t slv_addr, const uint16_t reg) +{ + int ret = 0, ret2 = 0; + ret = read_reg(slv_addr, reg); + + if (ret >= 0) { + ret = (ret & 0xFF) << 8; + ret2 = read_reg(slv_addr, reg + 1); + + if (ret2 < 0) { + ret = ret2; + } else { + ret |= ret2 & 0xFF; + } + } + + return ret; +} + + +static int write_reg(uint8_t slv_addr, const uint16_t reg, uint8_t value) +{ + int ret = 0; +#ifndef REG_DEBUG_ON + ret = SCCB_Write16(slv_addr, reg, value); +#else + int old_value = read_reg(slv_addr, reg); + + if (old_value < 0) { + return old_value; + } + + if ((uint8_t)old_value != value) { + ESP_LOGD(TAG, "NEW REG 0x%04x: 0x%02x to 0x%02x", reg, (uint8_t)old_value, value); + ret = SCCB_Write16(slv_addr, reg, value); + } else { + ESP_LOGD(TAG, "OLD REG 0x%04x: 0x%02x", reg, (uint8_t)old_value); + ret = SCCB_Write16(slv_addr, reg, value);//maybe not? + } + + if (ret < 0) { + ESP_LOGE(TAG, "WRITE REG 0x%04x FAILED: %d", reg, ret); + } + +#endif + return ret; +} + +static int set_reg_bits(uint8_t slv_addr, uint16_t reg, uint8_t offset, uint8_t mask, uint8_t value) +{ + int ret = 0; + uint8_t c_value, new_value; + ret = read_reg(slv_addr, reg); + + if (ret < 0) { + return ret; + } + + c_value = ret; + new_value = (c_value & ~(mask << offset)) | ((value & mask) << offset); + ret = write_reg(slv_addr, reg, new_value); + return ret; +} + +static int write_regs(uint8_t slv_addr, const uint16_t (*regs)[2]) +{ + int i = 0, ret = 0; + + while (!ret && regs[i][0] != REGLIST_TAIL) { + if (regs[i][0] == REG_DLY) { + vTaskDelay(regs[i][1] / portTICK_PERIOD_MS); + } else { + ret = write_reg(slv_addr, regs[i][0], regs[i][1]); + } + + i++; + } + + return ret; +} + +static int write_reg16(uint8_t slv_addr, const uint16_t reg, uint16_t value) +{ + if (write_reg(slv_addr, reg, value >> 8) || write_reg(slv_addr, reg + 1, value)) { + return -1; + } + + return 0; +} + +static int write_addr_reg(uint8_t slv_addr, const uint16_t reg, uint16_t x_value, uint16_t y_value) +{ + if (write_reg16(slv_addr, reg, x_value) || write_reg16(slv_addr, reg + 2, y_value)) { + return -1; + } + + return 0; +} + +#define write_reg_bits(slv_addr, reg, mask, enable) set_reg_bits(slv_addr, reg, 0, mask, enable?mask:0) + +static int calc_sysclk(int xclk, bool pll_bypass, int pll_multiplier, int pll_sys_div, int pll_pre_div, bool pll_root_2x, int pll_seld5, bool pclk_manual, int pclk_div) +{ + const int pll_pre_div2x_map[] = { 2, 3, 4, 6 };//values are multiplied by two to avoid floats + const int pll_seld52x_map[] = { 2, 2, 4, 5 }; + + if (!pll_sys_div) { + pll_sys_div = 1; + } + + int pll_pre_div2x = pll_pre_div2x_map[pll_pre_div]; + int pll_root_div = pll_root_2x ? 2 : 1; + int pll_seld52x = pll_seld52x_map[pll_seld5]; + + int VCO = (xclk / 1000) * pll_multiplier * pll_root_div * 2 / pll_pre_div2x; + int PLLCLK = pll_bypass ? (xclk) : (VCO * 1000 * 2 / pll_sys_div / pll_seld52x); + int PCLK = PLLCLK / 2 / ((pclk_manual && pclk_div) ? pclk_div : 1); + int SYSCLK = PLLCLK / 4; + + ESP_LOGD(TAG, "Calculated VCO: %d Hz, PLLCLK: %d Hz, SYSCLK: %d Hz, PCLK: %d Hz", VCO * 1000, PLLCLK, SYSCLK, PCLK); + return SYSCLK; +} + +static int set_pll(sensor_t *sensor, bool bypass, uint8_t multiplier, uint8_t sys_div, uint8_t pre_div, bool root_2x, uint8_t seld5, bool pclk_manual, uint8_t pclk_div) +{ + return -1; +} + +static int set_ae_level(sensor_t *sensor, int level); + +static int reset(sensor_t *sensor) +{ + + int ret = 0; + // Software Reset: clear all registers and reset them to their default values + ret = write_reg(sensor->slv_addr, SYSTEM_CTROL0, 0x01); + + if (ret) { + ESP_LOGE(TAG, "Software Reset FAILED!"); + return ret; + } + + vTaskDelay(100 / portTICK_PERIOD_MS); + ret = write_regs(sensor->slv_addr, sensor_default_regs); //re-initial + + if (ret == 0) { + ESP_LOGD(TAG, "Camera defaults loaded"); + ret = set_ae_level(sensor, 0); + vTaskDelay(100 / portTICK_PERIOD_MS); + } + + return ret; +} + +static int set_pixformat(sensor_t *sensor, pixformat_t pixformat) +{ + int ret = 0; + const uint16_t (*regs)[2]; + + switch (pixformat) { + case PIXFORMAT_YUV422: + regs = sensor_fmt_yuv422; + break; + + case PIXFORMAT_GRAYSCALE: + regs = sensor_fmt_grayscale; + break; + + case PIXFORMAT_RGB565: + case PIXFORMAT_RGB888: + regs = sensor_fmt_rgb565; + break; + + case PIXFORMAT_JPEG: + regs = sensor_fmt_jpeg; + break; + + case PIXFORMAT_RAW: + regs = sensor_fmt_raw; + break; + + default: + ESP_LOGE(TAG, "Unsupported pixformat: %u", pixformat); + return -1; + } + + ret = write_regs(sensor->slv_addr, regs); + + if (ret == 0) { + sensor->pixformat = pixformat; + ESP_LOGD(TAG, "Set pixformat to: %u", pixformat); + } + + return ret; +} + +static int set_image_options(sensor_t *sensor) +{ + int ret = 0; + uint8_t reg20 = 0; + uint8_t reg21 = 0; + uint8_t reg4514 = 0; + uint8_t reg4514_test = 0; + + // V-Flip + if (sensor->status.vflip) { + reg20 |= 0x01; + reg4514_test |= 1; + } + + // H-Mirror + if (sensor->status.hmirror) { + reg21 |= 0x02; + reg4514_test |= 2; + } + + switch (reg4514_test) { + + } + + if (write_reg(sensor->slv_addr, TIMING_TC_REG20, reg20 | reg21)) { + ESP_LOGE(TAG, "Setting Image Options Failed"); + ret = -1; + } + + ESP_LOGD(TAG, "Set Image Options: Compression: %u, Binning: %u, V-Flip: %u, H-Mirror: %u, Reg-4514: 0x%02x", + sensor->pixformat == PIXFORMAT_JPEG, sensor->status.binning, sensor->status.vflip, sensor->status.hmirror, reg4514); + return ret; +} + +static int set_framesize(sensor_t *sensor, framesize_t framesize) +{ + int ret = 0; + + sensor->status.framesize = framesize; + ret = write_regs(sensor->slv_addr, sensor_default_regs); + + if (framesize == FRAMESIZE_QVGA) { + ESP_LOGD(TAG, "Set FRAMESIZE_QVGA"); + ret = write_regs(sensor->slv_addr, sensor_framesize_QVGA); +#if CONFIG_NT99141_SUPPORT_XSKIP + ESP_LOGD(TAG, "Set FRAMESIZE_QVGA: xskip mode"); + ret = write_regs(sensor->slv_addr, sensor_framesize_QVGA_xskip); +#elif CONFIG_NT99141_SUPPORT_CROP + ESP_LOGD(TAG, "Set FRAMESIZE_QVGA: crop mode"); + ret = write_regs(sensor->slv_addr, sensor_framesize_QVGA_crop); +#endif + } else if (framesize == FRAMESIZE_VGA) { + ESP_LOGD(TAG, "Set FRAMESIZE_VGA"); + // ret = write_regs(sensor->slv_addr, sensor_framesize_VGA); + ret = write_regs(sensor->slv_addr, sensor_framesize_VGA_xyskip);// Resolution:640*360 This configuration is equally-scaled without deforming +#ifdef CONFIG_NT99141_SUPPORT_XSKIP + ESP_LOGD(TAG, "Set FRAMESIZE_QVGA: xskip mode"); + ret = write_regs(sensor->slv_addr, sensor_framesize_VGA_xskip); +#elif CONFIG_NT99141_SUPPORT_CROP + ESP_LOGD(TAG, "Set FRAMESIZE_QVGA: crop mode"); + ret = write_regs(sensor->slv_addr, sensor_framesize_VGA_crop); +#endif + } else if (framesize >= FRAMESIZE_HD) { + ESP_LOGD(TAG, "Set FRAMESIZE_HD"); + ret = write_regs(sensor->slv_addr, sensor_framesize_HD); + } else { + ESP_LOGD(TAG, "Dont suppost this size, Set FRAMESIZE_VGA"); + ret = write_regs(sensor->slv_addr, sensor_framesize_VGA); + } + + return 0; +} + +static int set_hmirror(sensor_t *sensor, int enable) +{ + int ret = 0; + sensor->status.hmirror = enable; + ret = set_image_options(sensor); + + if (ret == 0) { + ESP_LOGD(TAG, "Set h-mirror to: %d", enable); + } + + return ret; +} + +static int set_vflip(sensor_t *sensor, int enable) +{ + int ret = 0; + sensor->status.vflip = enable; + ret = set_image_options(sensor); + + if (ret == 0) { + ESP_LOGD(TAG, "Set v-flip to: %d", enable); + } + + return ret; +} + +static int set_quality(sensor_t *sensor, int qs) +{ + int ret = 0; + ret = write_reg(sensor->slv_addr, COMPRESSION_CTRL07, qs & 0x3f); + + if (ret == 0) { + sensor->status.quality = qs; + ESP_LOGD(TAG, "Set quality to: %d", qs); + } + + return ret; +} + +static int set_colorbar(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, PRE_ISP_TEST_SETTING_1, TEST_COLOR_BAR, enable); + + if (ret == 0) { + sensor->status.colorbar = enable; + ESP_LOGD(TAG, "Set colorbar to: %d", enable); + } + + return ret; +} + +static int set_gain_ctrl(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, 0x32bb, 0x87, enable); + + if (ret == 0) { + ESP_LOGD(TAG, "Set gain_ctrl to: %d", enable); + sensor->status.agc = enable; + } + + return ret; +} + +static int set_exposure_ctrl(sensor_t *sensor, int enable) +{ + int ret = 0; + int data = 0; + // ret = write_reg_bits(sensor->slv_addr, 0x32bb, 0x87, enable); + data = read_reg(sensor->slv_addr, 0x3201); + ESP_LOGD(TAG, "set_exposure_ctrl:enable"); + if (enable) { + ESP_LOGD(TAG, "set_exposure_ctrl:enable"); + ret = write_reg(sensor->slv_addr, 0x3201, (1 << 5) | data); + } else { + ESP_LOGD(TAG, "set_exposure_ctrl:disable"); + ret = write_reg(sensor->slv_addr, 0x3201, (~(1 << 5)) & data); + } + + if (ret == 0) { + ESP_LOGD(TAG, "Set exposure_ctrl to: %d", enable); + sensor->status.aec = enable; + } + + return ret; +} + +static int set_whitebal(sensor_t *sensor, int enable) +{ + int ret = 0; + + if (ret == 0) { + ESP_LOGD(TAG, "Set awb to: %d", enable); + sensor->status.awb = enable; + } + + return ret; +} + +//Advanced AWB +static int set_dcw_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + + if (ret == 0) { + ESP_LOGD(TAG, "Set dcw to: %d", enable); + sensor->status.dcw = enable; + } + + return ret; +} + +//night mode enable +static int set_aec2(sensor_t *sensor, int enable) +{ + int ret = 0; + + if (ret == 0) { + ESP_LOGD(TAG, "Set aec2 to: %d", enable); + sensor->status.aec2 = enable; + } + + return ret; +} + +static int set_bpc_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + + if (ret == 0) { + ESP_LOGD(TAG, "Set bpc to: %d", enable); + sensor->status.bpc = enable; + } + + return ret; +} + +static int set_wpc_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + + if (ret == 0) { + ESP_LOGD(TAG, "Set wpc to: %d", enable); + sensor->status.wpc = enable; + } + + return ret; +} + +//Gamma enable +static int set_raw_gma_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + + if (ret == 0) { + ESP_LOGD(TAG, "Set raw_gma to: %d", enable); + sensor->status.raw_gma = enable; + } + + return ret; +} + +static int set_lenc_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + + if (ret == 0) { + ESP_LOGD(TAG, "Set lenc to: %d", enable); + sensor->status.lenc = enable; + } + + return ret; +} + +static int get_agc_gain(sensor_t *sensor) +{ + ESP_LOGD(TAG, "get_agc_gain can not be configured at present"); + return 0; +} + +//real gain +static int set_agc_gain(sensor_t *sensor, int gain) +{ + ESP_LOGD(TAG, "set_agc_gain can not be configured at present"); + // ESP_LOGD(TAG, "GAIN = %d\n", gain); + int cnt = gain / 2; + + switch (cnt) { + case 0: + ESP_LOGD(TAG, "set_agc_gain: 1x"); + write_reg(sensor->slv_addr, 0X301D, 0X00); + break; + + case 1: + ESP_LOGD(TAG,"set_agc_gain: 2x"); + write_reg(sensor->slv_addr, 0X301D, 0X0F); + break; + + case 2: + ESP_LOGD(TAG,"set_agc_gain: 4x"); + write_reg(sensor->slv_addr, 0X301D, 0X2F); + break; + + case 3: + ESP_LOGD(TAG,"set_agc_gain: 6x"); + write_reg(sensor->slv_addr, 0X301D, 0X37); + break; + + case 4: + ESP_LOGD(TAG,"set_agc_gain: 8x"); + write_reg(sensor->slv_addr, 0X301D, 0X3F); + break; + + default: + ESP_LOGD(TAG,"fail set_agc_gain"); + break; + } + + return 0; +} + +static int get_aec_value(sensor_t *sensor) +{ + ESP_LOGD(TAG, "get_aec_value can not be configured at present"); + return 0; +} + +static int set_aec_value(sensor_t *sensor, int value) +{ + ESP_LOGD(TAG, "set_aec_value can not be configured at present"); + int ret = 0; + // ESP_LOGD(TAG, " set_aec_value to: %d", value); + ret = write_reg_bits(sensor->slv_addr, 0x3012, 0x00, (value >> 8) & 0xff); + ret = write_reg_bits(sensor->slv_addr, 0x3013, 0x01, value & 0xff); + + if (ret == 0) { + ESP_LOGD(TAG, " set_aec_value to: %d", value); + // sensor->status.aec = enable; + } + + return ret; +} + +static int set_ae_level(sensor_t *sensor, int level) +{ + ESP_LOGD(TAG, "set_ae_level can not be configured at present"); + int ret = 0; + + if (level < 0) { + level = 0; + } else if (level > 9) { + level = 9; + } + + for (int i = 0; i < 5; i++) { + ret += write_reg(sensor->slv_addr, sensor_ae_level[ 5 * level + i ][0], sensor_ae_level[5 * level + i ][1]); + } + + if (ret) { + ESP_LOGE(TAG, " fail to set ae level: %d", ret); + } + + return 0; +} + +static int set_wb_mode(sensor_t *sensor, int mode) +{ + int ret = 0; + + if (mode < 0 || mode > 4) { + return -1; + } + + ret = write_reg(sensor->slv_addr, 0x3201, (mode != 0)); + + if (ret) { + return ret; + } + + switch (mode) { + case 1://Sunny + ret = write_reg16(sensor->slv_addr, 0x3290, 0x01) + || write_reg16(sensor->slv_addr, 0x3291, 0x38) + || write_reg16(sensor->slv_addr, 0x3296, 0x01) + || write_reg16(sensor->slv_addr, 0x3297, 0x68) + || write_reg16(sensor->slv_addr, 0x3060, 0x01); + + break; + + case 2://Cloudy + + ret = write_reg16(sensor->slv_addr, 0x3290, 0x01) + || write_reg16(sensor->slv_addr, 0x3291, 0x51) + || write_reg16(sensor->slv_addr, 0x3296, 0x01) + || write_reg16(sensor->slv_addr, 0x3297, 0x00) + || write_reg16(sensor->slv_addr, 0x3060, 0x01); + break; + + case 3://INCANDESCENCE] + ret = write_reg16(sensor->slv_addr, 0x3290, 0x01) + || write_reg16(sensor->slv_addr, 0x3291, 0x30) + || write_reg16(sensor->slv_addr, 0x3296, 0x01) + || write_reg16(sensor->slv_addr, 0x3297, 0xCB) + || write_reg16(sensor->slv_addr, 0x3060, 0x01); + break; + + case 4://FLUORESCENT + ret = write_reg16(sensor->slv_addr, 0x3290, 0x01) + || write_reg16(sensor->slv_addr, 0x3291, 0x70) + || write_reg16(sensor->slv_addr, 0x3296, 0x01) + || write_reg16(sensor->slv_addr, 0x3297, 0xFF) + || write_reg16(sensor->slv_addr, 0x3060, 0x01); + break; + + default://AUTO + break; + } + + if (ret == 0) { + ESP_LOGD(TAG, "Set wb_mode to: %d", mode); + sensor->status.wb_mode = mode; + } + + return ret; +} + +static int set_awb_gain_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + int old_mode = sensor->status.wb_mode; + int mode = enable ? old_mode : 0; + + ret = set_wb_mode(sensor, mode); + + if (ret == 0) { + sensor->status.wb_mode = old_mode; + ESP_LOGD(TAG, "Set awb_gain to: %d", enable); + sensor->status.awb_gain = enable; + } + + return ret; +} + +static int set_special_effect(sensor_t *sensor, int effect) +{ + int ret = 0; + + if (effect < 0 || effect > 6) { + return -1; + } + + uint8_t *regs = (uint8_t *)sensor_special_effects[effect]; + ret = write_reg(sensor->slv_addr, 0x32F1, regs[0]) + || write_reg(sensor->slv_addr, 0x32F4, regs[1]) + || write_reg(sensor->slv_addr, 0x32F5, regs[2]) + || write_reg(sensor->slv_addr, 0x3060, regs[3]); + + if (ret == 0) { + ESP_LOGD(TAG, "Set special_effect to: %d", effect); + sensor->status.special_effect = effect; + } + + return ret; +} + +static int set_brightness(sensor_t *sensor, int level) +{ + int ret = 0; + uint8_t value = 0; + bool negative = false; + + switch (level) { + case 3: + value = 0xA0; + break; + + case 2: + value = 0x90; + break; + + case 1: + value = 0x88; + break; + + case -1: + value = 0x78; + negative = true; + break; + + case -2: + value = 0x70; + negative = true; + break; + + case -3: + value = 0x60; + negative = true; + break; + + default: // 0 + break; + } + + ret = write_reg(sensor->slv_addr, 0x32F2, value); + + if (ret == 0) { + ESP_LOGD(TAG, "Set brightness to: %d", level); + sensor->status.brightness = level; + } + + return ret; +} + +static int set_contrast(sensor_t *sensor, int level) +{ + int ret = 0; + uint8_t value1 = 0, value2 = 0 ; + bool negative = false; + + switch (level) { + case 3: + value1 = 0xD0; + value2 = 0xB0; + break; + + case 2: + value1 = 0xE0; + value2 = 0xA0; + break; + + case 1: + value1 = 0xF0; + value2 = 0x90; + break; + + case 0: + value1 = 0x00; + value2 = 0x80; + break; + + case -1: + value1 = 0x10; + value2 = 0x70; + break; + + case -2: + value1 = 0x20; + value2 = 0x60; + break; + + case -3: + value1 = 0x30; + value2 = 0x50; + break; + + default: // 0 + break; + } + + ret = write_reg(sensor->slv_addr, 0x32FC, value1); + ret = write_reg(sensor->slv_addr, 0x32F2, value2); + ret = write_reg(sensor->slv_addr, 0x3060, 0x01); + + if (ret == 0) { + ESP_LOGD(TAG, "Set contrast to: %d", level); + sensor->status.contrast = level; + } + + return ret; +} + +static int set_saturation(sensor_t *sensor, int level) +{ + int ret = 0; + + if (level > 4 || level < -4) { + return -1; + } + + uint8_t *regs = (uint8_t *)sensor_saturation_levels[level + 4]; + { + ret = write_reg(sensor->slv_addr, 0x32F3, regs[0]); + + if (ret) { + return ret; + } + } + + if (ret == 0) { + ESP_LOGD(TAG, "Set saturation to: %d", level); + sensor->status.saturation = level; + } + + return ret; +} + +static int set_sharpness(sensor_t *sensor, int level) +{ + int ret = 0; + + if (level > 3 || level < -3) { + return -1; + } + + uint8_t mt_offset_2 = (level + 3) * 8; + uint8_t mt_offset_1 = mt_offset_2 + 1; + + ret = write_reg_bits(sensor->slv_addr, 0x5308, 0x40, false)//0x40 means auto + || write_reg(sensor->slv_addr, 0x5300, 0x10) + || write_reg(sensor->slv_addr, 0x5301, 0x10) + || write_reg(sensor->slv_addr, 0x5302, mt_offset_1) + || write_reg(sensor->slv_addr, 0x5303, mt_offset_2) + || write_reg(sensor->slv_addr, 0x5309, 0x10) + || write_reg(sensor->slv_addr, 0x530a, 0x10) + || write_reg(sensor->slv_addr, 0x530b, 0x04) + || write_reg(sensor->slv_addr, 0x530c, 0x06); + + if (ret == 0) { + ESP_LOGD(TAG, "Set sharpness to: %d", level); + sensor->status.sharpness = level; + } + + return ret; +} + +static int set_gainceiling(sensor_t *sensor, gainceiling_t level) +{ + ESP_LOGD(TAG, "set_gainceiling can not be configured at present"); + return 0; +} + +static int get_denoise(sensor_t *sensor) +{ + + return (read_reg(sensor->slv_addr, 0x5306) / 4) + 1; +} + +static int set_denoise(sensor_t *sensor, int level) +{ + ESP_LOGD(TAG, "set_denoise can not be configured at present"); + return 0; +} + +static int get_reg(sensor_t *sensor, int reg, int mask) +{ + int ret = 0, ret2 = 0; + + if (mask > 0xFF) { + ret = read_reg16(sensor->slv_addr, reg); + + if (ret >= 0 && mask > 0xFFFF) { + ret2 = read_reg(sensor->slv_addr, reg + 2); + + if (ret2 >= 0) { + ret = (ret << 8) | ret2 ; + } else { + ret = ret2; + } + } + } else { + ret = read_reg(sensor->slv_addr, reg); + } + + if (ret > 0) { + ret &= mask; + } + + return ret; +} + +static int set_reg(sensor_t *sensor, int reg, int mask, int value) +{ + int ret = 0, ret2 = 0; + + if (mask > 0xFF) { + ret = read_reg16(sensor->slv_addr, reg); + + if (ret >= 0 && mask > 0xFFFF) { + ret2 = read_reg(sensor->slv_addr, reg + 2); + + if (ret2 >= 0) { + ret = (ret << 8) | ret2 ; + } else { + ret = ret2; + } + } + } else { + ret = read_reg(sensor->slv_addr, reg); + } + + if (ret < 0) { + return ret; + } + + value = (ret & ~mask) | (value & mask); + + if (mask > 0xFFFF) { + ret = write_reg16(sensor->slv_addr, reg, value >> 8); + + if (ret >= 0) { + ret = write_reg(sensor->slv_addr, reg + 2, value & 0xFF); + } + } else if (mask > 0xFF) { + ret = write_reg16(sensor->slv_addr, reg, value); + } else { + ret = write_reg(sensor->slv_addr, reg, value); + } + + return ret; +} + +static int set_res_raw(sensor_t *sensor, int startX, int startY, int endX, int endY, int offsetX, int offsetY, int totalX, int totalY, int outputX, int outputY, bool scale, bool binning) +{ + int ret = 0; + ret = write_addr_reg(sensor->slv_addr, X_ADDR_ST_H, startX, startY) + || write_addr_reg(sensor->slv_addr, X_ADDR_END_H, endX, endY) + || write_addr_reg(sensor->slv_addr, X_OFFSET_H, offsetX, offsetY) + || write_addr_reg(sensor->slv_addr, X_TOTAL_SIZE_H, totalX, totalY) + || write_addr_reg(sensor->slv_addr, X_OUTPUT_SIZE_H, outputX, outputY); + + if (!ret) { + sensor->status.scale = scale; + sensor->status.binning = binning; + ret = set_image_options(sensor); + } + + return ret; +} + +static int _set_pll(sensor_t *sensor, int bypass, int multiplier, int sys_div, int root_2x, int pre_div, int seld5, int pclk_manual, int pclk_div) +{ + return set_pll(sensor, bypass > 0, multiplier, sys_div, pre_div, root_2x > 0, seld5, pclk_manual > 0, pclk_div); +} + +esp_err_t xclk_timer_conf(int ledc_timer, int xclk_freq_hz); +static int set_xclk(sensor_t *sensor, int timer, int xclk) +{ + int ret = 0; + if (xclk > 10) + { + ESP_LOGE(TAG, "only XCLK under 10MHz is supported, and XCLK is now set to 10M"); + xclk = 10; + } + sensor->xclk_freq_hz = xclk * 1000000U; + ret = xclk_timer_conf(timer, sensor->xclk_freq_hz); + return ret; +} + +static int init_status(sensor_t *sensor) +{ + sensor->status.brightness = 0; + sensor->status.contrast = 0; + sensor->status.saturation = 0; + sensor->status.sharpness = (read_reg(sensor->slv_addr, 0x3301)); + sensor->status.denoise = get_denoise(sensor); + sensor->status.ae_level = 0; + sensor->status.gainceiling = read_reg16(sensor->slv_addr, 0x32F0) & 0xFF; + sensor->status.awb = check_reg_mask(sensor->slv_addr, ISP_CONTROL_01, 0x10); + sensor->status.dcw = !check_reg_mask(sensor->slv_addr, 0x5183, 0x80); + sensor->status.agc = !check_reg_mask(sensor->slv_addr, AEC_PK_MANUAL, AEC_PK_MANUAL_AGC_MANUALEN); + sensor->status.aec = !check_reg_mask(sensor->slv_addr, AEC_PK_MANUAL, AEC_PK_MANUAL_AEC_MANUALEN); + sensor->status.hmirror = check_reg_mask(sensor->slv_addr, TIMING_TC_REG21, TIMING_TC_REG21_HMIRROR); + sensor->status.vflip = check_reg_mask(sensor->slv_addr, TIMING_TC_REG20, TIMING_TC_REG20_VFLIP); + sensor->status.colorbar = check_reg_mask(sensor->slv_addr, PRE_ISP_TEST_SETTING_1, TEST_COLOR_BAR); + sensor->status.bpc = check_reg_mask(sensor->slv_addr, 0x5000, 0x04); + sensor->status.wpc = check_reg_mask(sensor->slv_addr, 0x5000, 0x02); + sensor->status.raw_gma = check_reg_mask(sensor->slv_addr, 0x5000, 0x20); + sensor->status.lenc = check_reg_mask(sensor->slv_addr, 0x5000, 0x80); + sensor->status.quality = read_reg(sensor->slv_addr, COMPRESSION_CTRL07) & 0x3f; + sensor->status.special_effect = 0; + sensor->status.wb_mode = 0; + sensor->status.awb_gain = check_reg_mask(sensor->slv_addr, 0x3000, 0x01); + sensor->status.agc_gain = get_agc_gain(sensor); + sensor->status.aec_value = get_aec_value(sensor); + sensor->status.aec2 = check_reg_mask(sensor->slv_addr, 0x3000, 0x04); + return 0; +} + +int NT99141_init(sensor_t *sensor) +{ + sensor->reset = reset; + sensor->set_pixformat = set_pixformat; + sensor->set_framesize = set_framesize; + sensor->set_contrast = set_contrast; + sensor->set_brightness = set_brightness; + sensor->set_saturation = set_saturation; + sensor->set_sharpness = set_sharpness; + sensor->set_gainceiling = set_gainceiling; + sensor->set_quality = set_quality; + sensor->set_colorbar = set_colorbar; + sensor->set_gain_ctrl = set_gain_ctrl; + sensor->set_exposure_ctrl = set_exposure_ctrl; + sensor->set_whitebal = set_whitebal; + sensor->set_hmirror = set_hmirror; + sensor->set_vflip = set_vflip; + sensor->init_status = init_status; + sensor->set_aec2 = set_aec2; + sensor->set_aec_value = set_aec_value; + sensor->set_special_effect = set_special_effect; + sensor->set_wb_mode = set_wb_mode; + sensor->set_ae_level = set_ae_level; + sensor->set_dcw = set_dcw_dsp; + sensor->set_bpc = set_bpc_dsp; + sensor->set_wpc = set_wpc_dsp; + sensor->set_awb_gain = set_awb_gain_dsp; + sensor->set_agc_gain = set_agc_gain; + sensor->set_raw_gma = set_raw_gma_dsp; + sensor->set_lenc = set_lenc_dsp; + sensor->set_denoise = set_denoise; + + sensor->get_reg = get_reg; + sensor->set_reg = set_reg; + sensor->set_res_raw = set_res_raw; + sensor->set_pll = _set_pll; + sensor->set_xclk = set_xclk; + return 0; +} diff --git a/esp32-cam-rtos/nt99141.h b/esp32-cam-rtos/nt99141.h new file mode 100644 index 0000000..287a742 --- /dev/null +++ b/esp32-cam-rtos/nt99141.h @@ -0,0 +1,16 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * NT99141 driver. + * + */ +#ifndef __NT99141_H__ +#define __NT99141_H__ + +#include "sensor.h" + +int NT99141_init(sensor_t *sensor); + +#endif // __NT99141_H__ diff --git a/esp32-cam-rtos/nt99141_regs.h b/esp32-cam-rtos/nt99141_regs.h new file mode 100644 index 0000000..8301db9 --- /dev/null +++ b/esp32-cam-rtos/nt99141_regs.h @@ -0,0 +1,211 @@ +/* + * NT99141 register definitions. + */ +#ifndef __NT99141_REG_REGS_H__ +#define __NT99141_REG_REGS_H__ + +/* system control registers */ +#define SYSTEM_CTROL0 0x3021 // 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 + +/* output format control registers */ +#define FORMAT_CTRL 0x501F // Format select + // Bit[2:0]: + // 000: YUV422 + // 001: RGB + // 010: Dither + // 011: RAW after DPC + // 101: RAW after CIP + +/* format control registers */ +#define FORMAT_CTRL00 0x4300 + +/* frame control registers */ +#define FRAME_CTRL01 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 +#define FRAME_CTRL02 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 + +/* ISP top control registers */ +#define PRE_ISP_TEST_SETTING_1 0x3025 // 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 + +/* AEC/AGC control functions */ +#define AEC_PK_MANUAL 0x3201 // 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 + +/* mirror and flip registers */ +#define TIMING_TC_REG20 0x3022 // Timing Control Register + // Bit[2:1]: Vertical flip enable + // 00: Normal + // 11: Vertical flip + // Bit[0]: Vertical binning enable +#define TIMING_TC_REG21 0x3022 // Timing Control Register + // Bit[5]: Compression Enable + // Bit[2:1]: Horizontal mirror enable + // 00: Normal + // 11: Horizontal mirror + // Bit[0]: Horizontal binning enable + +#define CLOCK_POL_CONTROL 0x3024// 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 +#define DRIVE_CAPABILITY 0x306a // Bit[7:6]: + // 00: 1x + // 01: 2x + // 10: 3x + // 11: 4x + + +#define X_ADDR_ST_H 0x3800 //Bit[3:0]: X address start[11:8] +#define X_ADDR_ST_L 0x3801 //Bit[7:0]: X address start[7:0] +#define Y_ADDR_ST_H 0x3802 //Bit[2:0]: Y address start[10:8] +#define Y_ADDR_ST_L 0x3803 //Bit[7:0]: Y address start[7:0] +#define X_ADDR_END_H 0x3804 //Bit[3:0]: X address end[11:8] +#define X_ADDR_END_L 0x3805 //Bit[7:0]: +#define Y_ADDR_END_H 0x3806 //Bit[2:0]: Y address end[10:8] +#define Y_ADDR_END_L 0x3807 //Bit[7:0]: +// Size after scaling +#define X_OUTPUT_SIZE_H 0x3808 //Bit[3:0]: DVP output horizontal width[11:8] +#define X_OUTPUT_SIZE_L 0x3809 //Bit[7:0]: +#define Y_OUTPUT_SIZE_H 0x380a //Bit[2:0]: DVP output vertical height[10:8] +#define Y_OUTPUT_SIZE_L 0x380b //Bit[7:0]: +#define X_TOTAL_SIZE_H 0x380c //Bit[3:0]: Total horizontal size[11:8] +#define X_TOTAL_SIZE_L 0x380d //Bit[7:0]: +#define Y_TOTAL_SIZE_H 0x380e //Bit[7:0]: Total vertical size[15:8] +#define Y_TOTAL_SIZE_L 0x380f //Bit[7:0]: +#define X_OFFSET_H 0x3810 //Bit[3:0]: ISP horizontal offset[11:8] +#define X_OFFSET_L 0x3811 //Bit[7:0]: +#define Y_OFFSET_H 0x3812 //Bit[2:0]: ISP vertical offset[10:8] +#define Y_OFFSET_L 0x3813 //Bit[7:0]: +#define X_INCREMENT 0x3814 //Bit[7:4]: Horizontal odd subsample increment + //Bit[3:0]: Horizontal even subsample increment +#define Y_INCREMENT 0x3815 //Bit[7:4]: Vertical odd subsample increment + //Bit[3:0]: Vertical even subsample increment +// Size before scaling +//#define X_INPUT_SIZE (X_ADDR_END - X_ADDR_ST + 1 - (2 * X_OFFSET)) +//#define Y_INPUT_SIZE (Y_ADDR_END - Y_ADDR_ST + 1 - (2 * Y_OFFSET)) + +#define ISP_CONTROL_01 0x3021 // Bit[5]: Scale enable + // 0: Disable + // 1: Enable + +#define SCALE_CTRL_1 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 + +#define SCALE_CTRL_2 0x5602 // X_SCALE High Bits +#define SCALE_CTRL_3 0x5603 // X_SCALE Low Bits +#define SCALE_CTRL_4 0x5604 // Y_SCALE High Bits +#define SCALE_CTRL_5 0x5605 // Y_SCALE Low Bits +#define SCALE_CTRL_6 0x5606 // Bit[3:0]: V Offset + +#define PCLK_RATIO 0x3824 // Bit[4:0]: PCLK ratio manual +#define VFIFO_CTRL0C 0x460C // Bit[1]: PCLK manual enable + // 0: Auto + // 1: Manual by PCLK_RATIO + +#define VFIFO_X_SIZE_H 0x4602 +#define VFIFO_X_SIZE_L 0x4603 +#define VFIFO_Y_SIZE_H 0x4604 +#define VFIFO_Y_SIZE_L 0x4605 + +#define SC_PLLS_CTRL0 0x303a // Bit[7]: PLLS bypass +#define SC_PLLS_CTRL1 0x303b // Bit[4:0]: PLLS multiplier +#define SC_PLLS_CTRL2 0x303c // Bit[6:4]: PLLS charge pump control + // Bit[3:0]: PLLS system divider +#define SC_PLLS_CTRL3 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 + +#define COMPRESSION_CTRL00 0x4400 // +#define COMPRESSION_CTRL01 0x4401 // +#define COMPRESSION_CTRL02 0x4402 // +#define COMPRESSION_CTRL03 0x4403 // +#define COMPRESSION_CTRL04 0x4404 // +#define COMPRESSION_CTRL05 0x4405 // +#define COMPRESSION_CTRL06 0x4406 // +#define COMPRESSION_CTRL07 0x3401 // Bit[5:0]: QS +#define COMPRESSION_ISI_CTRL 0x4408 // +#define COMPRESSION_CTRL09 0x4409 // +#define COMPRESSION_CTRL0a 0x440a // +#define COMPRESSION_CTRL0b 0x440b // +#define COMPRESSION_CTRL0c 0x440c // +#define COMPRESSION_CTRL0d 0x440d // +#define COMPRESSION_CTRL0E 0x440e // + +/** + * @brief register value + */ +#define TEST_COLOR_BAR 0x02 /* Enable Color Bar roling Test */ + +#define AEC_PK_MANUAL_AGC_MANUALEN 0x02 /* Enable AGC Manual enable */ +#define AEC_PK_MANUAL_AEC_MANUALEN 0x01 /* Enable AEC Manual enable */ + +#define TIMING_TC_REG20_VFLIP 0x01 /* Vertical flip enable */ +#define TIMING_TC_REG21_HMIRROR 0x02 /* Horizontal mirror enable */ + +#endif // __NT99141_REG_REGS_H__ diff --git a/esp32-cam-rtos/nt99141_settings.h b/esp32-cam-rtos/nt99141_settings.h new file mode 100644 index 0000000..1ffec20 --- /dev/null +++ b/esp32-cam-rtos/nt99141_settings.h @@ -0,0 +1,825 @@ +#ifndef _NT99141_SETTINGS_H_ +#define _NT99141_SETTINGS_H_ + +#include +#include +#include "esp_attr.h" +#include "nt99141_regs.h" + +static const ratio_settings_t ratio_table[] = { + // mw, mh, sx, sy, ex, ey, ox, oy, tx, ty + { 1280, 720, 0, 4, 1283, 723, 0, 4, 1660, 963 }, + +}; + +#define REG_DLY 0xffff +#define REGLIST_TAIL 0x0000 + +static const DRAM_ATTR uint16_t sensor_default_regs[][2] = { + //initial +{0x3021, 0x00}, +{REG_DLY, 100}, // delay 100ms +{0x3109, 0x04}, +{0x3040, 0x04}, +{0x3041, 0x02}, +{0x3042, 0xFF}, +{0x3043, 0x08}, +{0x3052, 0xE0}, +{0x305F, 0x33}, +{0x3100, 0x07}, +{0x3106, 0x03}, +{0x3105, 0x01}, +{0x3108, 0x05}, +{0x3110, 0x22}, +{0x3111, 0x57}, +{0x3112, 0x22}, +{0x3113, 0x55}, +{0x3114, 0x05}, +{0x3135, 0x00}, +{0x32F0, 0x01}, +{0x3290, 0x01}, +{0x3291, 0x80}, +{0x3296, 0x01}, +{0x3297, 0x73}, +{0x3250, 0x80}, +{0x3251, 0x03}, +{0x3252, 0xFF}, +{0x3253, 0x00}, +{0x3254, 0x03}, +{0x3255, 0xFF}, +{0x3256, 0x00}, +{0x3257, 0x50}, +{0x3270, 0x00}, +{0x3271, 0x0C}, +{0x3272, 0x18}, +{0x3273, 0x32}, +{0x3274, 0x44}, +{0x3275, 0x54}, +{0x3276, 0x70}, +{0x3277, 0x88}, +{0x3278, 0x9D}, +{0x3279, 0xB0}, +{0x327A, 0xCF}, +{0x327B, 0xE2}, +{0x327C, 0xEF}, +{0x327D, 0xF7}, +{0x327E, 0xFF}, +{0x3302, 0x00}, +{0x3303, 0x40}, +{0x3304, 0x00}, +{0x3305, 0x96}, +{0x3306, 0x00}, +{0x3307, 0x29}, +{0x3308, 0x07}, +{0x3309, 0xBA}, +{0x330A, 0x06}, +{0x330B, 0xF5}, +{0x330C, 0x01}, +{0x330D, 0x51}, +{0x330E, 0x01}, +{0x330F, 0x30}, +{0x3310, 0x07}, +{0x3311, 0x16}, +{0x3312, 0x07}, +{0x3313, 0xBA}, +{0x3326, 0x02}, +{0x32F6, 0x0F}, +{0x32F9, 0x42}, +{0x32FA, 0x24}, +{0x3325, 0x4A}, +{0x3330, 0x00}, +{0x3331, 0x0A}, +{0x3332, 0xFF}, +{0x3338, 0x30}, +{0x3339, 0x84}, +{0x333A, 0x48}, +{0x333F, 0x07}, +{0x3360, 0x10}, +{0x3361, 0x18}, +{0x3362, 0x1f}, +{0x3363, 0x37}, +{0x3364, 0x80}, +{0x3365, 0x80}, +{0x3366, 0x68}, +{0x3367, 0x60}, +{0x3368, 0x30}, +{0x3369, 0x28}, +{0x336A, 0x20}, +{0x336B, 0x10}, +{0x336C, 0x00}, +{0x336D, 0x20}, +{0x336E, 0x1C}, +{0x336F, 0x18}, +{0x3370, 0x10}, +{0x3371, 0x38}, +{0x3372, 0x3C}, +{0x3373, 0x3F}, +{0x3374, 0x3F}, +{0x338A, 0x34}, +{0x338B, 0x7F}, +{0x338C, 0x10}, +{0x338D, 0x23}, +{0x338E, 0x7F}, +{0x338F, 0x14}, +{0x3375, 0x08}, +{0x3376, 0x0C}, +{0x3377, 0x18}, +{0x3378, 0x20}, +{0x3012, 0x02}, +{0x3013, 0xD0}, +{0x3025, 0x02}, //colorbar +{REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_fmt_jpeg[][2] = { + {0x32F0, 0x70}, // YUV422 + {REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_fmt_raw[][2] = { + {0x32F0, 0x50}, // RAW + {REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_fmt_grayscale[][2] = { + {0x32F1, 0x01}, + {REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_fmt_yuv422[][2] = { + {0x32F0, 0x00}, // YUV422 + {REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_fmt_rgb565[][2] = { + {0x32F0, 0x01}, // RGB + {REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint8_t sensor_saturation_levels[9][1] = { + {0x60},//-4 + {0x68},//-3 + {0x70},//-2 + {0x78},//-1 + {0x80},//0 + {0x88},//+1 + {0x90},//+2 + {0x98},//+3 + {0xA0},//+4 +}; + +static const DRAM_ATTR uint8_t sensor_special_effects[7][4] = { + {0x00, 0x80, 0x80, 0x01},//Normal + {0x03, 0x80, 0x80, 0x01},//Negative + {0x01, 0x80, 0x80, 0x01},//Grayscale + {0x05, 0x2A, 0xF0, 0x01},//Red Tint + {0x05, 0x60, 0x20, 0x01},//Green Tint + {0x05, 0xF0, 0x80, 0x01},//Blue Tint + {0x02, 0x80, 0x80, 0x01},//Sepia + +}; + +// AE LEVEL +static const DRAM_ATTR uint16_t sensor_ae_level[][2] = { + +// 1. [AE_Target : 0x24] +// Set_Device_Format = FORMAT_16_8 +// SET_Device_Addr = 0x54 + {0x32B8, 0x29 }, + {0x32B9, 0x1F }, + {0x32BC, 0x24 }, + {0x32BD, 0x27 }, + {0x32BE, 0x21 }, +//------------------------------------------------------------------------ +// 2. [AE_Target : 0x28] +// Set_Device_Format = FORMAT_16_8 +// SET_Device_Addr = 0x54 + {0x32B8, 0x2D }, + {0x32B9, 0x23 }, + {0x32BC, 0x28 }, + {0x32BD, 0x2B }, + {0x32BE, 0x25 }, +//------------------------------------------------------------------------ +// 3. [AE_Target : 0x2C] +// Set_Device_Format = FORMAT_16_8 +// SET_Device_Addr = 0x54 + {0x32B8, 0x32 }, + {0x32B9, 0x26 }, + {0x32BC, 0x2C }, + {0x32BD, 0x2F }, + {0x32BE, 0x29 }, +//------------------------------------------------------------------------ +// 4, [AE_Target : 0x30] +// Set_Device_Format = FORMAT_16_8 +// SET_Device_Addr = 0x54 + {0x32B8, 0x36 }, + {0x32B9, 0x2A }, + {0x32BC, 0x30 }, + {0x32BD, 0x33 }, + {0x32BE, 0x2D }, +//------------------------------------------------------------------------ +// 5. [AE_Target : 0x34] +// Set_Device_Format = FORMAT_16_8 +// SET_Device_Addr = 0x54 + {0x32B8, 0x3B }, + {0x32B9, 0x2D }, + {0x32BC, 0x34 }, + {0x32BD, 0x38 }, + {0x32BE, 0x30 }, +//------------------------------------------------------------------------ +// 6. [AE_Target : 0x38] +// Set_Device_Format = FORMAT_16_8 +// SET_Device_Addr = 0x54 + {0x32B8, 0x3F }, + {0x32B9, 0x31 }, + {0x32BC, 0x38 }, + {0x32BD, 0x3C }, + {0x32BE, 0x34 }, +//------------------------------------------------------------------------ +// 7. [AE_Target : 0x3D] +// Set_Device_Format = FORMAT_16_8 +// SET_Device_Addr = 0x54 + {0x32B8, 0x44 }, + {0x32B9, 0x34 }, + {0x32BC, 0x3C }, + {0x32BD, 0x40 }, + {0x32BE, 0x38 }, +//------------------------------------------------------------------------ +// 8. [AE_Target : 0x40] +// Set_Device_Format = FORMAT_16_8 +// SET_Device_Addr = 0x54 + {0x32B8, 0x48 }, + {0x32B9, 0x38 }, + {0x32BC, 0x40 }, + {0x32BD, 0x44 }, + {0x32BE, 0x3C }, +//------------------------------------------------------------------------ +// 9. [AE_Target : 0x44] +// Set_Device_Format = FORMAT_16_8 +// SET_Device_Addr = 0x54 + {0x32B8, 0x4D }, + {0x32B9, 0x3B }, + {0x32BC, 0x44 }, + {0x32BD, 0x49 }, + {0x32BE, 0x3F }, +}; + +static const DRAM_ATTR uint16_t sensor_framesize_HD[][2] = { +//[JPEG_1280x720_8.18_8.18_Fps] +{0x3021, 0x00}, +{REG_DLY, 100}, // delay 100ms +{0x32BF, 0x60}, +{0x32C0, 0x5A}, +{0x32C1, 0x5A}, +{0x32C2, 0x5A}, +{0x32C3, 0x00}, +{0x32C4, 0x20}, +{0x32C5, 0x20}, +{0x32C6, 0x20}, +{0x32C7, 0x00}, +{0x32C8, 0x3C}, +{0x32C9, 0x5A}, +{0x32CA, 0x7A}, +{0x32CB, 0x7A}, +{0x32CC, 0x7A}, +{0x32CD, 0x7A}, +{0x32DB, 0x5E}, +{0x32F0, 0x70}, +{0x3400, 0x08}, +{0x3400, 0x00}, +{0x3401, 0x4E}, +{0x3404, 0x00}, +{0x3405, 0x00}, +{0x3410, 0x00}, +{0x3200, 0x3E}, +{0x3201, 0x0F}, +{0x3028, 0x0F}, +{0x3029, 0x00}, +{0x302A, 0x08}, +{0x3022, 0x24}, +{0x3023, 0x24}, +{0x3002, 0x00}, +{0x3003, 0x04}, +{0x3004, 0x00}, +{0x3005, 0x04}, +{0x3006, 0x05}, +{0x3007, 0x03}, +{0x3008, 0x02}, +{0x3009, 0xD3}, +{0x300A, 0x06}, +{0x300B, 0x7C}, +{0x300C, 0x02}, +{0x300D, 0xE0}, +{0x300E, 0x05}, +{0x300F, 0x00}, +{0x3010, 0x02}, +{0x3011, 0xD0}, +{0x32B8, 0x3F}, +{0x32B9, 0x31}, +{0x32BB, 0x87}, +{0x32BC, 0x38}, +{0x32BD, 0x3C}, +{0x32BE, 0x34}, +{0x3201, 0x3F}, +{0x3021, 0x06}, +{0x3025, 0x00}, //normal +{0x3400, 0x01}, +{0x3060, 0x01}, +{REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_framesize_VGA[][2] = { +//[JPEG_640x480_10.14_10.14_Fps] +{0x3021, 0x00}, +{REG_DLY, 100}, // delay 100ms +{0x32BF, 0x60}, +{0x32C0, 0x5A}, +{0x32C1, 0x5A}, +{0x32C2, 0x5A}, +{0x32C3, 0x00}, +{0x32C4, 0x20}, +{0x32C5, 0x20}, +{0x32C6, 0x20}, +{0x32C7, 0x00}, +{0x32C8, 0x4B}, +{0x32C9, 0x5A}, +{0x32CA, 0x7A}, +{0x32CB, 0x7A}, +{0x32CC, 0x7A}, +{0x32CD, 0x7A}, +{0x32DB, 0x62}, +{0x32F0, 0x70}, +{0x3400, 0x08}, +{0x3400, 0x00}, +{0x3401, 0x4E}, +{0x3404, 0x00}, +{0x3405, 0x00}, +{0x3410, 0x00}, +{0x32E0, 0x02}, +{0x32E1, 0x80}, +{0x32E2, 0x01}, +{0x32E3, 0xE0}, +{0x32E4, 0x00}, +{0x32E5, 0x80}, +{0x32E6, 0x00}, +{0x32E7, 0x80}, +{0x3200, 0x3E}, +{0x3201, 0x0F}, +{0x3028, 0x0F}, +{0x3029, 0x00}, +{0x302A, 0x08}, +{0x3022, 0x24}, +{0x3023, 0x24}, +{0x3002, 0x00}, +{0x3003, 0xA4}, +{0x3004, 0x00}, +{0x3005, 0x04}, +{0x3006, 0x04}, +{0x3007, 0x63}, +{0x3008, 0x02}, +{0x3009, 0xD3}, +{0x300A, 0x05}, +{0x300B, 0x3C}, +{0x300C, 0x02}, +{0x300D, 0xE0}, +{0x300E, 0x03}, +{0x300F, 0xC0}, +{0x3010, 0x02}, +{0x3011, 0xD0}, +{0x32B8, 0x3F}, +{0x32B9, 0x31}, +{0x32BB, 0x87}, +{0x32BC, 0x38}, +{0x32BD, 0x3C}, +{0x32BE, 0x34}, +{0x3201, 0x7F}, +{0x3021, 0x06}, +{0x3025, 0x00}, //normal +{0x3400, 0x01}, +{0x3060, 0x01}, +{REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_framesize_QVGA[][2] = { +//[JPEG_320x240_10.14_10.14_Fps] +{0x3021, 0x00}, +{REG_DLY, 100}, // delay 100ms +{0x32BF, 0x60}, +{0x32C0, 0x5A}, +{0x32C1, 0x5A}, +{0x32C2, 0x5A}, +{0x32C3, 0x00}, +{0x32C4, 0x20}, +{0x32C5, 0x20}, +{0x32C6, 0x20}, +{0x32C7, 0x00}, +{0x32C8, 0x4B}, +{0x32C9, 0x5A}, +{0x32CA, 0x7A}, +{0x32CB, 0x7A}, +{0x32CC, 0x7A}, +{0x32CD, 0x7A}, +{0x32DB, 0x62}, +{0x32F0, 0x70}, +{0x3400, 0x08}, +{0x3400, 0x00}, +{0x3401, 0x4E}, +{0x3404, 0x00}, +{0x3405, 0x00}, +{0x3410, 0x00}, +{0x32E0, 0x01}, +{0x32E1, 0x40}, +{0x32E2, 0x00}, +{0x32E3, 0xF0}, +{0x32E4, 0x02}, +{0x32E5, 0x02}, +{0x32E6, 0x02}, +{0x32E7, 0x03}, +{0x3200, 0x3E}, +{0x3201, 0x0F}, +{0x3028, 0x0F}, +{0x3029, 0x00}, +{0x302A, 0x08}, +{0x3022, 0x24}, +{0x3023, 0x24}, +{0x3002, 0x00}, +{0x3003, 0xA4}, +{0x3004, 0x00}, +{0x3005, 0x04}, +{0x3006, 0x04}, +{0x3007, 0x63}, +{0x3008, 0x02}, +{0x3009, 0xD3}, +{0x300A, 0x05}, +{0x300B, 0x3C}, +{0x300C, 0x02}, +{0x300D, 0xE0}, +{0x300E, 0x03}, +{0x300F, 0xC0}, +{0x3010, 0x02}, +{0x3011, 0xD0}, +{0x32B8, 0x3F}, +{0x32B9, 0x31}, +{0x32BB, 0x87}, +{0x32BC, 0x38}, +{0x32BD, 0x3C}, +{0x32BE, 0x34}, +{0x3201, 0x7F}, +{0x3021, 0x06}, +{0x3025, 0x00}, //normal +{0x3400, 0x01}, +{0x3060, 0x01}, +{REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_framesize_VGA_xyskip[][2] = { +// [JPEG_640x360_20.00_25.01_Fps_XY_Skip] +// Set_Device_Format = FORMAT_16_8 +// SET_Device_Addr = 0x54 +{0x3021, 0x00}, +{REG_DLY, 100}, // delay 100ms +{0x32BF, 0x60 }, +{0x320A, 0xB2 }, +{0x32C0, 0x64 }, +{0x32C1, 0x64 }, +{0x32C2, 0x64 }, +{0x32C3, 0x00 }, +{0x32C4, 0x20 }, +{0x32C5, 0x20 }, +{0x32C6, 0x20 }, +{0x32C7, 0x00 }, +{0x32C8, 0x62 }, +{0x32C9, 0x64 }, +{0x32CA, 0x84 }, +{0x32CB, 0x84 }, +{0x32CC, 0x84 }, +{0x32CD, 0x84 }, +{0x32DB, 0x68 }, +{0x32F0, 0x70 }, +{0x3400, 0x08 }, +{0x3400, 0x00 }, +{0x3401, 0x4E }, +{0x3404, 0x00 }, +{0x3405, 0x00 }, +{0x3410, 0x00 }, +{0x3200, 0x3E }, +{0x3201, 0x0F }, +{0x3028, 0x0F }, +{0x3029, 0x00 }, +{0x302A, 0x08 }, +{0x3022, 0x24 }, +{0x3023, 0x6C }, +{0x3002, 0x00 }, +{0x3003, 0x04 }, +{0x3004, 0x00 }, +{0x3005, 0x04 }, +{0x3006, 0x05 }, +{0x3007, 0x03 }, +{0x3008, 0x02 }, +{0x3009, 0xD3 }, +{0x300A, 0x03 }, +{0x300B, 0xFC }, +{0x300C, 0x01 }, +{0x300D, 0x88 }, +{0x300E, 0x02 }, +{0x300F, 0x80 }, +{0x3010, 0x01 }, +{0x3011, 0x68 }, +{0x32B8, 0x3F }, +{0x32B9, 0x31 }, +{0x32BB, 0x87 }, +{0x32BC, 0x38 }, +{0x32BD, 0x3C }, +{0x32BE, 0x34 }, +{0x3201, 0x3F }, +{0x3025, 0x00 }, //normal +{0x3021, 0x06 }, +{0x3400, 0x01 }, +{0x3060, 0x01 }, +{REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_framesize_VGA_xskip[][2] = { +//[JPEG_640x480_Xskip_13.32_13.32_Fps] +{0x3021, 0x00}, +{REG_DLY, 100}, // delay 100ms +{0x32BF, 0x60}, +{0x32C0, 0x5A}, +{0x32C1, 0x5A}, +{0x32C2, 0x5A}, +{0x32C3, 0x00}, +{0x32C4, 0x20}, +{0x32C5, 0x20}, +{0x32C6, 0x20}, +{0x32C7, 0x00}, +{0x32C8, 0x62}, +{0x32C9, 0x5A}, +{0x32CA, 0x7A}, +{0x32CB, 0x7A}, +{0x32CC, 0x7A}, +{0x32CD, 0x7A}, +{0x32DB, 0x68}, +{0x32F0, 0x70}, +{0x3400, 0x08}, +{0x3400, 0x00}, +{0x3401, 0x4E}, +{0x3404, 0x00}, +{0x3405, 0x00}, +{0x3410, 0x00}, +{0x32E0, 0x02}, +{0x32E1, 0x80}, +{0x32E2, 0x01}, +{0x32E3, 0xE0}, +{0x32E4, 0x00}, +{0x32E5, 0x00}, +{0x32E6, 0x00}, +{0x32E7, 0x80}, +{0x3200, 0x3E}, +{0x3201, 0x0F}, +{0x3028, 0x0F}, +{0x3029, 0x00}, +{0x302A, 0x08}, +{0x3022, 0x24}, +{0x3023, 0x2C}, +{0x3002, 0x00}, +{0x3003, 0x04}, +{0x3004, 0x00}, +{0x3005, 0x04}, +{0x3006, 0x05}, +{0x3007, 0x03}, +{0x3008, 0x02}, +{0x3009, 0xD3}, +{0x300A, 0x03}, +{0x300B, 0xFC}, +{0x300C, 0x02}, +{0x300D, 0xE0}, +{0x300E, 0x02}, +{0x300F, 0x80}, +{0x3010, 0x02}, +{0x3011, 0xD0}, +{0x32B8, 0x3F}, +{0x32B9, 0x31}, +{0x32BB, 0x87}, +{0x32BC, 0x38}, +{0x32BD, 0x3C}, +{0x32BE, 0x34}, +{0x3201, 0x7F}, +{0x3021, 0x06}, +{0x3025, 0x00}, //normal +{0x3400, 0x01}, +{0x3060, 0x01}, +{REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_framesize_QVGA_xskip[][2] = { +{0x3021, 0x00}, +{REG_DLY, 100}, // delay 100ms +//[JPEG_320x240_Xskip_13.32_13.32_Fps] +{0x32BF, 0x60}, +{0x32C0, 0x5A}, +{0x32C1, 0x5A}, +{0x32C2, 0x5A}, +{0x32C3, 0x00}, +{0x32C4, 0x20}, +{0x32C5, 0x20}, +{0x32C6, 0x20}, +{0x32C7, 0x00}, +{0x32C8, 0x62}, +{0x32C9, 0x5A}, +{0x32CA, 0x7A}, +{0x32CB, 0x7A}, +{0x32CC, 0x7A}, +{0x32CD, 0x7A}, +{0x32DB, 0x68}, +{0x32F0, 0x70}, +{0x3400, 0x08}, +{0x3400, 0x00}, +{0x3401, 0x4E}, +{0x3404, 0x00}, +{0x3405, 0x00}, +{0x3410, 0x00}, +{0x32E0, 0x01}, +{0x32E1, 0x40}, +{0x32E2, 0x00}, +{0x32E3, 0xF0}, +{0x32E4, 0x01}, +{0x32E5, 0x01}, +{0x32E6, 0x02}, +{0x32E7, 0x03}, +{0x3200, 0x3E}, +{0x3201, 0x0F}, +{0x3028, 0x0F}, +{0x3029, 0x00}, +{0x302A, 0x08}, +{0x3022, 0x24}, +{0x3023, 0x2C}, +{0x3002, 0x00}, +{0x3003, 0x04}, +{0x3004, 0x00}, +{0x3005, 0x04}, +{0x3006, 0x05}, +{0x3007, 0x03}, +{0x3008, 0x02}, +{0x3009, 0xD3}, +{0x300A, 0x03}, +{0x300B, 0xFC}, +{0x300C, 0x02}, +{0x300D, 0xE0}, +{0x300E, 0x02}, +{0x300F, 0x80}, +{0x3010, 0x02}, +{0x3011, 0xD0}, +{0x32B8, 0x3F}, +{0x32B9, 0x31}, +{0x32BB, 0x87}, +{0x32BC, 0x38}, +{0x32BD, 0x3C}, +{0x32BE, 0x34}, +{0x3201, 0x7F}, +{0x3021, 0x06}, +{0x3025, 0x00}, //normal +{0x3400, 0x01}, +{0x3060, 0x01}, +{REGLIST_TAIL, 0x00}, // tail +}; + + +static const DRAM_ATTR uint16_t sensor_framesize_VGA_crop[][2] = { +//[JPEG_640x480_Crop_19.77_19.77_Fps] +{0x3021, 0x00}, +{REG_DLY, 100}, // delay 100ms +{0x32BF, 0x60}, +{0x32C0, 0x5A}, +{0x32C1, 0x5A}, +{0x32C2, 0x5A}, +{0x32C3, 0x00}, +{0x32C4, 0x20}, +{0x32C5, 0x20}, +{0x32C6, 0x20}, +{0x32C7, 0x00}, +{0x32C8, 0x62}, +{0x32C9, 0x5A}, +{0x32CA, 0x7A}, +{0x32CB, 0x7A}, +{0x32CC, 0x7A}, +{0x32CD, 0x7A}, +{0x32DB, 0x68}, +{0x32F0, 0x70}, +{0x3400, 0x08}, +{0x3400, 0x00}, +{0x3401, 0x4E}, +{0x3404, 0x00}, +{0x3405, 0x00}, +{0x3410, 0x00}, +{0x3200, 0x3E}, +{0x3201, 0x0F}, +{0x3028, 0x0F}, +{0x3029, 0x00}, +{0x302A, 0x08}, +{0x3022, 0x24}, +{0x3023, 0x24}, +{0x3002, 0x01}, +{0x3003, 0x44}, +{0x3004, 0x00}, +{0x3005, 0x7C}, +{0x3006, 0x03}, +{0x3007, 0xC3}, +{0x3008, 0x02}, +{0x3009, 0x5B}, +{0x300A, 0x03}, +{0x300B, 0xFC}, +{0x300C, 0x01}, +{0x300D, 0xF0}, +{0x300E, 0x02}, +{0x300F, 0x80}, +{0x3010, 0x01}, +{0x3011, 0xE0}, +{0x32B8, 0x3F}, +{0x32B9, 0x31}, +{0x32BB, 0x87}, +{0x32BC, 0x38}, +{0x32BD, 0x3C}, +{0x32BE, 0x34}, +{0x3201, 0x3F}, +{0x3021, 0x06}, +{0x3025, 0x00}, //normal +{0x3400, 0x01}, +{0x3060, 0x01}, +{REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_framesize_QVGA_crop[][2] = { +//[JPEG_320x240_Crop_19.77_19.77_Fps] +{0x3021, 0x00}, +{REG_DLY, 100}, // delay 100ms +{0x32BF, 0x60}, +{0x32C0, 0x5A}, +{0x32C1, 0x5A}, +{0x32C2, 0x5A}, +{0x32C3, 0x00}, +{0x32C4, 0x20}, +{0x32C5, 0x20}, +{0x32C6, 0x20}, +{0x32C7, 0x00}, +{0x32C8, 0x62}, +{0x32C9, 0x5A}, +{0x32CA, 0x7A}, +{0x32CB, 0x7A}, +{0x32CC, 0x7A}, +{0x32CD, 0x7A}, +{0x32DB, 0x68}, +{0x32F0, 0x70}, +{0x3400, 0x08}, +{0x3400, 0x00}, +{0x3401, 0x4E}, +{0x3404, 0x00}, +{0x3405, 0x00}, +{0x3410, 0x00}, +{0x32E0, 0x01}, +{0x32E1, 0x40}, +{0x32E2, 0x00}, +{0x32E3, 0xF0}, +{0x32E4, 0x01}, +{0x32E5, 0x01}, +{0x32E6, 0x01}, +{0x32E7, 0x02}, +{0x3200, 0x3E}, +{0x3201, 0x0F}, +{0x3028, 0x0F}, +{0x3029, 0x00}, +{0x302A, 0x08}, +{0x3022, 0x24}, +{0x3023, 0x24}, +{0x3002, 0x01}, +{0x3003, 0x44}, +{0x3004, 0x00}, +{0x3005, 0x7C}, +{0x3006, 0x03}, +{0x3007, 0xC3}, +{0x3008, 0x02}, +{0x3009, 0x5B}, +{0x300A, 0x03}, +{0x300B, 0xFC}, +{0x300C, 0x01}, +{0x300D, 0xF0}, +{0x300E, 0x02}, +{0x300F, 0x80}, +{0x3010, 0x01}, +{0x3011, 0xE0}, +{0x32B8, 0x3F}, +{0x32B9, 0x31}, +{0x32BB, 0x87}, +{0x32BC, 0x38}, +{0x32BD, 0x3C}, +{0x32BE, 0x34}, +{0x3201, 0x7F}, +{0x3021, 0x06}, +{0x3025, 0x00}, //normal +{0x3400, 0x01}, +{0x3060, 0x01}, +{REGLIST_TAIL, 0x00}, // tail +}; + +#endif + + diff --git a/esp32-cam-rtos/ov7670.c b/esp32-cam-rtos/ov7670.c new file mode 100644 index 0000000..285fe13 --- /dev/null +++ b/esp32-cam-rtos/ov7670.c @@ -0,0 +1,439 @@ +/* + * This file is part of the OpenMV project. + * author: Juan Schiavoni + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * OV7725 driver. + * + */ +#include +#include +#include +#include "sccb.h" +#include "ov7670.h" +#include "ov7670_regs.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#else +#include "esp_log.h" +static const char* TAG = "ov7760"; +#endif + +static int ov7670_clkrc = 0x01; + +/* + * The default register settings, as obtained from OmniVision. There + * is really no making sense of most of these - lots of "reserved" values + * and such. + * + * These settings give VGA YUYV. + */ +struct regval_list { + uint8_t reg_num; + uint8_t value; +}; + +static struct regval_list ov7670_default_regs[] = { + /* Sensor automatically sets output window when resolution changes. */ + {TSLB, 0x04}, + + /* Frame rate 30 fps at 12 Mhz clock */ + {CLKRC, 0x00}, + {DBLV, 0x4A}, + + {COM10, COM10_VSYNC_NEG | COM10_PCLK_MASK}, + + /* Improve white balance */ + {COM4, 0x40}, + + /* Improve color */ + {RSVD_B0, 0x84}, + + /* Enable 50/60 Hz auto detection */ + {COM11, COM11_EXP|COM11_HZAUTO}, + + /* Disable some delays */ + {HSYST, 0}, + {HSYEN, 0}, + + {MVFP, MVFP_SUN}, + + /* More reserved magic, some of which tweaks white balance */ + {AWBC1, 0x0a}, + {AWBC2, 0xf0}, + {AWBC3, 0x34}, + {AWBC4, 0x58}, + {AWBC5, 0x28}, + {AWBC6, 0x3a}, + + {AWBCTR3, 0x0a}, + {AWBCTR2, 0x55}, + {AWBCTR1, 0x11}, + {AWBCTR0, 0x9e}, + + {COM8, COM8_FAST_AUTO|COM8_STEP_UNLIMIT|COM8_AGC_EN|COM8_AEC_EN|COM8_AWB_EN}, + + /* End marker is FF because in ov7670 the address of GAIN 0 and default value too. */ + {0xFF, 0xFF}, +}; + +static struct regval_list ov7670_fmt_yuv422[] = { + { COM7, 0x0 }, /* Selects YUV mode */ + { RGB444, 0 }, /* No RGB444 please */ + { COM1, 0 }, /* CCIR601 */ + { COM15, COM15_R00FF }, + { MVFP, MVFP_SUN }, + { COM9, 0x6A }, /* 128x gain ceiling; 0x8 is reserved bit */ + { MTX1, 0x80 }, /* "matrix coefficient 1" */ + { MTX2, 0x80 }, /* "matrix coefficient 2" */ + { MTX3, 0 }, /* vb */ + { MTX4, 0x22 }, /* "matrix coefficient 4" */ + { MTX5, 0x5e }, /* "matrix coefficient 5" */ + { MTX6, 0x80 }, /* "matrix coefficient 6" */ + { COM13, COM13_UVSAT }, + { 0xff, 0xff }, /* END MARKER */ +}; + +static struct regval_list ov7670_fmt_rgb565[] = { + { COM7, COM7_FMT_RGB565 }, /* Selects RGB mode */ + { RGB444, 0 }, /* No RGB444 please */ + { COM1, 0x0 }, /* CCIR601 */ + { COM15, COM15_RGB565 |COM15_R00FF }, + { MVFP, MVFP_SUN }, + { COM9, 0x6A }, /* 128x gain ceiling; 0x8 is reserved bit */ + { MTX1, 0xb3 }, /* "matrix coefficient 1" */ + { MTX2, 0xb3 }, /* "matrix coefficient 2" */ + { MTX3, 0 }, /* vb */ + { MTX4, 0x3d }, /* "matrix coefficient 4" */ + { MTX5, 0xa7 }, /* "matrix coefficient 5" */ + { MTX6, 0xe4 }, /* "matrix coefficient 6" */ + { COM13, COM13_UVSAT }, + { 0xff, 0xff }, /* END MARKER */ +}; + + +static struct regval_list ov7670_vga[] = { + { COM3, 0x00 }, + { COM14, 0x00 }, + { SCALING_XSC, 0x3A }, + { SCALING_YSC, 0x35 }, + { SCALING_DCWCTR, 0x11 }, + { SCALING_PCLK_DIV, 0xF0 }, + { SCALING_PCLK_DELAY, 0x02 }, + { 0xff, 0xff }, +}; + +static struct regval_list ov7670_qvga[] = { + { COM3, 0x04 }, + { COM14, 0x19 }, + { SCALING_XSC, 0x3A }, + { SCALING_YSC, 0x35 }, + { SCALING_DCWCTR, 0x11 }, + { SCALING_PCLK_DIV, 0xF1 }, + { SCALING_PCLK_DELAY, 0x02 }, + { 0xff, 0xff }, +}; + +static struct regval_list ov7670_qqvga[] = { + { COM3, 0x04 }, //DCW enable + { COM14, 0x1a }, //pixel clock divided by 4, manual scaling enable, DCW and PCLK controlled by register + { SCALING_XSC, 0x3a }, + { SCALING_YSC, 0x35 }, + { SCALING_DCWCTR, 0x22 }, //downsample by 4 + { SCALING_PCLK_DIV, 0xf2 }, //pixel clock divided by 4 + { SCALING_PCLK_DELAY, 0x02 }, + { 0xff, 0xff }, +}; + +/* + * Write a list of register settings; ff/ff stops the process. + */ +static int ov7670_write_array(sensor_t *sensor, struct regval_list *vals) +{ +int ret = 0; + + while ( (vals->reg_num != 0xff || vals->value != 0xff) && (ret == 0) ) { + ret = SCCB_Write(sensor->slv_addr, vals->reg_num, vals->value); + + ESP_LOGD(TAG, "reset reg %02X, W(%02X) R(%02X)", vals->reg_num, + vals->value, SCCB_Read(sensor->slv_addr, vals->reg_num) ); + + vals++; + } + + return ret; +} + +/* + * Calculate the frame control registers. + */ +static int ov7670_frame_control(sensor_t *sensor, int hstart, int hstop, int vstart, int vstop) +{ +struct regval_list frame[7]; + + frame[0].reg_num = HSTART; + frame[0].value = (hstart >> 3); + + frame[1].reg_num = HSTOP; + frame[1].value = (hstop >> 3); + + frame[2].reg_num = HREF; + frame[2].value = (((hstop & 0x07) << 3) | (hstart & 0x07)); + + frame[3].reg_num = VSTART; + frame[3].value = (vstart >> 2); + + frame[4].reg_num = VSTOP; + frame[4].value = (vstop >> 2); + + frame[5].reg_num = VREF; + frame[5].value = (((vstop & 0x02) << 2) | (vstart & 0x02)); + + /* End mark */ + frame[5].reg_num = 0xFF; + frame[5].value = 0xFF; + + return ov7670_write_array(sensor, frame); +} + +static int reset(sensor_t *sensor) +{ + int ret; + + // Reset all registers + SCCB_Write(sensor->slv_addr, COM7, COM7_RESET); + + // Delay 10 ms + vTaskDelay(10 / portTICK_PERIOD_MS); + + ret = ov7670_write_array(sensor, ov7670_default_regs); + + // Delay + vTaskDelay(30 / portTICK_PERIOD_MS); + + return ret; +} + +static int set_pixformat(sensor_t *sensor, pixformat_t pixformat) +{ +int ret; + + switch (pixformat) { + case PIXFORMAT_RGB565: + case PIXFORMAT_RGB888: + ret = ov7670_write_array(sensor, ov7670_fmt_rgb565); + break; + + case PIXFORMAT_YUV422: + case PIXFORMAT_GRAYSCALE: + default: + ret = ov7670_write_array(sensor, ov7670_fmt_yuv422); + break; + } + + vTaskDelay(30 / portTICK_PERIOD_MS); + + /* + * If we're running RGB565, we must rewrite clkrc after setting + * the other parameters or the image looks poor. If we're *not* + * doing RGB565, we must not rewrite clkrc or the image looks + * *really* poor. + * + * (Update) Now that we retain clkrc state, we should be able + * to write it unconditionally, and that will make the frame + * rate persistent too. + */ + if (pixformat == PIXFORMAT_RGB565) { + ret = SCCB_Write(sensor->slv_addr, CLKRC, ov7670_clkrc); + } + + return ret; +} + +static int set_framesize(sensor_t *sensor, framesize_t framesize) +{ + int ret; + + // store clkrc before changing window settings... + ov7670_clkrc = SCCB_Read(sensor->slv_addr, CLKRC); + + switch (framesize){ + case FRAMESIZE_VGA: + if( (ret = ov7670_write_array(sensor, ov7670_vga)) == 0 ) { + /* These values from Omnivision */ + ret = ov7670_frame_control(sensor, 158, 14, 10, 490); + } + break; + case FRAMESIZE_QVGA: + if( (ret = ov7670_write_array(sensor, ov7670_qvga)) == 0 ) { + /* These values from Omnivision */ + ret = ov7670_frame_control(sensor, 158, 14, 10, 490); + } + break; + case FRAMESIZE_QQVGA: + if( (ret = ov7670_write_array(sensor, ov7670_qqvga)) == 0 ) { + /* These values from Omnivision */ + ret = ov7670_frame_control(sensor, 158, 14, 10, 490); + } + break; + + default: + ret = -1; + } + + vTaskDelay(30 / portTICK_PERIOD_MS); + + if (ret == 0) { + sensor->status.framesize = framesize; + } + + return ret; +} + +static int set_colorbar(sensor_t *sensor, int enable) +{ + uint8_t ret = 0; + // Read register scaling_xsc + uint8_t reg = SCCB_Read(sensor->slv_addr, SCALING_XSC); + + // Pattern to set color bar bit[0]=0 in every case + reg = SCALING_XSC_CBAR(reg); + + // Write pattern to SCALING_XSC + ret = SCCB_Write(sensor->slv_addr, SCALING_XSC, reg); + + // Read register scaling_ysc + reg = SCCB_Read(sensor->slv_addr, SCALING_YSC); + + // Pattern to set color bar bit[0]=0 in every case + reg = SCALING_YSC_CBAR(reg, enable); + + // Write pattern to SCALING_YSC + ret = ret | SCCB_Write(sensor->slv_addr, SCALING_YSC, reg); + + // return 0 or 0xFF + return ret; +} + +static int set_whitebal(sensor_t *sensor, int enable) +{ + // Read register COM8 + uint8_t reg = SCCB_Read(sensor->slv_addr, COM8); + + // Set white bal on/off + reg = COM8_SET_AWB(reg, enable); + + // Write back register COM8 + return SCCB_Write(sensor->slv_addr, COM8, reg); +} + +static int set_gain_ctrl(sensor_t *sensor, int enable) +{ + // Read register COM8 + uint8_t reg = SCCB_Read(sensor->slv_addr, COM8); + + // Set white bal on/off + reg = COM8_SET_AGC(reg, enable); + + // Write back register COM8 + return SCCB_Write(sensor->slv_addr, COM8, reg); +} + +static int set_exposure_ctrl(sensor_t *sensor, int enable) +{ + // Read register COM8 + uint8_t reg = SCCB_Read(sensor->slv_addr, COM8); + + // Set white bal on/off + reg = COM8_SET_AEC(reg, enable); + + // Write back register COM8 + return SCCB_Write(sensor->slv_addr, COM8, reg); +} + +static int set_hmirror(sensor_t *sensor, int enable) +{ + // Read register MVFP + uint8_t reg = SCCB_Read(sensor->slv_addr, MVFP); + + // Set mirror on/off + reg = MVFP_SET_MIRROR(reg, enable); + + // Write back register MVFP + return SCCB_Write(sensor->slv_addr, MVFP, reg); +} + +static int set_vflip(sensor_t *sensor, int enable) +{ + // Read register MVFP + uint8_t reg = SCCB_Read(sensor->slv_addr, MVFP); + + // Set mirror on/off + reg = MVFP_SET_FLIP(reg, enable); + + // Write back register MVFP + return SCCB_Write(sensor->slv_addr, MVFP, reg); +} + +static int init_status(sensor_t *sensor) +{ + sensor->status.awb = 0; + sensor->status.aec = 0; + sensor->status.agc = 0; + sensor->status.hmirror = 0; + sensor->status.vflip = 0; + sensor->status.colorbar = 0; + return 0; +} + +static int set_dummy(sensor_t *sensor, int val){ return -1; } +static int set_gainceiling_dummy(sensor_t *sensor, gainceiling_t val){ return -1; } + +int ov7670_init(sensor_t *sensor) +{ + // Set function pointers + sensor->reset = reset; + sensor->init_status = init_status; + sensor->set_pixformat = set_pixformat; + sensor->set_framesize = set_framesize; + sensor->set_colorbar = set_colorbar; + sensor->set_whitebal = set_whitebal; + sensor->set_gain_ctrl = set_gain_ctrl; + sensor->set_exposure_ctrl = set_exposure_ctrl; + sensor->set_hmirror = set_hmirror; + sensor->set_vflip = set_vflip; + + //not supported + sensor->set_brightness= set_dummy; + sensor->set_saturation= set_dummy; + sensor->set_quality = set_dummy; + sensor->set_gainceiling = set_gainceiling_dummy; + sensor->set_aec2 = set_dummy; + sensor->set_aec_value = set_dummy; + sensor->set_special_effect = set_dummy; + sensor->set_wb_mode = set_dummy; + sensor->set_ae_level = set_dummy; + sensor->set_dcw = set_dummy; + sensor->set_bpc = set_dummy; + sensor->set_wpc = set_dummy; + sensor->set_awb_gain = set_dummy; + sensor->set_agc_gain = set_dummy; + sensor->set_raw_gma = set_dummy; + sensor->set_lenc = set_dummy; + sensor->set_sharpness = set_dummy; + sensor->set_denoise = set_dummy; + + // Retrieve sensor's signature + sensor->id.MIDH = SCCB_Read(sensor->slv_addr, REG_MIDH); + sensor->id.MIDL = SCCB_Read(sensor->slv_addr, REG_MIDL); + sensor->id.PID = SCCB_Read(sensor->slv_addr, REG_PID); + sensor->id.VER = SCCB_Read(sensor->slv_addr, REG_VER); + + ESP_LOGD(TAG, "OV7670 Attached"); + + return 0; +} diff --git a/esp32-cam-rtos/ov7670.h b/esp32-cam-rtos/ov7670.h new file mode 100644 index 0000000..cdf845c --- /dev/null +++ b/esp32-cam-rtos/ov7670.h @@ -0,0 +1,14 @@ +/* + * This file is part of the OpenMV project. + * author: Juan Schiavoni + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * OV7670 driver. + * + */ +#ifndef __OV7670_H__ +#define __OV7670_H__ +#include "sensor.h" + +int ov7670_init(sensor_t *sensor); +#endif // __OV7670_H__ diff --git a/esp32-cam-rtos/ov7670_regs.h b/esp32-cam-rtos/ov7670_regs.h new file mode 100644 index 0000000..6993548 --- /dev/null +++ b/esp32-cam-rtos/ov7670_regs.h @@ -0,0 +1,354 @@ +/* + * This file is for the OpenMV project so the OV7670 can be used + * author: Juan Schiavoni + * + * OV7670 register definitions. + */ +#ifndef __OV7670_REG_REGS_H__ +#define __OV7670_REG_REGS_H__ +#define GAIN 0x00 /* AGC – Gain control gain setting */ +#define BLUE 0x01 /* AWB – Blue channel gain setting */ +#define RED 0x02 /* AWB – Red channel gain setting */ +#define VREF 0x03 /* AWB – Green channel gain setting */ +#define COM1 0x04 /* Common Control 1 */ +#define BAVG 0x05 /* U/B Average Level */ +#define GAVG 0x06 /* Y/Gb Average Level */ +#define AECH 0x07 /* Exposure VAlue - AEC MSB 5 bits */ +#define RAVG 0x08 /* V/R Average Level */ + +#define COM2 0x09 /* Common Control 2 */ +#define COM2_SOFT_SLEEP 0x10 /* Soft sleep mode */ +#define COM2_OUT_DRIVE_1x 0x00 /* Output drive capability 1x */ +#define COM2_OUT_DRIVE_2x 0x01 /* Output drive capability 2x */ +#define COM2_OUT_DRIVE_3x 0x02 /* Output drive capability 3x */ +#define COM2_OUT_DRIVE_4x 0x03 /* Output drive capability 4x */ + +#define REG_PID 0x0A /* Product ID Number MSB */ +#define REG_VER 0x0B /* Product ID Number LSB */ + +#define COM3 0x0C /* Common Control 3 */ +#define COM3_SWAP_OUT 0x40 /* Output data MSB/LSB swap */ +#define COM3_TRI_CLK 0x20 /* Tri-state output clock */ +#define COM3_TRI_DATA 0x10 /* Tri-state option output */ +#define COM3_SCALE_EN 0x08 /* Scale enable */ +#define COM3_DCW 0x04 /* DCW enable */ + +#define COM4 0x0D /* Common Control 4 */ +#define COM4_PLL_BYPASS 0x00 /* Bypass PLL */ +#define COM4_PLL_4x 0x40 /* PLL frequency 4x */ +#define COM4_PLL_6x 0x80 /* PLL frequency 6x */ +#define COM4_PLL_8x 0xc0 /* PLL frequency 8x */ +#define COM4_AEC_FULL 0x00 /* AEC evaluate full window */ +#define COM4_AEC_1_2 0x10 /* AEC evaluate 1/2 window */ +#define COM4_AEC_1_4 0x20 /* AEC evaluate 1/4 window */ +#define COM4_AEC_2_3 0x30 /* AEC evaluate 2/3 window */ + +#define COM5 0x0E /* Common Control 5 */ +#define COM5_AFR 0x80 /* Auto frame rate control ON/OFF selection (night mode) */ +#define COM5_AFR_SPEED 0x40 /* Auto frame rate control speed selection */ +#define COM5_AFR_0 0x00 /* No reduction of frame rate */ +#define COM5_AFR_1_2 0x10 /* Max reduction to 1/2 frame rate */ +#define COM5_AFR_1_4 0x20 /* Max reduction to 1/4 frame rate */ +#define COM5_AFR_1_8 0x30 /* Max reduction to 1/8 frame rate */ +#define COM5_AFR_4x 0x04 /* Add frame when AGC reaches 4x gain */ +#define COM5_AFR_8x 0x08 /* Add frame when AGC reaches 8x gain */ +#define COM5_AFR_16x 0x0c /* Add frame when AGC reaches 16x gain */ +#define COM5_AEC_NO_LIMIT 0x01 /* No limit to AEC increase step */ + +#define COM6 0x0F /* Common Control 6 */ +#define COM6_AUTO_WINDOW 0x01 /* Auto window setting ON/OFF selection when format changes */ + +#define AEC 0x10 /* AEC[7:0] (see register AECH for AEC[15:8]) */ +#define CLKRC 0x11 /* Internal Clock */ + +#define COM7 0x12 /* Common Control 7 */ +#define COM7_RESET 0x80 /* SCCB Register Reset */ +#define COM7_RES_VGA 0x00 /* Resolution VGA */ +#define COM7_RES_QVGA 0x40 /* Resolution QVGA */ +#define COM7_BT656 0x20 /* BT.656 protocol ON/OFF */ +#define COM7_SENSOR_RAW 0x10 /* Sensor RAW */ +#define COM7_FMT_GBR422 0x00 /* RGB output format GBR422 */ +#define COM7_FMT_RGB565 0x04 /* RGB output format RGB565 */ +#define COM7_FMT_RGB555 0x08 /* RGB output format RGB555 */ +#define COM7_FMT_RGB444 0x0C /* RGB output format RGB444 */ +#define COM7_FMT_YUV 0x00 /* Output format YUV */ +#define COM7_FMT_P_BAYER 0x01 /* Output format Processed Bayer RAW */ +#define COM7_FMT_RGB 0x04 /* Output format RGB */ +#define COM7_FMT_R_BAYER 0x03 /* Output format Bayer RAW */ +#define COM7_SET_FMT(r, x) ((r&0xFC)|((x&0x5)<<0)) + +#define COM8 0x13 /* Common Control 8 */ +#define COM8_FAST_AUTO 0x80 /* Enable fast AGC/AEC algorithm */ +#define COM8_STEP_VSYNC 0x00 /* AEC - Step size limited to vertical blank */ +#define COM8_STEP_UNLIMIT 0x40 /* AEC - Step size unlimited step size */ +#define COM8_BANDF_EN 0x20 /* Banding filter ON/OFF */ +#define COM8_AEC_BANDF 0x10 /* Enable AEC below banding value */ +#define COM8_AEC_FINE_EN 0x08 /* Fine AEC ON/OFF control */ +#define COM8_AGC_EN 0x04 /* AGC Enable */ +#define COM8_AWB_EN 0x02 /* AWB Enable */ +#define COM8_AEC_EN 0x01 /* AEC Enable */ +#define COM8_SET_AGC(r, x) ((r&0xFB)|((x&0x1)<<2)) +#define COM8_SET_AWB(r, x) ((r&0xFD)|((x&0x1)<<1)) +#define COM8_SET_AEC(r, x) ((r&0xFE)|((x&0x1)<<0)) + +#define COM9 0x14 /* Common Control 9 */ +#define COM9_HISTO_AVG 0x80 /* Histogram or average based AEC/AGC selection */ +#define COM9_AGC_GAIN_2x 0x00 /* Automatic Gain Ceiling 2x */ +#define COM9_AGC_GAIN_4x 0x10 /* Automatic Gain Ceiling 4x */ +#define COM9_AGC_GAIN_8x 0x20 /* Automatic Gain Ceiling 8x */ +#define COM9_AGC_GAIN_16x 0x30 /* Automatic Gain Ceiling 16x */ +#define COM9_AGC_GAIN_32x 0x40 /* Automatic Gain Ceiling 32x */ +#define COM9_DROP_VSYNC 0x04 /* Drop VSYNC output of corrupt frame */ +#define COM9_DROP_HREF 0x02 /* Drop HREF output of corrupt frame */ +#define COM9_SET_AGC(r, x) ((r&0x8F)|((x&0x07)<<4)) + +#define COM10 0x15 /* Common Control 10 */ +#define COM10_NEGATIVE 0x80 /* Output negative data */ +#define COM10_HSYNC_EN 0x40 /* HREF changes to HSYNC */ +#define COM10_PCLK_FREE 0x00 /* PCLK output option: free running PCLK */ +#define COM10_PCLK_MASK 0x20 /* PCLK output option: masked during horizontal blank */ +#define COM10_PCLK_REV 0x10 /* PCLK reverse */ +#define COM10_HREF_REV 0x08 /* HREF reverse */ +#define COM10_VSYNC_FALLING 0x00 /* VSYNC changes on falling edge of PCLK */ +#define COM10_VSYNC_RISING 0x04 /* VSYNC changes on rising edge of PCLK */ +#define COM10_VSYNC_NEG 0x02 /* VSYNC negative */ +#define COM10_OUT_RANGE_8 0x01 /* Output data range: Full range */ +#define COM10_OUT_RANGE_10 0x00 /* Output data range: Data from [10] to [F0] (8 MSBs) */ + +#define RSVD_16 0x16 /* Reserved register */ + +#define HSTART 0x17 /* Horizontal Frame (HREF column) Start high 8-bit(low 3 bits are at HREF[2:0]) */ +#define HSTOP 0x18 /* Horizontal Frame (HREF column) end high 8-bit (low 3 bits are at HREF[5:3]) */ +#define VSTART 0x19 /* Vertical Frame (row) Start high 8-bit (low 2 bits are at VREF[1:0]) */ +#define VSTOP 0x1A /* Vertical Frame (row) End high 8-bit (low 2 bits are at VREF[3:2]) */ +#define PSHFT 0x1B /* Data Format - Pixel Delay Select */ +#define REG_MIDH 0x1C /* Manufacturer ID Byte – High */ +#define REG_MIDL 0x1D /* Manufacturer ID Byte – Low */ + +#define MVFP 0x1E /* Mirror/Vflip Enable */ +#define MVFP_MIRROR 0x20 /* Mirror image */ +#define MVFP_FLIP 0x10 /* Vertical flip */ +#define MVFP_SUN 0x02 /* Black sun enable */ +#define MVFP_SET_MIRROR(r,x) ((r&0xDF)|((x&1)<<5)) /* change only bit5 according to x */ +#define MVFP_SET_FLIP(r,x) ((r&0xEF)|((x&1)<<4)) /* change only bit4 according to x */ + +#define LAEC 0x1F /* Fine AEC Value - defines exposure value less than one row period (Reserved?) */ +#define ADCCTR0 0x20 /* ADC control */ +#define ADCCTR1 0x21 /* reserved */ +#define ADCCTR2 0x22 /* reserved */ +#define ADCCTR3 0x23 /* reserved */ +#define AEW 0x24 /* AGC/AEC - Stable Operating Region (Upper Limit) */ +#define AEB 0x25 /* AGC/AEC - Stable Operating Region (Lower Limit) */ +#define VPT 0x26 /* AGC/AEC Fast Mode Operating Region */ +#define BBIAS 0x27 /* B channel signal output bias (effective only when COM6[3]=1) */ +#define GbBIAS 0x28 /* Gb channel signal output bias (effective only when COM6[3]=1) */ +#define RSVD_29 0x29 /* reserved */ +#define EXHCH 0x2A /* Dummy Pixel Insert MSB */ +#define EXHCL 0x2B /* Dummy Pixel Insert LSB */ +#define RBIAS 0x2C /* R channel signal output bias (effective only when COM6[3]=1) */ +#define ADVFL 0x2D /* LSB of Insert Dummy Rows in Vertical Sync (1 bit equals 1 row) */ +#define ADVFH 0x2E /* MSB of Insert Dummy Rows in Vertical Sync */ +#define YAVE 0x2F /* Y/G Channel Average Value */ +#define HSYST 0x30 /* HSync rising edge delay */ +#define HSYEN 0x31 /* HSync falling edge delay */ +#define HREF 0x32 /* Image Start and Size Control DIFFERENT CONTROL SEQUENCE */ +#define CHLF 0x33 /* Array Current control */ +#define ARBLM 0x34 /* Array reference control */ +#define RSVD_35 0x35 /* Reserved */ +#define RSVD_36 0x36 /* Reserved */ +#define ADC 0x37 /* ADC control */ +#define ACOM 0x38 /* ADC and analog common mode control */ +#define OFON 0x39 /* ADC offset control */ +#define TSLB 0x3A /* Line buffer test option */ + +#define COM11 0x3B /* Common control 11 */ +#define COM11_EXP 0x02 +#define COM11_HZAUTO 0x10 /* Auto detect 50/60 Hz */ + +#define COM12 0x3C /* Common control 12 */ + +#define COM13 0x3D /* Common control 13 */ +#define COM13_GAMMA 0x80 /* Gamma enable */ +#define COM13_UVSAT 0x40 /* UV saturation auto adjustment */ + +#define COM14 0x3E /* Common Control 14 */ + +#define EDGE 0x3F /* edge enhancement adjustment */ +#define COM15 0x40 /* Common Control 15 DIFFERENT CONTROLS */ +#define COM15_SET_RGB565(r,x) ((r&0xEF)|((x&1)<<4)) /* set rgb565 mode */ +#define COM15_RGB565 0x10 /* RGB565 output */ +#define COM15_R00FF 0xC0 /* Output range: [00] to [FF] */ + +#define COM16 0x41 /* Common Control 16 DIFFERENT CONTROLS */ +#define COM16_AWBGAIN 0x08 /* AWB gain enable */ +#define COM17 0x42 /* Common Control 17 */ + +#define AWBC1 0x43 /* Reserved */ +#define AWBC2 0x44 /* Reserved */ +#define AWBC3 0x45 /* Reserved */ +#define AWBC4 0x46 /* Reserved */ +#define AWBC5 0x47 /* Reserved */ +#define AWBC6 0x48 /* Reserved */ + +#define RSVD_49 0x49 /* Reserved */ +#define RSVD_4A 0x4A /* Reserved */ + +#define REG4B 0x4B /* Register 4B */ +#define DNSTH 0x4C /* Denoise strength */ + +#define RSVD_4D 0x4D /* Reserved */ +#define RSVD_4E 0x4E /* Reserved */ + +#define MTX1 0x4F /* Matrix coefficient 1 */ +#define MTX2 0x50 /* Matrix coefficient 2 */ +#define MTX3 0x51 /* Matrix coefficient 3 */ +#define MTX4 0x52 /* Matrix coefficient 4 */ +#define MTX5 0x53 /* Matrix coefficient 5 */ +#define MTX6 0x54 /* Matrix coefficient 6 */ +#define BRIGHTNESS 0x55 /* Brightness control */ +#define CONTRAST 0x56 /* Contrast control */ +#define CONTRASCENTER 0x57 /* Contrast center */ +#define MTXS 0x58 /* Matrix coefficient sign for coefficient 5 to 0*/ + +#define RSVD_59 0x59 /* Reserved */ +#define RSVD_5A 0x5A /* Reserved */ +#define RSVD_5B 0x5B /* Reserved */ +#define RSVD_5C 0x5C /* Reserved */ +#define RSVD_5D 0x5D /* Reserved */ +#define RSVD_5E 0x5E /* Reserved */ +#define RSVD_5F 0x5F /* Reserved */ +#define RSVD_60 0x60 /* Reserved */ +#define RSVD_61 0x61 /* Reserved */ + +#define LCC1 0x62 /* Lens correction option 1 */ + +#define LCC2 0x63 /* Lens correction option 2 */ +#define LCC3 0x64 /* Lens correction option 3 */ +#define LCC4 0x65 /* Lens correction option 4 */ +#define LCC5 0x66 /* Lens correction option 5 */ + +#define MANU 0x67 /* Manual U Value */ +#define MANV 0x68 /* Manual V Value */ +#define GFIX 0x69 /* Fix gain control */ +#define GGAIN 0x6A /* G channel AWB gain */ + +#define DBLV 0x6B /* PLL and clock ? */ + +#define AWBCTR3 0x6C /* AWB Control 3 */ +#define AWBCTR2 0x6D /* AWB Control 2 */ +#define AWBCTR1 0x6E /* AWB Control 1 */ +#define AWBCTR0 0x6F /* AWB Control 0 */ +#define SCALING_XSC 0x70 /* test pattern and horizontal scaling factor */ +#define SCALING_XSC_CBAR(r) (r&0x7F) /* make sure bit7 is 0 for color bar */ +#define SCALING_YSC 0x71 /* test pattern and vertical scaling factor */ +#define SCALING_YSC_CBAR(r,x) ((r&0x7F)|((x&1)<<7)) /* change bit7 for color bar on/off */ +#define SCALING_DCWCTR 0x72 /* DCW control */ +#define SCALING_PCLK_DIV 0x73 /* */ +#define REG74 0x74 /* */ +#define REG75 0x75 /* */ +#define REG76 0x76 /* */ +#define REG77 0x77 /* */ + +#define RSVD_78 0x78 /* Reserved */ +#define RSVD_79 0x79 /* Reserved */ + +#define SLOP 0x7A /* Gamma curve highest segment slope */ +#define GAM1 0x7B /* Gamma Curve 1st Segment Input End Point 0x04 Output Value */ +#define GAM2 0x7C /* Gamma Curve 2nd Segment Input End Point 0x08 Output Value */ +#define GAM3 0x7D /* Gamma Curve 3rd Segment Input End Point 0x10 Output Value */ +#define GAM4 0x7E /* Gamma Curve 4th Segment Input End Point 0x20 Output Value */ +#define GAM5 0x7F /* Gamma Curve 5th Segment Input End Point 0x28 Output Value */ +#define GAM6 0x80 /* Gamma Curve 6rd Segment Input End Point 0x30 Output Value */ +#define GAM7 0x81 /* Gamma Curve 7th Segment Input End Point 0x38 Output Value */ +#define GAM8 0x82 /* Gamma Curve 8th Segment Input End Point 0x40 Output Value */ +#define GAM9 0x83 /* Gamma Curve 9th Segment Input End Point 0x48 Output Value */ +#define GAM10 0x84 /* Gamma Curve 10th Segment Input End Point 0x50 Output Value */ +#define GAM11 0x85 /* Gamma Curve 11th Segment Input End Point 0x60 Output Value */ +#define GAM12 0x86 /* Gamma Curve 12th Segment Input End Point 0x70 Output Value */ +#define GAM13 0x87 /* Gamma Curve 13th Segment Input End Point 0x90 Output Value */ +#define GAM14 0x88 /* Gamma Curve 14th Segment Input End Point 0xB0 Output Value */ +#define GAM15 0x89 /* Gamma Curve 15th Segment Input End Point 0xD0 Output Value */ + +#define RSVD_8A 0x8A /* Reserved */ +#define RSVD_8B 0x8B /* Reserved */ + +#define RGB444 0x8C /* */ + +#define RSVD_8D 0x8D /* Reserved */ +#define RSVD_8E 0x8E /* Reserved */ +#define RSVD_8F 0x8F /* Reserved */ +#define RSVD_90 0x90 /* Reserved */ +#define RSVD_91 0x91 /* Reserved */ + +#define DM_LNL 0x92 /* Dummy line low 8 bit */ +#define DM_LNH 0x93 /* Dummy line high 8 bit */ +#define LCC6 0x94 /* Lens correction option 6 */ +#define LCC7 0x95 /* Lens correction option 7 */ + +#define RSVD_96 0x96 /* Reserved */ +#define RSVD_97 0x97 /* Reserved */ +#define RSVD_98 0x98 /* Reserved */ +#define RSVD_99 0x99 /* Reserved */ +#define RSVD_9A 0x9A /* Reserved */ +#define RSVD_9B 0x9B /* Reserved */ +#define RSVD_9C 0x9C /* Reserved */ + +#define BD50ST 0x9D /* 50 Hz banding filter value */ +#define BD60ST 0x9E /* 60 Hz banding filter value */ +#define HAECC1 0x9F /* Histogram-based AEC/AGC control 1 */ +#define HAECC2 0xA0 /* Histogram-based AEC/AGC control 2 */ + +#define RSVD_A1 0xA1 /* Reserved */ + +#define SCALING_PCLK_DELAY 0xA2 /* Pixel clock delay */ + +#define RSVD_A3 0xA3 /* Reserved */ + +#define NT_CNTRL 0xA4 /* */ +#define BD50MAX 0xA5 /* 50 Hz banding step limit */ +#define HAECC3 0xA6 /* Histogram-based AEC/AGC control 3 */ +#define HAECC4 0xA7 /* Histogram-based AEC/AGC control 4 */ +#define HAECC5 0xA8 /* Histogram-based AEC/AGC control 5 */ +#define HAECC6 0xA9 /* Histogram-based AEC/AGC control 6 */ + +#define HAECC7 0xAA /* Histogram-based AEC/AGC control 7 */ +#define HAECC_EN 0x80 /* Histogram-based AEC algorithm enable */ + +#define BD60MAX 0xAB /* 60 Hz banding step limit */ + +#define STR_OPT 0xAC /* Register AC */ +#define STR_R 0xAD /* R gain for led output frame */ +#define STR_G 0xAE /* G gain for led output frame */ +#define STR_B 0xAF /* B gain for led output frame */ +#define RSVD_B0 0xB0 /* Reserved */ +#define ABLC1 0xB1 /* */ +#define RSVD_B2 0xB2 /* Reserved */ +#define THL_ST 0xB3 /* ABLC target */ +#define THL_DLT 0xB5 /* ABLC stable range */ + +#define RSVD_B6 0xB6 /* Reserved */ +#define RSVD_B7 0xB7 /* Reserved */ +#define RSVD_B8 0xB8 /* Reserved */ +#define RSVD_B9 0xB9 /* Reserved */ +#define RSVD_BA 0xBA /* Reserved */ +#define RSVD_BB 0xBB /* Reserved */ +#define RSVD_BC 0xBC /* Reserved */ +#define RSVD_BD 0xBD /* Reserved */ + +#define AD_CHB 0xBE /* blue channel black level compensation */ +#define AD_CHR 0xBF /* Red channel black level compensation */ +#define AD_CHGb 0xC0 /* Gb channel black level compensation */ +#define AD_CHGr 0xC1 /* Gr channel black level compensation */ + +#define RSVD_C2 0xC2 /* Reserved */ +#define RSVD_C3 0xC3 /* Reserved */ +#define RSVD_C4 0xC4 /* Reserved */ +#define RSVD_C5 0xC5 /* Reserved */ +#define RSVD_C6 0xC6 /* Reserved */ +#define RSVD_C7 0xC7 /* Reserved */ +#define RSVD_C8 0xC8 /* Reserved */ + +#define SATCTR 0xC9 /* Saturation control */ +#define SET_REG(reg, x) (##reg_DEFAULT|x) + +#endif //__OV7670_REG_REGS_H__ diff --git a/esp32-cam-rtos/sccb.c b/esp32-cam-rtos/sccb.c index d2f5fb9..cb615bb 100644 --- a/esp32-cam-rtos/sccb.c +++ b/esp32-cam-rtos/sccb.c @@ -7,6 +7,7 @@ * */ #include +#include #include #include #include "sccb.h" @@ -19,11 +20,8 @@ static const char* TAG = "sccb"; #endif -//#undef CONFIG_SCCB_HARDWARE_I2C - #define LITTLETOBIG(x) ((x<<8)|(x>>8)) -#ifdef CONFIG_SCCB_HARDWARE_I2C #include "driver/i2c.h" #define SCCB_FREQ 100000 /*!< I2C master frequency*/ @@ -39,16 +37,13 @@ const int SCCB_I2C_PORT = 1; const int SCCB_I2C_PORT = 0; #endif static uint8_t ESP_SLAVE_ADDR = 0x3c; -#else -#include "twi.h" -#endif int SCCB_Init(int pin_sda, int pin_scl) { ESP_LOGI(TAG, "pin_sda %d pin_scl %d\n", pin_sda, pin_scl); -#ifdef CONFIG_SCCB_HARDWARE_I2C //log_i("SCCB_Init start"); i2c_config_t conf; + memset(&conf, 0, sizeof(i2c_config_t)); conf.mode = I2C_MODE_MASTER; conf.sda_io_num = pin_sda; conf.sda_pullup_en = GPIO_PULLUP_ENABLE; @@ -58,15 +53,11 @@ int SCCB_Init(int pin_sda, int pin_scl) i2c_param_config(SCCB_I2C_PORT, &conf); i2c_driver_install(SCCB_I2C_PORT, conf.mode, 0, 0, 0); -#else - twi_init(pin_sda, pin_scl); -#endif return 0; } uint8_t SCCB_Probe() { -#ifdef CONFIG_SCCB_HARDWARE_I2C uint8_t slave_addr = 0x0; while(slave_addr < 0x7f) { i2c_cmd_handle_t cmd = i2c_cmd_link_create(); @@ -82,28 +73,10 @@ uint8_t SCCB_Probe() slave_addr++; } return ESP_SLAVE_ADDR; -#else - uint8_t reg = 0x00; - uint8_t slv_addr = 0x00; - - ESP_LOGI(TAG, "SCCB_Probe start"); - for (uint8_t i = 0; i < 127; i++) { - if (twi_writeTo(i, ®, 1, true) == 0) { - slv_addr = i; - break; - } - - if (i!=126) { - vTaskDelay(10 / portTICK_PERIOD_MS); // Necessary for OV7725 camera (not for OV2640). - } - } - return slv_addr; -#endif } uint8_t SCCB_Read(uint8_t slv_addr, uint8_t reg) { -#ifdef CONFIG_SCCB_HARDWARE_I2C uint8_t data=0; esp_err_t ret = ESP_FAIL; i2c_cmd_handle_t cmd = i2c_cmd_link_create(); @@ -125,28 +98,10 @@ uint8_t SCCB_Read(uint8_t slv_addr, uint8_t reg) ESP_LOGE(TAG, "SCCB_Read Failed addr:0x%02x, reg:0x%02x, data:0x%02x, ret:%d", slv_addr, reg, data, ret); } return data; -#else - uint8_t data=0; - - int rc = twi_writeTo(slv_addr, ®, 1, true); - if (rc != 0) { - data = 0xff; - } else { - rc = twi_readFrom(slv_addr, &data, 1, true); - if (rc != 0) { - data=0xFF; - } - } - if (rc != 0) { - ESP_LOGE(TAG, "SCCB_Read [%02x] failed rc=%d\n", reg, rc); - } - return data; -#endif } uint8_t SCCB_Write(uint8_t slv_addr, uint8_t reg, uint8_t data) { -#ifdef CONFIG_SCCB_HARDWARE_I2C esp_err_t ret = ESP_FAIL; i2c_cmd_handle_t cmd = i2c_cmd_link_create(); i2c_master_start(cmd); @@ -160,23 +115,10 @@ uint8_t SCCB_Write(uint8_t slv_addr, uint8_t reg, uint8_t data) ESP_LOGE(TAG, "SCCB_Write Failed addr:0x%02x, reg:0x%02x, data:0x%02x, ret:%d", slv_addr, reg, data, ret); } return ret == ESP_OK ? 0 : -1; -#else - uint8_t ret=0; - uint8_t buf[] = {reg, data}; - - if(twi_writeTo(slv_addr, buf, 2, true) != 0) { - ret=0xFF; - } - if (ret != 0) { - ESP_LOGE(TAG, "SCCB_Write [%02x]=%02x failed\n", reg, data); - } - return ret; -#endif } uint8_t SCCB_Read16(uint8_t slv_addr, uint16_t reg) { -#ifdef CONFIG_SCCB_HARDWARE_I2C uint8_t data=0; esp_err_t ret = ESP_FAIL; uint16_t reg_htons = LITTLETOBIG(reg); @@ -201,32 +143,11 @@ uint8_t SCCB_Read16(uint8_t slv_addr, uint16_t reg) ESP_LOGE(TAG, "W [%04x]=%02x fail\n", reg, data); } return data; -#else - uint8_t data=0; - uint16_t reg_htons = LITTLETOBIG(reg); - uint8_t *reg_u8 = (uint8_t *)®_htons; - uint8_t buf[] = {reg_u8[0], reg_u8[1]}; - - int rc = twi_writeTo(slv_addr, buf, 2, true); - if (rc != 0) { - data = 0xff; - } else { - rc = twi_readFrom(slv_addr, &data, 1, true); - if (rc != 0) { - data=0xFF; - } - } - if (rc != 0) { - ESP_LOGE(TAG, "R [%04x] fail rc=%d\n", reg, rc); - } - return data; -#endif } uint8_t SCCB_Write16(uint8_t slv_addr, uint16_t reg, uint8_t data) { static uint16_t i = 0; -#ifdef CONFIG_SCCB_HARDWARE_I2C esp_err_t ret = ESP_FAIL; uint16_t reg_htons = LITTLETOBIG(reg); uint8_t *reg_u8 = (uint8_t *)®_htons; @@ -243,18 +164,4 @@ uint8_t SCCB_Write16(uint8_t slv_addr, uint16_t reg, uint8_t data) ESP_LOGE(TAG, "W [%04x]=%02x %d fail\n", reg, data, i++); } return ret == ESP_OK ? 0 : -1; -#else - uint8_t ret=0; - uint16_t reg_htons = LITTLETOBIG(reg); - uint8_t *reg_u8 = (uint8_t *)®_htons; - uint8_t buf[] = {reg_u8[0], reg_u8[1], data}; - - if(twi_writeTo(slv_addr, buf, 3, true) != 0) { - ret = 0xFF; - } - if (ret != 0) { - ESP_LOGE(TAG, "W [%04x]=%02x %d fail\n", reg, data, i++); - } - return ret; -#endif } diff --git a/esp32-cam-rtos/sensor.h b/esp32-cam-rtos/sensor.h index 3ea7e2c..ad6cd89 100644 --- a/esp32-cam-rtos/sensor.h +++ b/esp32-cam-rtos/sensor.h @@ -11,11 +11,13 @@ #include #include +#define NT99141_PID (0x14) #define OV9650_PID (0x96) #define OV7725_PID (0x77) #define OV2640_PID (0x26) #define OV3660_PID (0x36) #define OV5640_PID (0x56) +#define OV7670_PID (0x76) typedef enum { PIXFORMAT_RGB565, // 2BPP/RGB565 diff --git a/esp32-cam-rtos/to_bmp.c b/esp32-cam-rtos/to_bmp.c index 59455de..85f9c88 100644 --- a/esp32-cam-rtos/to_bmp.c +++ b/esp32-cam-rtos/to_bmp.c @@ -20,6 +20,17 @@ #include "sdkconfig.h" #include "esp_jpg_decode.h" +#include "esp_system.h" +#if ESP_IDF_VERSION_MAJOR >= 4 // IDF 4+ +#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 +#include "esp32/spiram.h" +#else +#error Target CONFIG_IDF_TARGET is not supported +#endif +#else // ESP32 Before IDF 4.0 +#include "esp_spiram.h" +#endif + #if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) #include "esp32-hal-log.h" #define TAG "" diff --git a/esp32-cam-rtos/twi.h b/esp32-cam-rtos/twi.h index 60624f8..71f9907 100644 --- a/esp32-cam-rtos/twi.h +++ b/esp32-cam-rtos/twi.h @@ -35,4 +35,4 @@ uint8_t twi_readFrom(unsigned char address, unsigned char * buf, unsigned int le } #endif -#endif \ No newline at end of file +#endif diff --git a/esp32-cam/camera.c b/esp32-cam/camera.c index 1e78c99..f0f3a2e 100644 --- a/esp32-cam/camera.c +++ b/esp32-cam/camera.c @@ -48,6 +48,12 @@ #if CONFIG_OV5640_SUPPORT #include "ov5640.h" #endif +#if CONFIG_NT99141_SUPPORT +#include "nt99141.h" +#endif +#if CONFIG_OV7670_SUPPORT +#include "ov7670.h" +#endif typedef enum { CAMERA_NONE = 0, @@ -56,6 +62,8 @@ typedef enum { CAMERA_OV2640 = 2640, CAMERA_OV3660 = 3660, CAMERA_OV5640 = 5640, + CAMERA_OV7670 = 7670, + CAMERA_NT99141 = 9141, } camera_model_t; #define REG_PID 0x0A @@ -369,12 +377,10 @@ static inline void IRAM_ATTR i2s_conf_reset() } } -static void i2s_init() +static void i2s_gpio_init(const camera_config_t* config) { - camera_config_t* config = &s_state->config; - // Configure input GPIOs - gpio_num_t pins[] = { + const gpio_num_t pins[] = { config->pin_d7, config->pin_d6, config->pin_d5, @@ -391,15 +397,21 @@ static void i2s_init() .mode = GPIO_MODE_INPUT, .pull_up_en = GPIO_PULLUP_ENABLE, .pull_down_en = GPIO_PULLDOWN_DISABLE, - .intr_type = GPIO_INTR_DISABLE + .intr_type = GPIO_INTR_DISABLE, + .pin_bit_mask = 0LL }; for (int i = 0; i < sizeof(pins) / sizeof(gpio_num_t); ++i) { if (rtc_gpio_is_valid_gpio(pins[i])) { rtc_gpio_deinit(pins[i]); } - conf.pin_bit_mask = 1LL << pins[i]; - gpio_config(&conf); + conf.pin_bit_mask |= 1LL << pins[i]; } + gpio_config(&conf); +} + +static void i2s_init() +{ + camera_config_t* config = &s_state->config; // Route input GPIOs to I2S peripheral using GPIO matrix gpio_matrix_in(config->pin_d0, I2S0I_DATA_IN0_IDX, false); @@ -738,7 +750,7 @@ static void IRAM_ATTR dma_filter_buffer(size_t buf_idx) if(s_state->sensor.pixformat == PIXFORMAT_JPEG) { uint32_t sig = *((uint32_t *)s_state->fb->buf) & 0xFFFFFF; if(sig != 0xffd8ff) { - ets_printf("bh 0x%08x\n", sig); + ESP_LOGD(TAG,"unexpected JPEG signature 0x%08x\n", sig); s_state->fb->bad = 1; return; } @@ -955,11 +967,15 @@ esp_err_t camera_probe(const camera_config_t* config, camera_model_t* out_camera return ESP_ERR_NO_MEM; } - ESP_LOGD(TAG, "Enabling XCLK output"); - camera_enable_out_clock(config); + if(config->pin_xclk >= 0) { + ESP_LOGD(TAG, "Enabling XCLK output"); + camera_enable_out_clock(config); + } - ESP_LOGD(TAG, "Initializing SSCB"); - SCCB_Init(config->pin_sscb_sda, config->pin_sscb_scl); + if (config->pin_sscb_sda != -1) { + ESP_LOGD(TAG, "Initializing SSCB"); + SCCB_Init(config->pin_sscb_sda, config->pin_sscb_scl); + } if(config->pin_pwdn >= 0) { ESP_LOGD(TAG, "Resetting camera by power down line"); @@ -1011,16 +1027,33 @@ esp_err_t camera_probe(const camera_config_t* config, camera_model_t* out_camera slv_addr = SCCB_Probe(); } #endif +#if CONFIG_NT99141_SUPPORT + if (slv_addr == 0x2a) + { + ESP_LOGD(TAG, "Resetting NT99141"); + SCCB_Write16(0x2a, 0x3008, 0x01);//bank sensor + } +#endif s_state->sensor.slv_addr = slv_addr; s_state->sensor.xclk_freq_hz = config->xclk_freq_hz; -#if (CONFIG_OV3660_SUPPORT || CONFIG_OV5640_SUPPORT) +#if (CONFIG_OV3660_SUPPORT || CONFIG_OV5640_SUPPORT || CONFIG_NT99141_SUPPORT) if(s_state->sensor.slv_addr == 0x3c){ id->PID = SCCB_Read16(s_state->sensor.slv_addr, REG16_CHIDH); id->VER = SCCB_Read16(s_state->sensor.slv_addr, REG16_CHIDL); vTaskDelay(10 / portTICK_PERIOD_MS); ESP_LOGD(TAG, "Camera PID=0x%02x VER=0x%02x", id->PID, id->VER); + } else if(s_state->sensor.slv_addr == 0x2a){ + id->PID = SCCB_Read16(s_state->sensor.slv_addr, 0x3000); + id->VER = SCCB_Read16(s_state->sensor.slv_addr, 0x3001); + vTaskDelay(10 / portTICK_PERIOD_MS); + ESP_LOGD(TAG, "Camera PID=0x%02x VER=0x%02x", id->PID, id->VER); + if(config->xclk_freq_hz > 10000000) + { + ESP_LOGE(TAG, "NT99141: only XCLK under 10MHz is supported, and XCLK is now set to 10M"); + s_state->sensor.xclk_freq_hz = 10000000; + } } else { #endif id->PID = SCCB_Read(s_state->sensor.slv_addr, REG_PID); @@ -1031,7 +1064,7 @@ esp_err_t camera_probe(const camera_config_t* config, camera_model_t* out_camera ESP_LOGD(TAG, "Camera PID=0x%02x VER=0x%02x MIDL=0x%02x MIDH=0x%02x", id->PID, id->VER, id->MIDH, id->MIDL); -#if (CONFIG_OV3660_SUPPORT || CONFIG_OV5640_SUPPORT) +#if (CONFIG_OV3660_SUPPORT || CONFIG_OV5640_SUPPORT || CONFIG_NT99141_SUPPORT) } #endif @@ -1060,6 +1093,18 @@ esp_err_t camera_probe(const camera_config_t* config, camera_model_t* out_camera *out_camera_model = CAMERA_OV5640; ov5640_init(&s_state->sensor); break; +#endif +#if CONFIG_OV7670_SUPPORT + case OV7670_PID: + *out_camera_model = CAMERA_OV7670; + ov7670_init(&s_state->sensor); + break; +#endif +#if CONFIG_NT99141_SUPPORT + case NT99141_PID: + *out_camera_model = CAMERA_NT99141; + NT99141_init(&s_state->sensor); + break; #endif default: id->PID = 0; @@ -1116,6 +1161,20 @@ esp_err_t camera_init(const camera_config_t* config) frame_size = FRAMESIZE_QSXGA; } break; +#endif +#if CONFIG_OV7670_SUPPORT + case OV7670_PID: + if (frame_size > FRAMESIZE_VGA) { + frame_size = FRAMESIZE_VGA; + } + break; +#endif +#if CONFIG_NT99141_SUPPORT + case NT99141_PID: + if (frame_size > FRAMESIZE_HD) { + frame_size = FRAMESIZE_HD; + } + break; #endif default: return ESP_ERR_CAMERA_NOT_SUPPORTED; @@ -1126,7 +1185,7 @@ esp_err_t camera_init(const camera_config_t* config) if (pix_format == PIXFORMAT_GRAYSCALE) { s_state->fb_size = s_state->width * s_state->height; - if (s_state->sensor.id.PID == OV3660_PID || s_state->sensor.id.PID == OV5640_PID) { + if (s_state->sensor.id.PID == OV3660_PID || s_state->sensor.id.PID == OV5640_PID || s_state->sensor.id.PID == NT99141_PID) { if (is_hs_mode()) { s_state->sampling_mode = SM_0A00_0B00; s_state->dma_filter = &dma_filter_yuyv_highspeed; @@ -1147,20 +1206,28 @@ esp_err_t camera_init(const camera_config_t* config) } s_state->fb_bytes_per_pixel = 1; // frame buffer stores Y8 } else if (pix_format == PIXFORMAT_YUV422 || pix_format == PIXFORMAT_RGB565) { - s_state->fb_size = s_state->width * s_state->height * 2; - if (is_hs_mode() && s_state->sensor.id.PID != OV7725_PID) { - s_state->sampling_mode = SM_0A00_0B00; - s_state->dma_filter = &dma_filter_yuyv_highspeed; - } else { - s_state->sampling_mode = SM_0A0B_0C0D; - s_state->dma_filter = &dma_filter_yuyv; - } - s_state->in_bytes_per_pixel = 2; // camera sends YU/YV - s_state->fb_bytes_per_pixel = 2; // frame buffer stores YU/YV/RGB565 + s_state->fb_size = s_state->width * s_state->height * 2; + if (is_hs_mode() && s_state->sensor.id.PID != OV7725_PID) { + if(s_state->sensor.id.PID == OV7670_PID) { + s_state->sampling_mode = SM_0A0B_0B0C; + }else{ + s_state->sampling_mode = SM_0A00_0B00; + } + s_state->dma_filter = &dma_filter_yuyv_highspeed; + } else { + s_state->sampling_mode = SM_0A0B_0C0D; + s_state->dma_filter = &dma_filter_yuyv; + } + s_state->in_bytes_per_pixel = 2; // camera sends YU/YV + s_state->fb_bytes_per_pixel = 2; // frame buffer stores YU/YV/RGB565 } else if (pix_format == PIXFORMAT_RGB888) { s_state->fb_size = s_state->width * s_state->height * 3; if (is_hs_mode()) { - s_state->sampling_mode = SM_0A00_0B00; + if(s_state->sensor.id.PID == OV7670_PID) { + s_state->sampling_mode = SM_0A0B_0B0C; + }else{ + s_state->sampling_mode = SM_0A00_0B00; + } s_state->dma_filter = &dma_filter_rgb888_highspeed; } else { s_state->sampling_mode = SM_0A0B_0C0D; @@ -1169,7 +1236,7 @@ esp_err_t camera_init(const camera_config_t* config) s_state->in_bytes_per_pixel = 2; // camera sends RGB565 s_state->fb_bytes_per_pixel = 3; // frame buffer stores RGB888 } else if (pix_format == PIXFORMAT_JPEG) { - if (s_state->sensor.id.PID != OV2640_PID && s_state->sensor.id.PID != OV3660_PID && s_state->sensor.id.PID != OV5640_PID) { + if (s_state->sensor.id.PID != OV2640_PID && s_state->sensor.id.PID != OV3660_PID && s_state->sensor.id.PID != OV5640_PID && s_state->sensor.id.PID != NT99141_PID) { ESP_LOGE(TAG, "JPEG format is only supported for ov2640, ov3660 and ov5640"); err = ESP_ERR_NOT_SUPPORTED; goto fail; @@ -1256,8 +1323,13 @@ esp_err_t camera_init(const camera_config_t* config) vsync_intr_disable(); err = gpio_install_isr_service(ESP_INTR_FLAG_LEVEL1 | ESP_INTR_FLAG_IRAM); if (err != ESP_OK) { - ESP_LOGE(TAG, "gpio_install_isr_service failed (%x)", err); - goto fail; + if (err != ESP_ERR_INVALID_STATE) { + ESP_LOGE(TAG, "gpio_install_isr_service failed (%x)", err); + goto fail; + } + else { + ESP_LOGW(TAG, "gpio_install_isr_service already installed"); + } } err = gpio_isr_handler_add(s_state->config.pin_vsync, &vsync_isr, NULL); if (err != ESP_OK) { @@ -1301,26 +1373,31 @@ fail: esp_err_t esp_camera_init(const camera_config_t* config) { camera_model_t camera_model = CAMERA_NONE; + i2s_gpio_init(config); esp_err_t err = camera_probe(config, &camera_model); if (err != ESP_OK) { ESP_LOGE(TAG, "Camera probe failed with error 0x%x", err); goto fail; } if (camera_model == CAMERA_OV7725) { - ESP_LOGD(TAG, "Detected OV7725 camera"); + ESP_LOGI(TAG, "Detected OV7725 camera"); if(config->pixel_format == PIXFORMAT_JPEG) { ESP_LOGE(TAG, "Camera does not support JPEG"); err = ESP_ERR_CAMERA_NOT_SUPPORTED; goto fail; } } else if (camera_model == CAMERA_OV2640) { - ESP_LOGD(TAG, "Detected OV2640 camera"); + ESP_LOGI(TAG, "Detected OV2640 camera"); } else if (camera_model == CAMERA_OV3660) { - ESP_LOGD(TAG, "Detected OV3660 camera"); + ESP_LOGI(TAG, "Detected OV3660 camera"); } else if (camera_model == CAMERA_OV5640) { - ESP_LOGD(TAG, "Detected OV5640 camera"); + ESP_LOGI(TAG, "Detected OV5640 camera"); + } else if (camera_model == CAMERA_OV7670) { + ESP_LOGI(TAG, "Detected OV7670 camera"); + } else if (camera_model == CAMERA_NT99141) { + ESP_LOGI(TAG, "Detected NT99141 camera"); } else { - ESP_LOGE(TAG, "Camera not supported"); + ESP_LOGI(TAG, "Camera not supported"); err = ESP_ERR_CAMERA_NOT_SUPPORTED; goto fail; } @@ -1365,9 +1442,12 @@ esp_err_t esp_camera_deinit() } dma_desc_deinit(); camera_fb_deinit(); + + if(s_state->config.pin_xclk >= 0) { + camera_disable_out_clock(); + } free(s_state); s_state = NULL; - camera_disable_out_clock(); periph_module_disable(PERIPH_I2S0_MODULE); return ESP_OK; } @@ -1425,11 +1505,11 @@ sensor_t * esp_camera_sensor_get() esp_err_t esp_camera_save_to_nvs(const char *key) { -#ifdef ESP_IDF_VERSION_MAJOR - nvs_handle_t handle; -#else +//#if ESP_IDF_VERSION_MAJOR > 3 +// nvs_handle_t handle; +//#else nvs_handle handle; -#endif +//#endif esp_err_t ret = nvs_open(key,NVS_READWRITE,&handle); if (ret == ESP_OK) { @@ -1453,11 +1533,11 @@ esp_err_t esp_camera_save_to_nvs(const char *key) esp_err_t esp_camera_load_from_nvs(const char *key) { -#ifdef ESP_IDF_VERSION_MAJOR - nvs_handle_t handle; -#else +//#if ESP_IDF_VERSION_MAJOR > 3 +// nvs_handle_t handle; +//#else nvs_handle handle; -#endif +//#endif uint8_t pf; esp_err_t ret = nvs_open(key,NVS_READWRITE,&handle); diff --git a/esp32-cam/esp32-cam.ino b/esp32-cam/esp32-cam.ino index d8d1c3e..92baed4 100644 --- a/esp32-cam/esp32-cam.ino +++ b/esp32-cam/esp32-cam.ino @@ -37,10 +37,10 @@ // Select camera model //#define CAMERA_MODEL_WROVER_KIT -//#define CAMERA_MODEL_ESP_EYE +#define CAMERA_MODEL_ESP_EYE //#define CAMERA_MODEL_M5STACK_PSRAM //#define CAMERA_MODEL_M5STACK_WIDE -#define CAMERA_MODEL_AI_THINKER +//#define CAMERA_MODEL_AI_THINKER #include "camera_pins.h" @@ -113,7 +113,7 @@ void mjpegCB(void* pvParameters) { NULL, //(void*) handler, 2, &tStream, -// APP_CPU); + // APP_CPU); PRO_CPU); // Registering webserver handling routines @@ -469,6 +469,8 @@ void setup() ESP.restart(); } + sensor_t* s = esp_camera_sensor_get(); + s->set_vflip(s, true); // Configure and connect to WiFi IPAddress ip; diff --git a/esp32-cam/esp32-camera-master.zip b/esp32-cam/esp32-camera-master.zip deleted file mode 100644 index b3dbe9a0687980f5d51db3d51a8500fe022814ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 97227 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Ν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 Ee~ccmjQ{`u diff --git a/esp32-cam/img_converters.h b/esp32-cam/img_converters.h index 2b83c4d..330f8db 100644 --- a/esp32-cam/img_converters.h +++ b/esp32-cam/img_converters.h @@ -62,7 +62,8 @@ bool frame2jpg_cb(camera_fb_t * fb, uint8_t quality, jpg_out_cb cb, void * arg); * @param height Height in pixels of the source image * @param format Format of the source image * @param quality JPEG quality of the resulting image - * @param out Pointer to be populated with the address of the resulting buffer + * @param out Pointer to be populated with the address of the resulting buffer. + * You MUST free the pointer once you are done with it. * @param out_len Pointer to be populated with the length of the output buffer * * @return true on success diff --git a/esp32-cam/nt99141.c b/esp32-cam/nt99141.c new file mode 100644 index 0000000..07a9cc4 --- /dev/null +++ b/esp32-cam/nt99141.c @@ -0,0 +1,1032 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * NT99141 driver. + * + */ +#include +#include +#include +#include "sccb.h" +#include "nt99141.h" +#include "nt99141_regs.h" +#include "nt99141_settings.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#else +#include "esp_log.h" +static const char *TAG = "NT99141"; +#endif + +//#define REG_DEBUG_ON + +static int read_reg(uint8_t slv_addr, const uint16_t reg) +{ + int ret = SCCB_Read16(slv_addr, reg); +#ifdef REG_DEBUG_ON + + if (ret < 0) { + ESP_LOGE(TAG, "READ REG 0x%04x FAILED: %d", reg, ret); + } + +#endif + return ret; +} + +static int check_reg_mask(uint8_t slv_addr, uint16_t reg, uint8_t mask) +{ + return (read_reg(slv_addr, reg) & mask) == mask; +} + +static int read_reg16(uint8_t slv_addr, const uint16_t reg) +{ + int ret = 0, ret2 = 0; + ret = read_reg(slv_addr, reg); + + if (ret >= 0) { + ret = (ret & 0xFF) << 8; + ret2 = read_reg(slv_addr, reg + 1); + + if (ret2 < 0) { + ret = ret2; + } else { + ret |= ret2 & 0xFF; + } + } + + return ret; +} + + +static int write_reg(uint8_t slv_addr, const uint16_t reg, uint8_t value) +{ + int ret = 0; +#ifndef REG_DEBUG_ON + ret = SCCB_Write16(slv_addr, reg, value); +#else + int old_value = read_reg(slv_addr, reg); + + if (old_value < 0) { + return old_value; + } + + if ((uint8_t)old_value != value) { + ESP_LOGD(TAG, "NEW REG 0x%04x: 0x%02x to 0x%02x", reg, (uint8_t)old_value, value); + ret = SCCB_Write16(slv_addr, reg, value); + } else { + ESP_LOGD(TAG, "OLD REG 0x%04x: 0x%02x", reg, (uint8_t)old_value); + ret = SCCB_Write16(slv_addr, reg, value);//maybe not? + } + + if (ret < 0) { + ESP_LOGE(TAG, "WRITE REG 0x%04x FAILED: %d", reg, ret); + } + +#endif + return ret; +} + +static int set_reg_bits(uint8_t slv_addr, uint16_t reg, uint8_t offset, uint8_t mask, uint8_t value) +{ + int ret = 0; + uint8_t c_value, new_value; + ret = read_reg(slv_addr, reg); + + if (ret < 0) { + return ret; + } + + c_value = ret; + new_value = (c_value & ~(mask << offset)) | ((value & mask) << offset); + ret = write_reg(slv_addr, reg, new_value); + return ret; +} + +static int write_regs(uint8_t slv_addr, const uint16_t (*regs)[2]) +{ + int i = 0, ret = 0; + + while (!ret && regs[i][0] != REGLIST_TAIL) { + if (regs[i][0] == REG_DLY) { + vTaskDelay(regs[i][1] / portTICK_PERIOD_MS); + } else { + ret = write_reg(slv_addr, regs[i][0], regs[i][1]); + } + + i++; + } + + return ret; +} + +static int write_reg16(uint8_t slv_addr, const uint16_t reg, uint16_t value) +{ + if (write_reg(slv_addr, reg, value >> 8) || write_reg(slv_addr, reg + 1, value)) { + return -1; + } + + return 0; +} + +static int write_addr_reg(uint8_t slv_addr, const uint16_t reg, uint16_t x_value, uint16_t y_value) +{ + if (write_reg16(slv_addr, reg, x_value) || write_reg16(slv_addr, reg + 2, y_value)) { + return -1; + } + + return 0; +} + +#define write_reg_bits(slv_addr, reg, mask, enable) set_reg_bits(slv_addr, reg, 0, mask, enable?mask:0) + +static int calc_sysclk(int xclk, bool pll_bypass, int pll_multiplier, int pll_sys_div, int pll_pre_div, bool pll_root_2x, int pll_seld5, bool pclk_manual, int pclk_div) +{ + const int pll_pre_div2x_map[] = { 2, 3, 4, 6 };//values are multiplied by two to avoid floats + const int pll_seld52x_map[] = { 2, 2, 4, 5 }; + + if (!pll_sys_div) { + pll_sys_div = 1; + } + + int pll_pre_div2x = pll_pre_div2x_map[pll_pre_div]; + int pll_root_div = pll_root_2x ? 2 : 1; + int pll_seld52x = pll_seld52x_map[pll_seld5]; + + int VCO = (xclk / 1000) * pll_multiplier * pll_root_div * 2 / pll_pre_div2x; + int PLLCLK = pll_bypass ? (xclk) : (VCO * 1000 * 2 / pll_sys_div / pll_seld52x); + int PCLK = PLLCLK / 2 / ((pclk_manual && pclk_div) ? pclk_div : 1); + int SYSCLK = PLLCLK / 4; + + ESP_LOGD(TAG, "Calculated VCO: %d Hz, PLLCLK: %d Hz, SYSCLK: %d Hz, PCLK: %d Hz", VCO * 1000, PLLCLK, SYSCLK, PCLK); + return SYSCLK; +} + +static int set_pll(sensor_t *sensor, bool bypass, uint8_t multiplier, uint8_t sys_div, uint8_t pre_div, bool root_2x, uint8_t seld5, bool pclk_manual, uint8_t pclk_div) +{ + return -1; +} + +static int set_ae_level(sensor_t *sensor, int level); + +static int reset(sensor_t *sensor) +{ + + int ret = 0; + // Software Reset: clear all registers and reset them to their default values + ret = write_reg(sensor->slv_addr, SYSTEM_CTROL0, 0x01); + + if (ret) { + ESP_LOGE(TAG, "Software Reset FAILED!"); + return ret; + } + + vTaskDelay(100 / portTICK_PERIOD_MS); + ret = write_regs(sensor->slv_addr, sensor_default_regs); //re-initial + + if (ret == 0) { + ESP_LOGD(TAG, "Camera defaults loaded"); + ret = set_ae_level(sensor, 0); + vTaskDelay(100 / portTICK_PERIOD_MS); + } + + return ret; +} + +static int set_pixformat(sensor_t *sensor, pixformat_t pixformat) +{ + int ret = 0; + const uint16_t (*regs)[2]; + + switch (pixformat) { + case PIXFORMAT_YUV422: + regs = sensor_fmt_yuv422; + break; + + case PIXFORMAT_GRAYSCALE: + regs = sensor_fmt_grayscale; + break; + + case PIXFORMAT_RGB565: + case PIXFORMAT_RGB888: + regs = sensor_fmt_rgb565; + break; + + case PIXFORMAT_JPEG: + regs = sensor_fmt_jpeg; + break; + + case PIXFORMAT_RAW: + regs = sensor_fmt_raw; + break; + + default: + ESP_LOGE(TAG, "Unsupported pixformat: %u", pixformat); + return -1; + } + + ret = write_regs(sensor->slv_addr, regs); + + if (ret == 0) { + sensor->pixformat = pixformat; + ESP_LOGD(TAG, "Set pixformat to: %u", pixformat); + } + + return ret; +} + +static int set_image_options(sensor_t *sensor) +{ + int ret = 0; + uint8_t reg20 = 0; + uint8_t reg21 = 0; + uint8_t reg4514 = 0; + uint8_t reg4514_test = 0; + + // V-Flip + if (sensor->status.vflip) { + reg20 |= 0x01; + reg4514_test |= 1; + } + + // H-Mirror + if (sensor->status.hmirror) { + reg21 |= 0x02; + reg4514_test |= 2; + } + + switch (reg4514_test) { + + } + + if (write_reg(sensor->slv_addr, TIMING_TC_REG20, reg20 | reg21)) { + ESP_LOGE(TAG, "Setting Image Options Failed"); + ret = -1; + } + + ESP_LOGD(TAG, "Set Image Options: Compression: %u, Binning: %u, V-Flip: %u, H-Mirror: %u, Reg-4514: 0x%02x", + sensor->pixformat == PIXFORMAT_JPEG, sensor->status.binning, sensor->status.vflip, sensor->status.hmirror, reg4514); + return ret; +} + +static int set_framesize(sensor_t *sensor, framesize_t framesize) +{ + int ret = 0; + + sensor->status.framesize = framesize; + ret = write_regs(sensor->slv_addr, sensor_default_regs); + + if (framesize == FRAMESIZE_QVGA) { + ESP_LOGD(TAG, "Set FRAMESIZE_QVGA"); + ret = write_regs(sensor->slv_addr, sensor_framesize_QVGA); +#if CONFIG_NT99141_SUPPORT_XSKIP + ESP_LOGD(TAG, "Set FRAMESIZE_QVGA: xskip mode"); + ret = write_regs(sensor->slv_addr, sensor_framesize_QVGA_xskip); +#elif CONFIG_NT99141_SUPPORT_CROP + ESP_LOGD(TAG, "Set FRAMESIZE_QVGA: crop mode"); + ret = write_regs(sensor->slv_addr, sensor_framesize_QVGA_crop); +#endif + } else if (framesize == FRAMESIZE_VGA) { + ESP_LOGD(TAG, "Set FRAMESIZE_VGA"); + // ret = write_regs(sensor->slv_addr, sensor_framesize_VGA); + ret = write_regs(sensor->slv_addr, sensor_framesize_VGA_xyskip);// Resolution:640*360 This configuration is equally-scaled without deforming +#ifdef CONFIG_NT99141_SUPPORT_XSKIP + ESP_LOGD(TAG, "Set FRAMESIZE_QVGA: xskip mode"); + ret = write_regs(sensor->slv_addr, sensor_framesize_VGA_xskip); +#elif CONFIG_NT99141_SUPPORT_CROP + ESP_LOGD(TAG, "Set FRAMESIZE_QVGA: crop mode"); + ret = write_regs(sensor->slv_addr, sensor_framesize_VGA_crop); +#endif + } else if (framesize >= FRAMESIZE_HD) { + ESP_LOGD(TAG, "Set FRAMESIZE_HD"); + ret = write_regs(sensor->slv_addr, sensor_framesize_HD); + } else { + ESP_LOGD(TAG, "Dont suppost this size, Set FRAMESIZE_VGA"); + ret = write_regs(sensor->slv_addr, sensor_framesize_VGA); + } + + return 0; +} + +static int set_hmirror(sensor_t *sensor, int enable) +{ + int ret = 0; + sensor->status.hmirror = enable; + ret = set_image_options(sensor); + + if (ret == 0) { + ESP_LOGD(TAG, "Set h-mirror to: %d", enable); + } + + return ret; +} + +static int set_vflip(sensor_t *sensor, int enable) +{ + int ret = 0; + sensor->status.vflip = enable; + ret = set_image_options(sensor); + + if (ret == 0) { + ESP_LOGD(TAG, "Set v-flip to: %d", enable); + } + + return ret; +} + +static int set_quality(sensor_t *sensor, int qs) +{ + int ret = 0; + ret = write_reg(sensor->slv_addr, COMPRESSION_CTRL07, qs & 0x3f); + + if (ret == 0) { + sensor->status.quality = qs; + ESP_LOGD(TAG, "Set quality to: %d", qs); + } + + return ret; +} + +static int set_colorbar(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, PRE_ISP_TEST_SETTING_1, TEST_COLOR_BAR, enable); + + if (ret == 0) { + sensor->status.colorbar = enable; + ESP_LOGD(TAG, "Set colorbar to: %d", enable); + } + + return ret; +} + +static int set_gain_ctrl(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, 0x32bb, 0x87, enable); + + if (ret == 0) { + ESP_LOGD(TAG, "Set gain_ctrl to: %d", enable); + sensor->status.agc = enable; + } + + return ret; +} + +static int set_exposure_ctrl(sensor_t *sensor, int enable) +{ + int ret = 0; + int data = 0; + // ret = write_reg_bits(sensor->slv_addr, 0x32bb, 0x87, enable); + data = read_reg(sensor->slv_addr, 0x3201); + ESP_LOGD(TAG, "set_exposure_ctrl:enable"); + if (enable) { + ESP_LOGD(TAG, "set_exposure_ctrl:enable"); + ret = write_reg(sensor->slv_addr, 0x3201, (1 << 5) | data); + } else { + ESP_LOGD(TAG, "set_exposure_ctrl:disable"); + ret = write_reg(sensor->slv_addr, 0x3201, (~(1 << 5)) & data); + } + + if (ret == 0) { + ESP_LOGD(TAG, "Set exposure_ctrl to: %d", enable); + sensor->status.aec = enable; + } + + return ret; +} + +static int set_whitebal(sensor_t *sensor, int enable) +{ + int ret = 0; + + if (ret == 0) { + ESP_LOGD(TAG, "Set awb to: %d", enable); + sensor->status.awb = enable; + } + + return ret; +} + +//Advanced AWB +static int set_dcw_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + + if (ret == 0) { + ESP_LOGD(TAG, "Set dcw to: %d", enable); + sensor->status.dcw = enable; + } + + return ret; +} + +//night mode enable +static int set_aec2(sensor_t *sensor, int enable) +{ + int ret = 0; + + if (ret == 0) { + ESP_LOGD(TAG, "Set aec2 to: %d", enable); + sensor->status.aec2 = enable; + } + + return ret; +} + +static int set_bpc_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + + if (ret == 0) { + ESP_LOGD(TAG, "Set bpc to: %d", enable); + sensor->status.bpc = enable; + } + + return ret; +} + +static int set_wpc_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + + if (ret == 0) { + ESP_LOGD(TAG, "Set wpc to: %d", enable); + sensor->status.wpc = enable; + } + + return ret; +} + +//Gamma enable +static int set_raw_gma_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + + if (ret == 0) { + ESP_LOGD(TAG, "Set raw_gma to: %d", enable); + sensor->status.raw_gma = enable; + } + + return ret; +} + +static int set_lenc_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + + if (ret == 0) { + ESP_LOGD(TAG, "Set lenc to: %d", enable); + sensor->status.lenc = enable; + } + + return ret; +} + +static int get_agc_gain(sensor_t *sensor) +{ + ESP_LOGD(TAG, "get_agc_gain can not be configured at present"); + return 0; +} + +//real gain +static int set_agc_gain(sensor_t *sensor, int gain) +{ + ESP_LOGD(TAG, "set_agc_gain can not be configured at present"); + // ESP_LOGD(TAG, "GAIN = %d\n", gain); + int cnt = gain / 2; + + switch (cnt) { + case 0: + ESP_LOGD(TAG, "set_agc_gain: 1x"); + write_reg(sensor->slv_addr, 0X301D, 0X00); + break; + + case 1: + ESP_LOGD(TAG,"set_agc_gain: 2x"); + write_reg(sensor->slv_addr, 0X301D, 0X0F); + break; + + case 2: + ESP_LOGD(TAG,"set_agc_gain: 4x"); + write_reg(sensor->slv_addr, 0X301D, 0X2F); + break; + + case 3: + ESP_LOGD(TAG,"set_agc_gain: 6x"); + write_reg(sensor->slv_addr, 0X301D, 0X37); + break; + + case 4: + ESP_LOGD(TAG,"set_agc_gain: 8x"); + write_reg(sensor->slv_addr, 0X301D, 0X3F); + break; + + default: + ESP_LOGD(TAG,"fail set_agc_gain"); + break; + } + + return 0; +} + +static int get_aec_value(sensor_t *sensor) +{ + ESP_LOGD(TAG, "get_aec_value can not be configured at present"); + return 0; +} + +static int set_aec_value(sensor_t *sensor, int value) +{ + ESP_LOGD(TAG, "set_aec_value can not be configured at present"); + int ret = 0; + // ESP_LOGD(TAG, " set_aec_value to: %d", value); + ret = write_reg_bits(sensor->slv_addr, 0x3012, 0x00, (value >> 8) & 0xff); + ret = write_reg_bits(sensor->slv_addr, 0x3013, 0x01, value & 0xff); + + if (ret == 0) { + ESP_LOGD(TAG, " set_aec_value to: %d", value); + // sensor->status.aec = enable; + } + + return ret; +} + +static int set_ae_level(sensor_t *sensor, int level) +{ + ESP_LOGD(TAG, "set_ae_level can not be configured at present"); + int ret = 0; + + if (level < 0) { + level = 0; + } else if (level > 9) { + level = 9; + } + + for (int i = 0; i < 5; i++) { + ret += write_reg(sensor->slv_addr, sensor_ae_level[ 5 * level + i ][0], sensor_ae_level[5 * level + i ][1]); + } + + if (ret) { + ESP_LOGE(TAG, " fail to set ae level: %d", ret); + } + + return 0; +} + +static int set_wb_mode(sensor_t *sensor, int mode) +{ + int ret = 0; + + if (mode < 0 || mode > 4) { + return -1; + } + + ret = write_reg(sensor->slv_addr, 0x3201, (mode != 0)); + + if (ret) { + return ret; + } + + switch (mode) { + case 1://Sunny + ret = write_reg16(sensor->slv_addr, 0x3290, 0x01) + || write_reg16(sensor->slv_addr, 0x3291, 0x38) + || write_reg16(sensor->slv_addr, 0x3296, 0x01) + || write_reg16(sensor->slv_addr, 0x3297, 0x68) + || write_reg16(sensor->slv_addr, 0x3060, 0x01); + + break; + + case 2://Cloudy + + ret = write_reg16(sensor->slv_addr, 0x3290, 0x01) + || write_reg16(sensor->slv_addr, 0x3291, 0x51) + || write_reg16(sensor->slv_addr, 0x3296, 0x01) + || write_reg16(sensor->slv_addr, 0x3297, 0x00) + || write_reg16(sensor->slv_addr, 0x3060, 0x01); + break; + + case 3://INCANDESCENCE] + ret = write_reg16(sensor->slv_addr, 0x3290, 0x01) + || write_reg16(sensor->slv_addr, 0x3291, 0x30) + || write_reg16(sensor->slv_addr, 0x3296, 0x01) + || write_reg16(sensor->slv_addr, 0x3297, 0xCB) + || write_reg16(sensor->slv_addr, 0x3060, 0x01); + break; + + case 4://FLUORESCENT + ret = write_reg16(sensor->slv_addr, 0x3290, 0x01) + || write_reg16(sensor->slv_addr, 0x3291, 0x70) + || write_reg16(sensor->slv_addr, 0x3296, 0x01) + || write_reg16(sensor->slv_addr, 0x3297, 0xFF) + || write_reg16(sensor->slv_addr, 0x3060, 0x01); + break; + + default://AUTO + break; + } + + if (ret == 0) { + ESP_LOGD(TAG, "Set wb_mode to: %d", mode); + sensor->status.wb_mode = mode; + } + + return ret; +} + +static int set_awb_gain_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + int old_mode = sensor->status.wb_mode; + int mode = enable ? old_mode : 0; + + ret = set_wb_mode(sensor, mode); + + if (ret == 0) { + sensor->status.wb_mode = old_mode; + ESP_LOGD(TAG, "Set awb_gain to: %d", enable); + sensor->status.awb_gain = enable; + } + + return ret; +} + +static int set_special_effect(sensor_t *sensor, int effect) +{ + int ret = 0; + + if (effect < 0 || effect > 6) { + return -1; + } + + uint8_t *regs = (uint8_t *)sensor_special_effects[effect]; + ret = write_reg(sensor->slv_addr, 0x32F1, regs[0]) + || write_reg(sensor->slv_addr, 0x32F4, regs[1]) + || write_reg(sensor->slv_addr, 0x32F5, regs[2]) + || write_reg(sensor->slv_addr, 0x3060, regs[3]); + + if (ret == 0) { + ESP_LOGD(TAG, "Set special_effect to: %d", effect); + sensor->status.special_effect = effect; + } + + return ret; +} + +static int set_brightness(sensor_t *sensor, int level) +{ + int ret = 0; + uint8_t value = 0; + bool negative = false; + + switch (level) { + case 3: + value = 0xA0; + break; + + case 2: + value = 0x90; + break; + + case 1: + value = 0x88; + break; + + case -1: + value = 0x78; + negative = true; + break; + + case -2: + value = 0x70; + negative = true; + break; + + case -3: + value = 0x60; + negative = true; + break; + + default: // 0 + break; + } + + ret = write_reg(sensor->slv_addr, 0x32F2, value); + + if (ret == 0) { + ESP_LOGD(TAG, "Set brightness to: %d", level); + sensor->status.brightness = level; + } + + return ret; +} + +static int set_contrast(sensor_t *sensor, int level) +{ + int ret = 0; + uint8_t value1 = 0, value2 = 0 ; + bool negative = false; + + switch (level) { + case 3: + value1 = 0xD0; + value2 = 0xB0; + break; + + case 2: + value1 = 0xE0; + value2 = 0xA0; + break; + + case 1: + value1 = 0xF0; + value2 = 0x90; + break; + + case 0: + value1 = 0x00; + value2 = 0x80; + break; + + case -1: + value1 = 0x10; + value2 = 0x70; + break; + + case -2: + value1 = 0x20; + value2 = 0x60; + break; + + case -3: + value1 = 0x30; + value2 = 0x50; + break; + + default: // 0 + break; + } + + ret = write_reg(sensor->slv_addr, 0x32FC, value1); + ret = write_reg(sensor->slv_addr, 0x32F2, value2); + ret = write_reg(sensor->slv_addr, 0x3060, 0x01); + + if (ret == 0) { + ESP_LOGD(TAG, "Set contrast to: %d", level); + sensor->status.contrast = level; + } + + return ret; +} + +static int set_saturation(sensor_t *sensor, int level) +{ + int ret = 0; + + if (level > 4 || level < -4) { + return -1; + } + + uint8_t *regs = (uint8_t *)sensor_saturation_levels[level + 4]; + { + ret = write_reg(sensor->slv_addr, 0x32F3, regs[0]); + + if (ret) { + return ret; + } + } + + if (ret == 0) { + ESP_LOGD(TAG, "Set saturation to: %d", level); + sensor->status.saturation = level; + } + + return ret; +} + +static int set_sharpness(sensor_t *sensor, int level) +{ + int ret = 0; + + if (level > 3 || level < -3) { + return -1; + } + + uint8_t mt_offset_2 = (level + 3) * 8; + uint8_t mt_offset_1 = mt_offset_2 + 1; + + ret = write_reg_bits(sensor->slv_addr, 0x5308, 0x40, false)//0x40 means auto + || write_reg(sensor->slv_addr, 0x5300, 0x10) + || write_reg(sensor->slv_addr, 0x5301, 0x10) + || write_reg(sensor->slv_addr, 0x5302, mt_offset_1) + || write_reg(sensor->slv_addr, 0x5303, mt_offset_2) + || write_reg(sensor->slv_addr, 0x5309, 0x10) + || write_reg(sensor->slv_addr, 0x530a, 0x10) + || write_reg(sensor->slv_addr, 0x530b, 0x04) + || write_reg(sensor->slv_addr, 0x530c, 0x06); + + if (ret == 0) { + ESP_LOGD(TAG, "Set sharpness to: %d", level); + sensor->status.sharpness = level; + } + + return ret; +} + +static int set_gainceiling(sensor_t *sensor, gainceiling_t level) +{ + ESP_LOGD(TAG, "set_gainceiling can not be configured at present"); + return 0; +} + +static int get_denoise(sensor_t *sensor) +{ + + return (read_reg(sensor->slv_addr, 0x5306) / 4) + 1; +} + +static int set_denoise(sensor_t *sensor, int level) +{ + ESP_LOGD(TAG, "set_denoise can not be configured at present"); + return 0; +} + +static int get_reg(sensor_t *sensor, int reg, int mask) +{ + int ret = 0, ret2 = 0; + + if (mask > 0xFF) { + ret = read_reg16(sensor->slv_addr, reg); + + if (ret >= 0 && mask > 0xFFFF) { + ret2 = read_reg(sensor->slv_addr, reg + 2); + + if (ret2 >= 0) { + ret = (ret << 8) | ret2 ; + } else { + ret = ret2; + } + } + } else { + ret = read_reg(sensor->slv_addr, reg); + } + + if (ret > 0) { + ret &= mask; + } + + return ret; +} + +static int set_reg(sensor_t *sensor, int reg, int mask, int value) +{ + int ret = 0, ret2 = 0; + + if (mask > 0xFF) { + ret = read_reg16(sensor->slv_addr, reg); + + if (ret >= 0 && mask > 0xFFFF) { + ret2 = read_reg(sensor->slv_addr, reg + 2); + + if (ret2 >= 0) { + ret = (ret << 8) | ret2 ; + } else { + ret = ret2; + } + } + } else { + ret = read_reg(sensor->slv_addr, reg); + } + + if (ret < 0) { + return ret; + } + + value = (ret & ~mask) | (value & mask); + + if (mask > 0xFFFF) { + ret = write_reg16(sensor->slv_addr, reg, value >> 8); + + if (ret >= 0) { + ret = write_reg(sensor->slv_addr, reg + 2, value & 0xFF); + } + } else if (mask > 0xFF) { + ret = write_reg16(sensor->slv_addr, reg, value); + } else { + ret = write_reg(sensor->slv_addr, reg, value); + } + + return ret; +} + +static int set_res_raw(sensor_t *sensor, int startX, int startY, int endX, int endY, int offsetX, int offsetY, int totalX, int totalY, int outputX, int outputY, bool scale, bool binning) +{ + int ret = 0; + ret = write_addr_reg(sensor->slv_addr, X_ADDR_ST_H, startX, startY) + || write_addr_reg(sensor->slv_addr, X_ADDR_END_H, endX, endY) + || write_addr_reg(sensor->slv_addr, X_OFFSET_H, offsetX, offsetY) + || write_addr_reg(sensor->slv_addr, X_TOTAL_SIZE_H, totalX, totalY) + || write_addr_reg(sensor->slv_addr, X_OUTPUT_SIZE_H, outputX, outputY); + + if (!ret) { + sensor->status.scale = scale; + sensor->status.binning = binning; + ret = set_image_options(sensor); + } + + return ret; +} + +static int _set_pll(sensor_t *sensor, int bypass, int multiplier, int sys_div, int root_2x, int pre_div, int seld5, int pclk_manual, int pclk_div) +{ + return set_pll(sensor, bypass > 0, multiplier, sys_div, pre_div, root_2x > 0, seld5, pclk_manual > 0, pclk_div); +} + +esp_err_t xclk_timer_conf(int ledc_timer, int xclk_freq_hz); +static int set_xclk(sensor_t *sensor, int timer, int xclk) +{ + int ret = 0; + if (xclk > 10) + { + ESP_LOGE(TAG, "only XCLK under 10MHz is supported, and XCLK is now set to 10M"); + xclk = 10; + } + sensor->xclk_freq_hz = xclk * 1000000U; + ret = xclk_timer_conf(timer, sensor->xclk_freq_hz); + return ret; +} + +static int init_status(sensor_t *sensor) +{ + sensor->status.brightness = 0; + sensor->status.contrast = 0; + sensor->status.saturation = 0; + sensor->status.sharpness = (read_reg(sensor->slv_addr, 0x3301)); + sensor->status.denoise = get_denoise(sensor); + sensor->status.ae_level = 0; + sensor->status.gainceiling = read_reg16(sensor->slv_addr, 0x32F0) & 0xFF; + sensor->status.awb = check_reg_mask(sensor->slv_addr, ISP_CONTROL_01, 0x10); + sensor->status.dcw = !check_reg_mask(sensor->slv_addr, 0x5183, 0x80); + sensor->status.agc = !check_reg_mask(sensor->slv_addr, AEC_PK_MANUAL, AEC_PK_MANUAL_AGC_MANUALEN); + sensor->status.aec = !check_reg_mask(sensor->slv_addr, AEC_PK_MANUAL, AEC_PK_MANUAL_AEC_MANUALEN); + sensor->status.hmirror = check_reg_mask(sensor->slv_addr, TIMING_TC_REG21, TIMING_TC_REG21_HMIRROR); + sensor->status.vflip = check_reg_mask(sensor->slv_addr, TIMING_TC_REG20, TIMING_TC_REG20_VFLIP); + sensor->status.colorbar = check_reg_mask(sensor->slv_addr, PRE_ISP_TEST_SETTING_1, TEST_COLOR_BAR); + sensor->status.bpc = check_reg_mask(sensor->slv_addr, 0x5000, 0x04); + sensor->status.wpc = check_reg_mask(sensor->slv_addr, 0x5000, 0x02); + sensor->status.raw_gma = check_reg_mask(sensor->slv_addr, 0x5000, 0x20); + sensor->status.lenc = check_reg_mask(sensor->slv_addr, 0x5000, 0x80); + sensor->status.quality = read_reg(sensor->slv_addr, COMPRESSION_CTRL07) & 0x3f; + sensor->status.special_effect = 0; + sensor->status.wb_mode = 0; + sensor->status.awb_gain = check_reg_mask(sensor->slv_addr, 0x3000, 0x01); + sensor->status.agc_gain = get_agc_gain(sensor); + sensor->status.aec_value = get_aec_value(sensor); + sensor->status.aec2 = check_reg_mask(sensor->slv_addr, 0x3000, 0x04); + return 0; +} + +int NT99141_init(sensor_t *sensor) +{ + sensor->reset = reset; + sensor->set_pixformat = set_pixformat; + sensor->set_framesize = set_framesize; + sensor->set_contrast = set_contrast; + sensor->set_brightness = set_brightness; + sensor->set_saturation = set_saturation; + sensor->set_sharpness = set_sharpness; + sensor->set_gainceiling = set_gainceiling; + sensor->set_quality = set_quality; + sensor->set_colorbar = set_colorbar; + sensor->set_gain_ctrl = set_gain_ctrl; + sensor->set_exposure_ctrl = set_exposure_ctrl; + sensor->set_whitebal = set_whitebal; + sensor->set_hmirror = set_hmirror; + sensor->set_vflip = set_vflip; + sensor->init_status = init_status; + sensor->set_aec2 = set_aec2; + sensor->set_aec_value = set_aec_value; + sensor->set_special_effect = set_special_effect; + sensor->set_wb_mode = set_wb_mode; + sensor->set_ae_level = set_ae_level; + sensor->set_dcw = set_dcw_dsp; + sensor->set_bpc = set_bpc_dsp; + sensor->set_wpc = set_wpc_dsp; + sensor->set_awb_gain = set_awb_gain_dsp; + sensor->set_agc_gain = set_agc_gain; + sensor->set_raw_gma = set_raw_gma_dsp; + sensor->set_lenc = set_lenc_dsp; + sensor->set_denoise = set_denoise; + + sensor->get_reg = get_reg; + sensor->set_reg = set_reg; + sensor->set_res_raw = set_res_raw; + sensor->set_pll = _set_pll; + sensor->set_xclk = set_xclk; + return 0; +} diff --git a/esp32-cam/nt99141.h b/esp32-cam/nt99141.h new file mode 100644 index 0000000..287a742 --- /dev/null +++ b/esp32-cam/nt99141.h @@ -0,0 +1,16 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * NT99141 driver. + * + */ +#ifndef __NT99141_H__ +#define __NT99141_H__ + +#include "sensor.h" + +int NT99141_init(sensor_t *sensor); + +#endif // __NT99141_H__ diff --git a/esp32-cam/nt99141_regs.h b/esp32-cam/nt99141_regs.h new file mode 100644 index 0000000..8301db9 --- /dev/null +++ b/esp32-cam/nt99141_regs.h @@ -0,0 +1,211 @@ +/* + * NT99141 register definitions. + */ +#ifndef __NT99141_REG_REGS_H__ +#define __NT99141_REG_REGS_H__ + +/* system control registers */ +#define SYSTEM_CTROL0 0x3021 // 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 + +/* output format control registers */ +#define FORMAT_CTRL 0x501F // Format select + // Bit[2:0]: + // 000: YUV422 + // 001: RGB + // 010: Dither + // 011: RAW after DPC + // 101: RAW after CIP + +/* format control registers */ +#define FORMAT_CTRL00 0x4300 + +/* frame control registers */ +#define FRAME_CTRL01 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 +#define FRAME_CTRL02 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 + +/* ISP top control registers */ +#define PRE_ISP_TEST_SETTING_1 0x3025 // 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 + +/* AEC/AGC control functions */ +#define AEC_PK_MANUAL 0x3201 // 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 + +/* mirror and flip registers */ +#define TIMING_TC_REG20 0x3022 // Timing Control Register + // Bit[2:1]: Vertical flip enable + // 00: Normal + // 11: Vertical flip + // Bit[0]: Vertical binning enable +#define TIMING_TC_REG21 0x3022 // Timing Control Register + // Bit[5]: Compression Enable + // Bit[2:1]: Horizontal mirror enable + // 00: Normal + // 11: Horizontal mirror + // Bit[0]: Horizontal binning enable + +#define CLOCK_POL_CONTROL 0x3024// 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 +#define DRIVE_CAPABILITY 0x306a // Bit[7:6]: + // 00: 1x + // 01: 2x + // 10: 3x + // 11: 4x + + +#define X_ADDR_ST_H 0x3800 //Bit[3:0]: X address start[11:8] +#define X_ADDR_ST_L 0x3801 //Bit[7:0]: X address start[7:0] +#define Y_ADDR_ST_H 0x3802 //Bit[2:0]: Y address start[10:8] +#define Y_ADDR_ST_L 0x3803 //Bit[7:0]: Y address start[7:0] +#define X_ADDR_END_H 0x3804 //Bit[3:0]: X address end[11:8] +#define X_ADDR_END_L 0x3805 //Bit[7:0]: +#define Y_ADDR_END_H 0x3806 //Bit[2:0]: Y address end[10:8] +#define Y_ADDR_END_L 0x3807 //Bit[7:0]: +// Size after scaling +#define X_OUTPUT_SIZE_H 0x3808 //Bit[3:0]: DVP output horizontal width[11:8] +#define X_OUTPUT_SIZE_L 0x3809 //Bit[7:0]: +#define Y_OUTPUT_SIZE_H 0x380a //Bit[2:0]: DVP output vertical height[10:8] +#define Y_OUTPUT_SIZE_L 0x380b //Bit[7:0]: +#define X_TOTAL_SIZE_H 0x380c //Bit[3:0]: Total horizontal size[11:8] +#define X_TOTAL_SIZE_L 0x380d //Bit[7:0]: +#define Y_TOTAL_SIZE_H 0x380e //Bit[7:0]: Total vertical size[15:8] +#define Y_TOTAL_SIZE_L 0x380f //Bit[7:0]: +#define X_OFFSET_H 0x3810 //Bit[3:0]: ISP horizontal offset[11:8] +#define X_OFFSET_L 0x3811 //Bit[7:0]: +#define Y_OFFSET_H 0x3812 //Bit[2:0]: ISP vertical offset[10:8] +#define Y_OFFSET_L 0x3813 //Bit[7:0]: +#define X_INCREMENT 0x3814 //Bit[7:4]: Horizontal odd subsample increment + //Bit[3:0]: Horizontal even subsample increment +#define Y_INCREMENT 0x3815 //Bit[7:4]: Vertical odd subsample increment + //Bit[3:0]: Vertical even subsample increment +// Size before scaling +//#define X_INPUT_SIZE (X_ADDR_END - X_ADDR_ST + 1 - (2 * X_OFFSET)) +//#define Y_INPUT_SIZE (Y_ADDR_END - Y_ADDR_ST + 1 - (2 * Y_OFFSET)) + +#define ISP_CONTROL_01 0x3021 // Bit[5]: Scale enable + // 0: Disable + // 1: Enable + +#define SCALE_CTRL_1 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 + +#define SCALE_CTRL_2 0x5602 // X_SCALE High Bits +#define SCALE_CTRL_3 0x5603 // X_SCALE Low Bits +#define SCALE_CTRL_4 0x5604 // Y_SCALE High Bits +#define SCALE_CTRL_5 0x5605 // Y_SCALE Low Bits +#define SCALE_CTRL_6 0x5606 // Bit[3:0]: V Offset + +#define PCLK_RATIO 0x3824 // Bit[4:0]: PCLK ratio manual +#define VFIFO_CTRL0C 0x460C // Bit[1]: PCLK manual enable + // 0: Auto + // 1: Manual by PCLK_RATIO + +#define VFIFO_X_SIZE_H 0x4602 +#define VFIFO_X_SIZE_L 0x4603 +#define VFIFO_Y_SIZE_H 0x4604 +#define VFIFO_Y_SIZE_L 0x4605 + +#define SC_PLLS_CTRL0 0x303a // Bit[7]: PLLS bypass +#define SC_PLLS_CTRL1 0x303b // Bit[4:0]: PLLS multiplier +#define SC_PLLS_CTRL2 0x303c // Bit[6:4]: PLLS charge pump control + // Bit[3:0]: PLLS system divider +#define SC_PLLS_CTRL3 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 + +#define COMPRESSION_CTRL00 0x4400 // +#define COMPRESSION_CTRL01 0x4401 // +#define COMPRESSION_CTRL02 0x4402 // +#define COMPRESSION_CTRL03 0x4403 // +#define COMPRESSION_CTRL04 0x4404 // +#define COMPRESSION_CTRL05 0x4405 // +#define COMPRESSION_CTRL06 0x4406 // +#define COMPRESSION_CTRL07 0x3401 // Bit[5:0]: QS +#define COMPRESSION_ISI_CTRL 0x4408 // +#define COMPRESSION_CTRL09 0x4409 // +#define COMPRESSION_CTRL0a 0x440a // +#define COMPRESSION_CTRL0b 0x440b // +#define COMPRESSION_CTRL0c 0x440c // +#define COMPRESSION_CTRL0d 0x440d // +#define COMPRESSION_CTRL0E 0x440e // + +/** + * @brief register value + */ +#define TEST_COLOR_BAR 0x02 /* Enable Color Bar roling Test */ + +#define AEC_PK_MANUAL_AGC_MANUALEN 0x02 /* Enable AGC Manual enable */ +#define AEC_PK_MANUAL_AEC_MANUALEN 0x01 /* Enable AEC Manual enable */ + +#define TIMING_TC_REG20_VFLIP 0x01 /* Vertical flip enable */ +#define TIMING_TC_REG21_HMIRROR 0x02 /* Horizontal mirror enable */ + +#endif // __NT99141_REG_REGS_H__ diff --git a/esp32-cam/nt99141_settings.h b/esp32-cam/nt99141_settings.h new file mode 100644 index 0000000..1ffec20 --- /dev/null +++ b/esp32-cam/nt99141_settings.h @@ -0,0 +1,825 @@ +#ifndef _NT99141_SETTINGS_H_ +#define _NT99141_SETTINGS_H_ + +#include +#include +#include "esp_attr.h" +#include "nt99141_regs.h" + +static const ratio_settings_t ratio_table[] = { + // mw, mh, sx, sy, ex, ey, ox, oy, tx, ty + { 1280, 720, 0, 4, 1283, 723, 0, 4, 1660, 963 }, + +}; + +#define REG_DLY 0xffff +#define REGLIST_TAIL 0x0000 + +static const DRAM_ATTR uint16_t sensor_default_regs[][2] = { + //initial +{0x3021, 0x00}, +{REG_DLY, 100}, // delay 100ms +{0x3109, 0x04}, +{0x3040, 0x04}, +{0x3041, 0x02}, +{0x3042, 0xFF}, +{0x3043, 0x08}, +{0x3052, 0xE0}, +{0x305F, 0x33}, +{0x3100, 0x07}, +{0x3106, 0x03}, +{0x3105, 0x01}, +{0x3108, 0x05}, +{0x3110, 0x22}, +{0x3111, 0x57}, +{0x3112, 0x22}, +{0x3113, 0x55}, +{0x3114, 0x05}, +{0x3135, 0x00}, +{0x32F0, 0x01}, +{0x3290, 0x01}, +{0x3291, 0x80}, +{0x3296, 0x01}, +{0x3297, 0x73}, +{0x3250, 0x80}, +{0x3251, 0x03}, +{0x3252, 0xFF}, +{0x3253, 0x00}, +{0x3254, 0x03}, +{0x3255, 0xFF}, +{0x3256, 0x00}, +{0x3257, 0x50}, +{0x3270, 0x00}, +{0x3271, 0x0C}, +{0x3272, 0x18}, +{0x3273, 0x32}, +{0x3274, 0x44}, +{0x3275, 0x54}, +{0x3276, 0x70}, +{0x3277, 0x88}, +{0x3278, 0x9D}, +{0x3279, 0xB0}, +{0x327A, 0xCF}, +{0x327B, 0xE2}, +{0x327C, 0xEF}, +{0x327D, 0xF7}, +{0x327E, 0xFF}, +{0x3302, 0x00}, +{0x3303, 0x40}, +{0x3304, 0x00}, +{0x3305, 0x96}, +{0x3306, 0x00}, +{0x3307, 0x29}, +{0x3308, 0x07}, +{0x3309, 0xBA}, +{0x330A, 0x06}, +{0x330B, 0xF5}, +{0x330C, 0x01}, +{0x330D, 0x51}, +{0x330E, 0x01}, +{0x330F, 0x30}, +{0x3310, 0x07}, +{0x3311, 0x16}, +{0x3312, 0x07}, +{0x3313, 0xBA}, +{0x3326, 0x02}, +{0x32F6, 0x0F}, +{0x32F9, 0x42}, +{0x32FA, 0x24}, +{0x3325, 0x4A}, +{0x3330, 0x00}, +{0x3331, 0x0A}, +{0x3332, 0xFF}, +{0x3338, 0x30}, +{0x3339, 0x84}, +{0x333A, 0x48}, +{0x333F, 0x07}, +{0x3360, 0x10}, +{0x3361, 0x18}, +{0x3362, 0x1f}, +{0x3363, 0x37}, +{0x3364, 0x80}, +{0x3365, 0x80}, +{0x3366, 0x68}, +{0x3367, 0x60}, +{0x3368, 0x30}, +{0x3369, 0x28}, +{0x336A, 0x20}, +{0x336B, 0x10}, +{0x336C, 0x00}, +{0x336D, 0x20}, +{0x336E, 0x1C}, +{0x336F, 0x18}, +{0x3370, 0x10}, +{0x3371, 0x38}, +{0x3372, 0x3C}, +{0x3373, 0x3F}, +{0x3374, 0x3F}, +{0x338A, 0x34}, +{0x338B, 0x7F}, +{0x338C, 0x10}, +{0x338D, 0x23}, +{0x338E, 0x7F}, +{0x338F, 0x14}, +{0x3375, 0x08}, +{0x3376, 0x0C}, +{0x3377, 0x18}, +{0x3378, 0x20}, +{0x3012, 0x02}, +{0x3013, 0xD0}, +{0x3025, 0x02}, //colorbar +{REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_fmt_jpeg[][2] = { + {0x32F0, 0x70}, // YUV422 + {REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_fmt_raw[][2] = { + {0x32F0, 0x50}, // RAW + {REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_fmt_grayscale[][2] = { + {0x32F1, 0x01}, + {REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_fmt_yuv422[][2] = { + {0x32F0, 0x00}, // YUV422 + {REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_fmt_rgb565[][2] = { + {0x32F0, 0x01}, // RGB + {REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint8_t sensor_saturation_levels[9][1] = { + {0x60},//-4 + {0x68},//-3 + {0x70},//-2 + {0x78},//-1 + {0x80},//0 + {0x88},//+1 + {0x90},//+2 + {0x98},//+3 + {0xA0},//+4 +}; + +static const DRAM_ATTR uint8_t sensor_special_effects[7][4] = { + {0x00, 0x80, 0x80, 0x01},//Normal + {0x03, 0x80, 0x80, 0x01},//Negative + {0x01, 0x80, 0x80, 0x01},//Grayscale + {0x05, 0x2A, 0xF0, 0x01},//Red Tint + {0x05, 0x60, 0x20, 0x01},//Green Tint + {0x05, 0xF0, 0x80, 0x01},//Blue Tint + {0x02, 0x80, 0x80, 0x01},//Sepia + +}; + +// AE LEVEL +static const DRAM_ATTR uint16_t sensor_ae_level[][2] = { + +// 1. [AE_Target : 0x24] +// Set_Device_Format = FORMAT_16_8 +// SET_Device_Addr = 0x54 + {0x32B8, 0x29 }, + {0x32B9, 0x1F }, + {0x32BC, 0x24 }, + {0x32BD, 0x27 }, + {0x32BE, 0x21 }, +//------------------------------------------------------------------------ +// 2. [AE_Target : 0x28] +// Set_Device_Format = FORMAT_16_8 +// SET_Device_Addr = 0x54 + {0x32B8, 0x2D }, + {0x32B9, 0x23 }, + {0x32BC, 0x28 }, + {0x32BD, 0x2B }, + {0x32BE, 0x25 }, +//------------------------------------------------------------------------ +// 3. [AE_Target : 0x2C] +// Set_Device_Format = FORMAT_16_8 +// SET_Device_Addr = 0x54 + {0x32B8, 0x32 }, + {0x32B9, 0x26 }, + {0x32BC, 0x2C }, + {0x32BD, 0x2F }, + {0x32BE, 0x29 }, +//------------------------------------------------------------------------ +// 4, [AE_Target : 0x30] +// Set_Device_Format = FORMAT_16_8 +// SET_Device_Addr = 0x54 + {0x32B8, 0x36 }, + {0x32B9, 0x2A }, + {0x32BC, 0x30 }, + {0x32BD, 0x33 }, + {0x32BE, 0x2D }, +//------------------------------------------------------------------------ +// 5. [AE_Target : 0x34] +// Set_Device_Format = FORMAT_16_8 +// SET_Device_Addr = 0x54 + {0x32B8, 0x3B }, + {0x32B9, 0x2D }, + {0x32BC, 0x34 }, + {0x32BD, 0x38 }, + {0x32BE, 0x30 }, +//------------------------------------------------------------------------ +// 6. [AE_Target : 0x38] +// Set_Device_Format = FORMAT_16_8 +// SET_Device_Addr = 0x54 + {0x32B8, 0x3F }, + {0x32B9, 0x31 }, + {0x32BC, 0x38 }, + {0x32BD, 0x3C }, + {0x32BE, 0x34 }, +//------------------------------------------------------------------------ +// 7. [AE_Target : 0x3D] +// Set_Device_Format = FORMAT_16_8 +// SET_Device_Addr = 0x54 + {0x32B8, 0x44 }, + {0x32B9, 0x34 }, + {0x32BC, 0x3C }, + {0x32BD, 0x40 }, + {0x32BE, 0x38 }, +//------------------------------------------------------------------------ +// 8. [AE_Target : 0x40] +// Set_Device_Format = FORMAT_16_8 +// SET_Device_Addr = 0x54 + {0x32B8, 0x48 }, + {0x32B9, 0x38 }, + {0x32BC, 0x40 }, + {0x32BD, 0x44 }, + {0x32BE, 0x3C }, +//------------------------------------------------------------------------ +// 9. [AE_Target : 0x44] +// Set_Device_Format = FORMAT_16_8 +// SET_Device_Addr = 0x54 + {0x32B8, 0x4D }, + {0x32B9, 0x3B }, + {0x32BC, 0x44 }, + {0x32BD, 0x49 }, + {0x32BE, 0x3F }, +}; + +static const DRAM_ATTR uint16_t sensor_framesize_HD[][2] = { +//[JPEG_1280x720_8.18_8.18_Fps] +{0x3021, 0x00}, +{REG_DLY, 100}, // delay 100ms +{0x32BF, 0x60}, +{0x32C0, 0x5A}, +{0x32C1, 0x5A}, +{0x32C2, 0x5A}, +{0x32C3, 0x00}, +{0x32C4, 0x20}, +{0x32C5, 0x20}, +{0x32C6, 0x20}, +{0x32C7, 0x00}, +{0x32C8, 0x3C}, +{0x32C9, 0x5A}, +{0x32CA, 0x7A}, +{0x32CB, 0x7A}, +{0x32CC, 0x7A}, +{0x32CD, 0x7A}, +{0x32DB, 0x5E}, +{0x32F0, 0x70}, +{0x3400, 0x08}, +{0x3400, 0x00}, +{0x3401, 0x4E}, +{0x3404, 0x00}, +{0x3405, 0x00}, +{0x3410, 0x00}, +{0x3200, 0x3E}, +{0x3201, 0x0F}, +{0x3028, 0x0F}, +{0x3029, 0x00}, +{0x302A, 0x08}, +{0x3022, 0x24}, +{0x3023, 0x24}, +{0x3002, 0x00}, +{0x3003, 0x04}, +{0x3004, 0x00}, +{0x3005, 0x04}, +{0x3006, 0x05}, +{0x3007, 0x03}, +{0x3008, 0x02}, +{0x3009, 0xD3}, +{0x300A, 0x06}, +{0x300B, 0x7C}, +{0x300C, 0x02}, +{0x300D, 0xE0}, +{0x300E, 0x05}, +{0x300F, 0x00}, +{0x3010, 0x02}, +{0x3011, 0xD0}, +{0x32B8, 0x3F}, +{0x32B9, 0x31}, +{0x32BB, 0x87}, +{0x32BC, 0x38}, +{0x32BD, 0x3C}, +{0x32BE, 0x34}, +{0x3201, 0x3F}, +{0x3021, 0x06}, +{0x3025, 0x00}, //normal +{0x3400, 0x01}, +{0x3060, 0x01}, +{REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_framesize_VGA[][2] = { +//[JPEG_640x480_10.14_10.14_Fps] +{0x3021, 0x00}, +{REG_DLY, 100}, // delay 100ms +{0x32BF, 0x60}, +{0x32C0, 0x5A}, +{0x32C1, 0x5A}, +{0x32C2, 0x5A}, +{0x32C3, 0x00}, +{0x32C4, 0x20}, +{0x32C5, 0x20}, +{0x32C6, 0x20}, +{0x32C7, 0x00}, +{0x32C8, 0x4B}, +{0x32C9, 0x5A}, +{0x32CA, 0x7A}, +{0x32CB, 0x7A}, +{0x32CC, 0x7A}, +{0x32CD, 0x7A}, +{0x32DB, 0x62}, +{0x32F0, 0x70}, +{0x3400, 0x08}, +{0x3400, 0x00}, +{0x3401, 0x4E}, +{0x3404, 0x00}, +{0x3405, 0x00}, +{0x3410, 0x00}, +{0x32E0, 0x02}, +{0x32E1, 0x80}, +{0x32E2, 0x01}, +{0x32E3, 0xE0}, +{0x32E4, 0x00}, +{0x32E5, 0x80}, +{0x32E6, 0x00}, +{0x32E7, 0x80}, +{0x3200, 0x3E}, +{0x3201, 0x0F}, +{0x3028, 0x0F}, +{0x3029, 0x00}, +{0x302A, 0x08}, +{0x3022, 0x24}, +{0x3023, 0x24}, +{0x3002, 0x00}, +{0x3003, 0xA4}, +{0x3004, 0x00}, +{0x3005, 0x04}, +{0x3006, 0x04}, +{0x3007, 0x63}, +{0x3008, 0x02}, +{0x3009, 0xD3}, +{0x300A, 0x05}, +{0x300B, 0x3C}, +{0x300C, 0x02}, +{0x300D, 0xE0}, +{0x300E, 0x03}, +{0x300F, 0xC0}, +{0x3010, 0x02}, +{0x3011, 0xD0}, +{0x32B8, 0x3F}, +{0x32B9, 0x31}, +{0x32BB, 0x87}, +{0x32BC, 0x38}, +{0x32BD, 0x3C}, +{0x32BE, 0x34}, +{0x3201, 0x7F}, +{0x3021, 0x06}, +{0x3025, 0x00}, //normal +{0x3400, 0x01}, +{0x3060, 0x01}, +{REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_framesize_QVGA[][2] = { +//[JPEG_320x240_10.14_10.14_Fps] +{0x3021, 0x00}, +{REG_DLY, 100}, // delay 100ms +{0x32BF, 0x60}, +{0x32C0, 0x5A}, +{0x32C1, 0x5A}, +{0x32C2, 0x5A}, +{0x32C3, 0x00}, +{0x32C4, 0x20}, +{0x32C5, 0x20}, +{0x32C6, 0x20}, +{0x32C7, 0x00}, +{0x32C8, 0x4B}, +{0x32C9, 0x5A}, +{0x32CA, 0x7A}, +{0x32CB, 0x7A}, +{0x32CC, 0x7A}, +{0x32CD, 0x7A}, +{0x32DB, 0x62}, +{0x32F0, 0x70}, +{0x3400, 0x08}, +{0x3400, 0x00}, +{0x3401, 0x4E}, +{0x3404, 0x00}, +{0x3405, 0x00}, +{0x3410, 0x00}, +{0x32E0, 0x01}, +{0x32E1, 0x40}, +{0x32E2, 0x00}, +{0x32E3, 0xF0}, +{0x32E4, 0x02}, +{0x32E5, 0x02}, +{0x32E6, 0x02}, +{0x32E7, 0x03}, +{0x3200, 0x3E}, +{0x3201, 0x0F}, +{0x3028, 0x0F}, +{0x3029, 0x00}, +{0x302A, 0x08}, +{0x3022, 0x24}, +{0x3023, 0x24}, +{0x3002, 0x00}, +{0x3003, 0xA4}, +{0x3004, 0x00}, +{0x3005, 0x04}, +{0x3006, 0x04}, +{0x3007, 0x63}, +{0x3008, 0x02}, +{0x3009, 0xD3}, +{0x300A, 0x05}, +{0x300B, 0x3C}, +{0x300C, 0x02}, +{0x300D, 0xE0}, +{0x300E, 0x03}, +{0x300F, 0xC0}, +{0x3010, 0x02}, +{0x3011, 0xD0}, +{0x32B8, 0x3F}, +{0x32B9, 0x31}, +{0x32BB, 0x87}, +{0x32BC, 0x38}, +{0x32BD, 0x3C}, +{0x32BE, 0x34}, +{0x3201, 0x7F}, +{0x3021, 0x06}, +{0x3025, 0x00}, //normal +{0x3400, 0x01}, +{0x3060, 0x01}, +{REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_framesize_VGA_xyskip[][2] = { +// [JPEG_640x360_20.00_25.01_Fps_XY_Skip] +// Set_Device_Format = FORMAT_16_8 +// SET_Device_Addr = 0x54 +{0x3021, 0x00}, +{REG_DLY, 100}, // delay 100ms +{0x32BF, 0x60 }, +{0x320A, 0xB2 }, +{0x32C0, 0x64 }, +{0x32C1, 0x64 }, +{0x32C2, 0x64 }, +{0x32C3, 0x00 }, +{0x32C4, 0x20 }, +{0x32C5, 0x20 }, +{0x32C6, 0x20 }, +{0x32C7, 0x00 }, +{0x32C8, 0x62 }, +{0x32C9, 0x64 }, +{0x32CA, 0x84 }, +{0x32CB, 0x84 }, +{0x32CC, 0x84 }, +{0x32CD, 0x84 }, +{0x32DB, 0x68 }, +{0x32F0, 0x70 }, +{0x3400, 0x08 }, +{0x3400, 0x00 }, +{0x3401, 0x4E }, +{0x3404, 0x00 }, +{0x3405, 0x00 }, +{0x3410, 0x00 }, +{0x3200, 0x3E }, +{0x3201, 0x0F }, +{0x3028, 0x0F }, +{0x3029, 0x00 }, +{0x302A, 0x08 }, +{0x3022, 0x24 }, +{0x3023, 0x6C }, +{0x3002, 0x00 }, +{0x3003, 0x04 }, +{0x3004, 0x00 }, +{0x3005, 0x04 }, +{0x3006, 0x05 }, +{0x3007, 0x03 }, +{0x3008, 0x02 }, +{0x3009, 0xD3 }, +{0x300A, 0x03 }, +{0x300B, 0xFC }, +{0x300C, 0x01 }, +{0x300D, 0x88 }, +{0x300E, 0x02 }, +{0x300F, 0x80 }, +{0x3010, 0x01 }, +{0x3011, 0x68 }, +{0x32B8, 0x3F }, +{0x32B9, 0x31 }, +{0x32BB, 0x87 }, +{0x32BC, 0x38 }, +{0x32BD, 0x3C }, +{0x32BE, 0x34 }, +{0x3201, 0x3F }, +{0x3025, 0x00 }, //normal +{0x3021, 0x06 }, +{0x3400, 0x01 }, +{0x3060, 0x01 }, +{REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_framesize_VGA_xskip[][2] = { +//[JPEG_640x480_Xskip_13.32_13.32_Fps] +{0x3021, 0x00}, +{REG_DLY, 100}, // delay 100ms +{0x32BF, 0x60}, +{0x32C0, 0x5A}, +{0x32C1, 0x5A}, +{0x32C2, 0x5A}, +{0x32C3, 0x00}, +{0x32C4, 0x20}, +{0x32C5, 0x20}, +{0x32C6, 0x20}, +{0x32C7, 0x00}, +{0x32C8, 0x62}, +{0x32C9, 0x5A}, +{0x32CA, 0x7A}, +{0x32CB, 0x7A}, +{0x32CC, 0x7A}, +{0x32CD, 0x7A}, +{0x32DB, 0x68}, +{0x32F0, 0x70}, +{0x3400, 0x08}, +{0x3400, 0x00}, +{0x3401, 0x4E}, +{0x3404, 0x00}, +{0x3405, 0x00}, +{0x3410, 0x00}, +{0x32E0, 0x02}, +{0x32E1, 0x80}, +{0x32E2, 0x01}, +{0x32E3, 0xE0}, +{0x32E4, 0x00}, +{0x32E5, 0x00}, +{0x32E6, 0x00}, +{0x32E7, 0x80}, +{0x3200, 0x3E}, +{0x3201, 0x0F}, +{0x3028, 0x0F}, +{0x3029, 0x00}, +{0x302A, 0x08}, +{0x3022, 0x24}, +{0x3023, 0x2C}, +{0x3002, 0x00}, +{0x3003, 0x04}, +{0x3004, 0x00}, +{0x3005, 0x04}, +{0x3006, 0x05}, +{0x3007, 0x03}, +{0x3008, 0x02}, +{0x3009, 0xD3}, +{0x300A, 0x03}, +{0x300B, 0xFC}, +{0x300C, 0x02}, +{0x300D, 0xE0}, +{0x300E, 0x02}, +{0x300F, 0x80}, +{0x3010, 0x02}, +{0x3011, 0xD0}, +{0x32B8, 0x3F}, +{0x32B9, 0x31}, +{0x32BB, 0x87}, +{0x32BC, 0x38}, +{0x32BD, 0x3C}, +{0x32BE, 0x34}, +{0x3201, 0x7F}, +{0x3021, 0x06}, +{0x3025, 0x00}, //normal +{0x3400, 0x01}, +{0x3060, 0x01}, +{REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_framesize_QVGA_xskip[][2] = { +{0x3021, 0x00}, +{REG_DLY, 100}, // delay 100ms +//[JPEG_320x240_Xskip_13.32_13.32_Fps] +{0x32BF, 0x60}, +{0x32C0, 0x5A}, +{0x32C1, 0x5A}, +{0x32C2, 0x5A}, +{0x32C3, 0x00}, +{0x32C4, 0x20}, +{0x32C5, 0x20}, +{0x32C6, 0x20}, +{0x32C7, 0x00}, +{0x32C8, 0x62}, +{0x32C9, 0x5A}, +{0x32CA, 0x7A}, +{0x32CB, 0x7A}, +{0x32CC, 0x7A}, +{0x32CD, 0x7A}, +{0x32DB, 0x68}, +{0x32F0, 0x70}, +{0x3400, 0x08}, +{0x3400, 0x00}, +{0x3401, 0x4E}, +{0x3404, 0x00}, +{0x3405, 0x00}, +{0x3410, 0x00}, +{0x32E0, 0x01}, +{0x32E1, 0x40}, +{0x32E2, 0x00}, +{0x32E3, 0xF0}, +{0x32E4, 0x01}, +{0x32E5, 0x01}, +{0x32E6, 0x02}, +{0x32E7, 0x03}, +{0x3200, 0x3E}, +{0x3201, 0x0F}, +{0x3028, 0x0F}, +{0x3029, 0x00}, +{0x302A, 0x08}, +{0x3022, 0x24}, +{0x3023, 0x2C}, +{0x3002, 0x00}, +{0x3003, 0x04}, +{0x3004, 0x00}, +{0x3005, 0x04}, +{0x3006, 0x05}, +{0x3007, 0x03}, +{0x3008, 0x02}, +{0x3009, 0xD3}, +{0x300A, 0x03}, +{0x300B, 0xFC}, +{0x300C, 0x02}, +{0x300D, 0xE0}, +{0x300E, 0x02}, +{0x300F, 0x80}, +{0x3010, 0x02}, +{0x3011, 0xD0}, +{0x32B8, 0x3F}, +{0x32B9, 0x31}, +{0x32BB, 0x87}, +{0x32BC, 0x38}, +{0x32BD, 0x3C}, +{0x32BE, 0x34}, +{0x3201, 0x7F}, +{0x3021, 0x06}, +{0x3025, 0x00}, //normal +{0x3400, 0x01}, +{0x3060, 0x01}, +{REGLIST_TAIL, 0x00}, // tail +}; + + +static const DRAM_ATTR uint16_t sensor_framesize_VGA_crop[][2] = { +//[JPEG_640x480_Crop_19.77_19.77_Fps] +{0x3021, 0x00}, +{REG_DLY, 100}, // delay 100ms +{0x32BF, 0x60}, +{0x32C0, 0x5A}, +{0x32C1, 0x5A}, +{0x32C2, 0x5A}, +{0x32C3, 0x00}, +{0x32C4, 0x20}, +{0x32C5, 0x20}, +{0x32C6, 0x20}, +{0x32C7, 0x00}, +{0x32C8, 0x62}, +{0x32C9, 0x5A}, +{0x32CA, 0x7A}, +{0x32CB, 0x7A}, +{0x32CC, 0x7A}, +{0x32CD, 0x7A}, +{0x32DB, 0x68}, +{0x32F0, 0x70}, +{0x3400, 0x08}, +{0x3400, 0x00}, +{0x3401, 0x4E}, +{0x3404, 0x00}, +{0x3405, 0x00}, +{0x3410, 0x00}, +{0x3200, 0x3E}, +{0x3201, 0x0F}, +{0x3028, 0x0F}, +{0x3029, 0x00}, +{0x302A, 0x08}, +{0x3022, 0x24}, +{0x3023, 0x24}, +{0x3002, 0x01}, +{0x3003, 0x44}, +{0x3004, 0x00}, +{0x3005, 0x7C}, +{0x3006, 0x03}, +{0x3007, 0xC3}, +{0x3008, 0x02}, +{0x3009, 0x5B}, +{0x300A, 0x03}, +{0x300B, 0xFC}, +{0x300C, 0x01}, +{0x300D, 0xF0}, +{0x300E, 0x02}, +{0x300F, 0x80}, +{0x3010, 0x01}, +{0x3011, 0xE0}, +{0x32B8, 0x3F}, +{0x32B9, 0x31}, +{0x32BB, 0x87}, +{0x32BC, 0x38}, +{0x32BD, 0x3C}, +{0x32BE, 0x34}, +{0x3201, 0x3F}, +{0x3021, 0x06}, +{0x3025, 0x00}, //normal +{0x3400, 0x01}, +{0x3060, 0x01}, +{REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_framesize_QVGA_crop[][2] = { +//[JPEG_320x240_Crop_19.77_19.77_Fps] +{0x3021, 0x00}, +{REG_DLY, 100}, // delay 100ms +{0x32BF, 0x60}, +{0x32C0, 0x5A}, +{0x32C1, 0x5A}, +{0x32C2, 0x5A}, +{0x32C3, 0x00}, +{0x32C4, 0x20}, +{0x32C5, 0x20}, +{0x32C6, 0x20}, +{0x32C7, 0x00}, +{0x32C8, 0x62}, +{0x32C9, 0x5A}, +{0x32CA, 0x7A}, +{0x32CB, 0x7A}, +{0x32CC, 0x7A}, +{0x32CD, 0x7A}, +{0x32DB, 0x68}, +{0x32F0, 0x70}, +{0x3400, 0x08}, +{0x3400, 0x00}, +{0x3401, 0x4E}, +{0x3404, 0x00}, +{0x3405, 0x00}, +{0x3410, 0x00}, +{0x32E0, 0x01}, +{0x32E1, 0x40}, +{0x32E2, 0x00}, +{0x32E3, 0xF0}, +{0x32E4, 0x01}, +{0x32E5, 0x01}, +{0x32E6, 0x01}, +{0x32E7, 0x02}, +{0x3200, 0x3E}, +{0x3201, 0x0F}, +{0x3028, 0x0F}, +{0x3029, 0x00}, +{0x302A, 0x08}, +{0x3022, 0x24}, +{0x3023, 0x24}, +{0x3002, 0x01}, +{0x3003, 0x44}, +{0x3004, 0x00}, +{0x3005, 0x7C}, +{0x3006, 0x03}, +{0x3007, 0xC3}, +{0x3008, 0x02}, +{0x3009, 0x5B}, +{0x300A, 0x03}, +{0x300B, 0xFC}, +{0x300C, 0x01}, +{0x300D, 0xF0}, +{0x300E, 0x02}, +{0x300F, 0x80}, +{0x3010, 0x01}, +{0x3011, 0xE0}, +{0x32B8, 0x3F}, +{0x32B9, 0x31}, +{0x32BB, 0x87}, +{0x32BC, 0x38}, +{0x32BD, 0x3C}, +{0x32BE, 0x34}, +{0x3201, 0x7F}, +{0x3021, 0x06}, +{0x3025, 0x00}, //normal +{0x3400, 0x01}, +{0x3060, 0x01}, +{REGLIST_TAIL, 0x00}, // tail +}; + +#endif + + diff --git a/esp32-cam/ov7670.c b/esp32-cam/ov7670.c new file mode 100644 index 0000000..285fe13 --- /dev/null +++ b/esp32-cam/ov7670.c @@ -0,0 +1,439 @@ +/* + * This file is part of the OpenMV project. + * author: Juan Schiavoni + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * OV7725 driver. + * + */ +#include +#include +#include +#include "sccb.h" +#include "ov7670.h" +#include "ov7670_regs.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#else +#include "esp_log.h" +static const char* TAG = "ov7760"; +#endif + +static int ov7670_clkrc = 0x01; + +/* + * The default register settings, as obtained from OmniVision. There + * is really no making sense of most of these - lots of "reserved" values + * and such. + * + * These settings give VGA YUYV. + */ +struct regval_list { + uint8_t reg_num; + uint8_t value; +}; + +static struct regval_list ov7670_default_regs[] = { + /* Sensor automatically sets output window when resolution changes. */ + {TSLB, 0x04}, + + /* Frame rate 30 fps at 12 Mhz clock */ + {CLKRC, 0x00}, + {DBLV, 0x4A}, + + {COM10, COM10_VSYNC_NEG | COM10_PCLK_MASK}, + + /* Improve white balance */ + {COM4, 0x40}, + + /* Improve color */ + {RSVD_B0, 0x84}, + + /* Enable 50/60 Hz auto detection */ + {COM11, COM11_EXP|COM11_HZAUTO}, + + /* Disable some delays */ + {HSYST, 0}, + {HSYEN, 0}, + + {MVFP, MVFP_SUN}, + + /* More reserved magic, some of which tweaks white balance */ + {AWBC1, 0x0a}, + {AWBC2, 0xf0}, + {AWBC3, 0x34}, + {AWBC4, 0x58}, + {AWBC5, 0x28}, + {AWBC6, 0x3a}, + + {AWBCTR3, 0x0a}, + {AWBCTR2, 0x55}, + {AWBCTR1, 0x11}, + {AWBCTR0, 0x9e}, + + {COM8, COM8_FAST_AUTO|COM8_STEP_UNLIMIT|COM8_AGC_EN|COM8_AEC_EN|COM8_AWB_EN}, + + /* End marker is FF because in ov7670 the address of GAIN 0 and default value too. */ + {0xFF, 0xFF}, +}; + +static struct regval_list ov7670_fmt_yuv422[] = { + { COM7, 0x0 }, /* Selects YUV mode */ + { RGB444, 0 }, /* No RGB444 please */ + { COM1, 0 }, /* CCIR601 */ + { COM15, COM15_R00FF }, + { MVFP, MVFP_SUN }, + { COM9, 0x6A }, /* 128x gain ceiling; 0x8 is reserved bit */ + { MTX1, 0x80 }, /* "matrix coefficient 1" */ + { MTX2, 0x80 }, /* "matrix coefficient 2" */ + { MTX3, 0 }, /* vb */ + { MTX4, 0x22 }, /* "matrix coefficient 4" */ + { MTX5, 0x5e }, /* "matrix coefficient 5" */ + { MTX6, 0x80 }, /* "matrix coefficient 6" */ + { COM13, COM13_UVSAT }, + { 0xff, 0xff }, /* END MARKER */ +}; + +static struct regval_list ov7670_fmt_rgb565[] = { + { COM7, COM7_FMT_RGB565 }, /* Selects RGB mode */ + { RGB444, 0 }, /* No RGB444 please */ + { COM1, 0x0 }, /* CCIR601 */ + { COM15, COM15_RGB565 |COM15_R00FF }, + { MVFP, MVFP_SUN }, + { COM9, 0x6A }, /* 128x gain ceiling; 0x8 is reserved bit */ + { MTX1, 0xb3 }, /* "matrix coefficient 1" */ + { MTX2, 0xb3 }, /* "matrix coefficient 2" */ + { MTX3, 0 }, /* vb */ + { MTX4, 0x3d }, /* "matrix coefficient 4" */ + { MTX5, 0xa7 }, /* "matrix coefficient 5" */ + { MTX6, 0xe4 }, /* "matrix coefficient 6" */ + { COM13, COM13_UVSAT }, + { 0xff, 0xff }, /* END MARKER */ +}; + + +static struct regval_list ov7670_vga[] = { + { COM3, 0x00 }, + { COM14, 0x00 }, + { SCALING_XSC, 0x3A }, + { SCALING_YSC, 0x35 }, + { SCALING_DCWCTR, 0x11 }, + { SCALING_PCLK_DIV, 0xF0 }, + { SCALING_PCLK_DELAY, 0x02 }, + { 0xff, 0xff }, +}; + +static struct regval_list ov7670_qvga[] = { + { COM3, 0x04 }, + { COM14, 0x19 }, + { SCALING_XSC, 0x3A }, + { SCALING_YSC, 0x35 }, + { SCALING_DCWCTR, 0x11 }, + { SCALING_PCLK_DIV, 0xF1 }, + { SCALING_PCLK_DELAY, 0x02 }, + { 0xff, 0xff }, +}; + +static struct regval_list ov7670_qqvga[] = { + { COM3, 0x04 }, //DCW enable + { COM14, 0x1a }, //pixel clock divided by 4, manual scaling enable, DCW and PCLK controlled by register + { SCALING_XSC, 0x3a }, + { SCALING_YSC, 0x35 }, + { SCALING_DCWCTR, 0x22 }, //downsample by 4 + { SCALING_PCLK_DIV, 0xf2 }, //pixel clock divided by 4 + { SCALING_PCLK_DELAY, 0x02 }, + { 0xff, 0xff }, +}; + +/* + * Write a list of register settings; ff/ff stops the process. + */ +static int ov7670_write_array(sensor_t *sensor, struct regval_list *vals) +{ +int ret = 0; + + while ( (vals->reg_num != 0xff || vals->value != 0xff) && (ret == 0) ) { + ret = SCCB_Write(sensor->slv_addr, vals->reg_num, vals->value); + + ESP_LOGD(TAG, "reset reg %02X, W(%02X) R(%02X)", vals->reg_num, + vals->value, SCCB_Read(sensor->slv_addr, vals->reg_num) ); + + vals++; + } + + return ret; +} + +/* + * Calculate the frame control registers. + */ +static int ov7670_frame_control(sensor_t *sensor, int hstart, int hstop, int vstart, int vstop) +{ +struct regval_list frame[7]; + + frame[0].reg_num = HSTART; + frame[0].value = (hstart >> 3); + + frame[1].reg_num = HSTOP; + frame[1].value = (hstop >> 3); + + frame[2].reg_num = HREF; + frame[2].value = (((hstop & 0x07) << 3) | (hstart & 0x07)); + + frame[3].reg_num = VSTART; + frame[3].value = (vstart >> 2); + + frame[4].reg_num = VSTOP; + frame[4].value = (vstop >> 2); + + frame[5].reg_num = VREF; + frame[5].value = (((vstop & 0x02) << 2) | (vstart & 0x02)); + + /* End mark */ + frame[5].reg_num = 0xFF; + frame[5].value = 0xFF; + + return ov7670_write_array(sensor, frame); +} + +static int reset(sensor_t *sensor) +{ + int ret; + + // Reset all registers + SCCB_Write(sensor->slv_addr, COM7, COM7_RESET); + + // Delay 10 ms + vTaskDelay(10 / portTICK_PERIOD_MS); + + ret = ov7670_write_array(sensor, ov7670_default_regs); + + // Delay + vTaskDelay(30 / portTICK_PERIOD_MS); + + return ret; +} + +static int set_pixformat(sensor_t *sensor, pixformat_t pixformat) +{ +int ret; + + switch (pixformat) { + case PIXFORMAT_RGB565: + case PIXFORMAT_RGB888: + ret = ov7670_write_array(sensor, ov7670_fmt_rgb565); + break; + + case PIXFORMAT_YUV422: + case PIXFORMAT_GRAYSCALE: + default: + ret = ov7670_write_array(sensor, ov7670_fmt_yuv422); + break; + } + + vTaskDelay(30 / portTICK_PERIOD_MS); + + /* + * If we're running RGB565, we must rewrite clkrc after setting + * the other parameters or the image looks poor. If we're *not* + * doing RGB565, we must not rewrite clkrc or the image looks + * *really* poor. + * + * (Update) Now that we retain clkrc state, we should be able + * to write it unconditionally, and that will make the frame + * rate persistent too. + */ + if (pixformat == PIXFORMAT_RGB565) { + ret = SCCB_Write(sensor->slv_addr, CLKRC, ov7670_clkrc); + } + + return ret; +} + +static int set_framesize(sensor_t *sensor, framesize_t framesize) +{ + int ret; + + // store clkrc before changing window settings... + ov7670_clkrc = SCCB_Read(sensor->slv_addr, CLKRC); + + switch (framesize){ + case FRAMESIZE_VGA: + if( (ret = ov7670_write_array(sensor, ov7670_vga)) == 0 ) { + /* These values from Omnivision */ + ret = ov7670_frame_control(sensor, 158, 14, 10, 490); + } + break; + case FRAMESIZE_QVGA: + if( (ret = ov7670_write_array(sensor, ov7670_qvga)) == 0 ) { + /* These values from Omnivision */ + ret = ov7670_frame_control(sensor, 158, 14, 10, 490); + } + break; + case FRAMESIZE_QQVGA: + if( (ret = ov7670_write_array(sensor, ov7670_qqvga)) == 0 ) { + /* These values from Omnivision */ + ret = ov7670_frame_control(sensor, 158, 14, 10, 490); + } + break; + + default: + ret = -1; + } + + vTaskDelay(30 / portTICK_PERIOD_MS); + + if (ret == 0) { + sensor->status.framesize = framesize; + } + + return ret; +} + +static int set_colorbar(sensor_t *sensor, int enable) +{ + uint8_t ret = 0; + // Read register scaling_xsc + uint8_t reg = SCCB_Read(sensor->slv_addr, SCALING_XSC); + + // Pattern to set color bar bit[0]=0 in every case + reg = SCALING_XSC_CBAR(reg); + + // Write pattern to SCALING_XSC + ret = SCCB_Write(sensor->slv_addr, SCALING_XSC, reg); + + // Read register scaling_ysc + reg = SCCB_Read(sensor->slv_addr, SCALING_YSC); + + // Pattern to set color bar bit[0]=0 in every case + reg = SCALING_YSC_CBAR(reg, enable); + + // Write pattern to SCALING_YSC + ret = ret | SCCB_Write(sensor->slv_addr, SCALING_YSC, reg); + + // return 0 or 0xFF + return ret; +} + +static int set_whitebal(sensor_t *sensor, int enable) +{ + // Read register COM8 + uint8_t reg = SCCB_Read(sensor->slv_addr, COM8); + + // Set white bal on/off + reg = COM8_SET_AWB(reg, enable); + + // Write back register COM8 + return SCCB_Write(sensor->slv_addr, COM8, reg); +} + +static int set_gain_ctrl(sensor_t *sensor, int enable) +{ + // Read register COM8 + uint8_t reg = SCCB_Read(sensor->slv_addr, COM8); + + // Set white bal on/off + reg = COM8_SET_AGC(reg, enable); + + // Write back register COM8 + return SCCB_Write(sensor->slv_addr, COM8, reg); +} + +static int set_exposure_ctrl(sensor_t *sensor, int enable) +{ + // Read register COM8 + uint8_t reg = SCCB_Read(sensor->slv_addr, COM8); + + // Set white bal on/off + reg = COM8_SET_AEC(reg, enable); + + // Write back register COM8 + return SCCB_Write(sensor->slv_addr, COM8, reg); +} + +static int set_hmirror(sensor_t *sensor, int enable) +{ + // Read register MVFP + uint8_t reg = SCCB_Read(sensor->slv_addr, MVFP); + + // Set mirror on/off + reg = MVFP_SET_MIRROR(reg, enable); + + // Write back register MVFP + return SCCB_Write(sensor->slv_addr, MVFP, reg); +} + +static int set_vflip(sensor_t *sensor, int enable) +{ + // Read register MVFP + uint8_t reg = SCCB_Read(sensor->slv_addr, MVFP); + + // Set mirror on/off + reg = MVFP_SET_FLIP(reg, enable); + + // Write back register MVFP + return SCCB_Write(sensor->slv_addr, MVFP, reg); +} + +static int init_status(sensor_t *sensor) +{ + sensor->status.awb = 0; + sensor->status.aec = 0; + sensor->status.agc = 0; + sensor->status.hmirror = 0; + sensor->status.vflip = 0; + sensor->status.colorbar = 0; + return 0; +} + +static int set_dummy(sensor_t *sensor, int val){ return -1; } +static int set_gainceiling_dummy(sensor_t *sensor, gainceiling_t val){ return -1; } + +int ov7670_init(sensor_t *sensor) +{ + // Set function pointers + sensor->reset = reset; + sensor->init_status = init_status; + sensor->set_pixformat = set_pixformat; + sensor->set_framesize = set_framesize; + sensor->set_colorbar = set_colorbar; + sensor->set_whitebal = set_whitebal; + sensor->set_gain_ctrl = set_gain_ctrl; + sensor->set_exposure_ctrl = set_exposure_ctrl; + sensor->set_hmirror = set_hmirror; + sensor->set_vflip = set_vflip; + + //not supported + sensor->set_brightness= set_dummy; + sensor->set_saturation= set_dummy; + sensor->set_quality = set_dummy; + sensor->set_gainceiling = set_gainceiling_dummy; + sensor->set_aec2 = set_dummy; + sensor->set_aec_value = set_dummy; + sensor->set_special_effect = set_dummy; + sensor->set_wb_mode = set_dummy; + sensor->set_ae_level = set_dummy; + sensor->set_dcw = set_dummy; + sensor->set_bpc = set_dummy; + sensor->set_wpc = set_dummy; + sensor->set_awb_gain = set_dummy; + sensor->set_agc_gain = set_dummy; + sensor->set_raw_gma = set_dummy; + sensor->set_lenc = set_dummy; + sensor->set_sharpness = set_dummy; + sensor->set_denoise = set_dummy; + + // Retrieve sensor's signature + sensor->id.MIDH = SCCB_Read(sensor->slv_addr, REG_MIDH); + sensor->id.MIDL = SCCB_Read(sensor->slv_addr, REG_MIDL); + sensor->id.PID = SCCB_Read(sensor->slv_addr, REG_PID); + sensor->id.VER = SCCB_Read(sensor->slv_addr, REG_VER); + + ESP_LOGD(TAG, "OV7670 Attached"); + + return 0; +} diff --git a/esp32-cam/ov7670.h b/esp32-cam/ov7670.h new file mode 100644 index 0000000..cdf845c --- /dev/null +++ b/esp32-cam/ov7670.h @@ -0,0 +1,14 @@ +/* + * This file is part of the OpenMV project. + * author: Juan Schiavoni + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * OV7670 driver. + * + */ +#ifndef __OV7670_H__ +#define __OV7670_H__ +#include "sensor.h" + +int ov7670_init(sensor_t *sensor); +#endif // __OV7670_H__ diff --git a/esp32-cam/ov7670_regs.h b/esp32-cam/ov7670_regs.h new file mode 100644 index 0000000..6993548 --- /dev/null +++ b/esp32-cam/ov7670_regs.h @@ -0,0 +1,354 @@ +/* + * This file is for the OpenMV project so the OV7670 can be used + * author: Juan Schiavoni + * + * OV7670 register definitions. + */ +#ifndef __OV7670_REG_REGS_H__ +#define __OV7670_REG_REGS_H__ +#define GAIN 0x00 /* AGC – Gain control gain setting */ +#define BLUE 0x01 /* AWB – Blue channel gain setting */ +#define RED 0x02 /* AWB – Red channel gain setting */ +#define VREF 0x03 /* AWB – Green channel gain setting */ +#define COM1 0x04 /* Common Control 1 */ +#define BAVG 0x05 /* U/B Average Level */ +#define GAVG 0x06 /* Y/Gb Average Level */ +#define AECH 0x07 /* Exposure VAlue - AEC MSB 5 bits */ +#define RAVG 0x08 /* V/R Average Level */ + +#define COM2 0x09 /* Common Control 2 */ +#define COM2_SOFT_SLEEP 0x10 /* Soft sleep mode */ +#define COM2_OUT_DRIVE_1x 0x00 /* Output drive capability 1x */ +#define COM2_OUT_DRIVE_2x 0x01 /* Output drive capability 2x */ +#define COM2_OUT_DRIVE_3x 0x02 /* Output drive capability 3x */ +#define COM2_OUT_DRIVE_4x 0x03 /* Output drive capability 4x */ + +#define REG_PID 0x0A /* Product ID Number MSB */ +#define REG_VER 0x0B /* Product ID Number LSB */ + +#define COM3 0x0C /* Common Control 3 */ +#define COM3_SWAP_OUT 0x40 /* Output data MSB/LSB swap */ +#define COM3_TRI_CLK 0x20 /* Tri-state output clock */ +#define COM3_TRI_DATA 0x10 /* Tri-state option output */ +#define COM3_SCALE_EN 0x08 /* Scale enable */ +#define COM3_DCW 0x04 /* DCW enable */ + +#define COM4 0x0D /* Common Control 4 */ +#define COM4_PLL_BYPASS 0x00 /* Bypass PLL */ +#define COM4_PLL_4x 0x40 /* PLL frequency 4x */ +#define COM4_PLL_6x 0x80 /* PLL frequency 6x */ +#define COM4_PLL_8x 0xc0 /* PLL frequency 8x */ +#define COM4_AEC_FULL 0x00 /* AEC evaluate full window */ +#define COM4_AEC_1_2 0x10 /* AEC evaluate 1/2 window */ +#define COM4_AEC_1_4 0x20 /* AEC evaluate 1/4 window */ +#define COM4_AEC_2_3 0x30 /* AEC evaluate 2/3 window */ + +#define COM5 0x0E /* Common Control 5 */ +#define COM5_AFR 0x80 /* Auto frame rate control ON/OFF selection (night mode) */ +#define COM5_AFR_SPEED 0x40 /* Auto frame rate control speed selection */ +#define COM5_AFR_0 0x00 /* No reduction of frame rate */ +#define COM5_AFR_1_2 0x10 /* Max reduction to 1/2 frame rate */ +#define COM5_AFR_1_4 0x20 /* Max reduction to 1/4 frame rate */ +#define COM5_AFR_1_8 0x30 /* Max reduction to 1/8 frame rate */ +#define COM5_AFR_4x 0x04 /* Add frame when AGC reaches 4x gain */ +#define COM5_AFR_8x 0x08 /* Add frame when AGC reaches 8x gain */ +#define COM5_AFR_16x 0x0c /* Add frame when AGC reaches 16x gain */ +#define COM5_AEC_NO_LIMIT 0x01 /* No limit to AEC increase step */ + +#define COM6 0x0F /* Common Control 6 */ +#define COM6_AUTO_WINDOW 0x01 /* Auto window setting ON/OFF selection when format changes */ + +#define AEC 0x10 /* AEC[7:0] (see register AECH for AEC[15:8]) */ +#define CLKRC 0x11 /* Internal Clock */ + +#define COM7 0x12 /* Common Control 7 */ +#define COM7_RESET 0x80 /* SCCB Register Reset */ +#define COM7_RES_VGA 0x00 /* Resolution VGA */ +#define COM7_RES_QVGA 0x40 /* Resolution QVGA */ +#define COM7_BT656 0x20 /* BT.656 protocol ON/OFF */ +#define COM7_SENSOR_RAW 0x10 /* Sensor RAW */ +#define COM7_FMT_GBR422 0x00 /* RGB output format GBR422 */ +#define COM7_FMT_RGB565 0x04 /* RGB output format RGB565 */ +#define COM7_FMT_RGB555 0x08 /* RGB output format RGB555 */ +#define COM7_FMT_RGB444 0x0C /* RGB output format RGB444 */ +#define COM7_FMT_YUV 0x00 /* Output format YUV */ +#define COM7_FMT_P_BAYER 0x01 /* Output format Processed Bayer RAW */ +#define COM7_FMT_RGB 0x04 /* Output format RGB */ +#define COM7_FMT_R_BAYER 0x03 /* Output format Bayer RAW */ +#define COM7_SET_FMT(r, x) ((r&0xFC)|((x&0x5)<<0)) + +#define COM8 0x13 /* Common Control 8 */ +#define COM8_FAST_AUTO 0x80 /* Enable fast AGC/AEC algorithm */ +#define COM8_STEP_VSYNC 0x00 /* AEC - Step size limited to vertical blank */ +#define COM8_STEP_UNLIMIT 0x40 /* AEC - Step size unlimited step size */ +#define COM8_BANDF_EN 0x20 /* Banding filter ON/OFF */ +#define COM8_AEC_BANDF 0x10 /* Enable AEC below banding value */ +#define COM8_AEC_FINE_EN 0x08 /* Fine AEC ON/OFF control */ +#define COM8_AGC_EN 0x04 /* AGC Enable */ +#define COM8_AWB_EN 0x02 /* AWB Enable */ +#define COM8_AEC_EN 0x01 /* AEC Enable */ +#define COM8_SET_AGC(r, x) ((r&0xFB)|((x&0x1)<<2)) +#define COM8_SET_AWB(r, x) ((r&0xFD)|((x&0x1)<<1)) +#define COM8_SET_AEC(r, x) ((r&0xFE)|((x&0x1)<<0)) + +#define COM9 0x14 /* Common Control 9 */ +#define COM9_HISTO_AVG 0x80 /* Histogram or average based AEC/AGC selection */ +#define COM9_AGC_GAIN_2x 0x00 /* Automatic Gain Ceiling 2x */ +#define COM9_AGC_GAIN_4x 0x10 /* Automatic Gain Ceiling 4x */ +#define COM9_AGC_GAIN_8x 0x20 /* Automatic Gain Ceiling 8x */ +#define COM9_AGC_GAIN_16x 0x30 /* Automatic Gain Ceiling 16x */ +#define COM9_AGC_GAIN_32x 0x40 /* Automatic Gain Ceiling 32x */ +#define COM9_DROP_VSYNC 0x04 /* Drop VSYNC output of corrupt frame */ +#define COM9_DROP_HREF 0x02 /* Drop HREF output of corrupt frame */ +#define COM9_SET_AGC(r, x) ((r&0x8F)|((x&0x07)<<4)) + +#define COM10 0x15 /* Common Control 10 */ +#define COM10_NEGATIVE 0x80 /* Output negative data */ +#define COM10_HSYNC_EN 0x40 /* HREF changes to HSYNC */ +#define COM10_PCLK_FREE 0x00 /* PCLK output option: free running PCLK */ +#define COM10_PCLK_MASK 0x20 /* PCLK output option: masked during horizontal blank */ +#define COM10_PCLK_REV 0x10 /* PCLK reverse */ +#define COM10_HREF_REV 0x08 /* HREF reverse */ +#define COM10_VSYNC_FALLING 0x00 /* VSYNC changes on falling edge of PCLK */ +#define COM10_VSYNC_RISING 0x04 /* VSYNC changes on rising edge of PCLK */ +#define COM10_VSYNC_NEG 0x02 /* VSYNC negative */ +#define COM10_OUT_RANGE_8 0x01 /* Output data range: Full range */ +#define COM10_OUT_RANGE_10 0x00 /* Output data range: Data from [10] to [F0] (8 MSBs) */ + +#define RSVD_16 0x16 /* Reserved register */ + +#define HSTART 0x17 /* Horizontal Frame (HREF column) Start high 8-bit(low 3 bits are at HREF[2:0]) */ +#define HSTOP 0x18 /* Horizontal Frame (HREF column) end high 8-bit (low 3 bits are at HREF[5:3]) */ +#define VSTART 0x19 /* Vertical Frame (row) Start high 8-bit (low 2 bits are at VREF[1:0]) */ +#define VSTOP 0x1A /* Vertical Frame (row) End high 8-bit (low 2 bits are at VREF[3:2]) */ +#define PSHFT 0x1B /* Data Format - Pixel Delay Select */ +#define REG_MIDH 0x1C /* Manufacturer ID Byte – High */ +#define REG_MIDL 0x1D /* Manufacturer ID Byte – Low */ + +#define MVFP 0x1E /* Mirror/Vflip Enable */ +#define MVFP_MIRROR 0x20 /* Mirror image */ +#define MVFP_FLIP 0x10 /* Vertical flip */ +#define MVFP_SUN 0x02 /* Black sun enable */ +#define MVFP_SET_MIRROR(r,x) ((r&0xDF)|((x&1)<<5)) /* change only bit5 according to x */ +#define MVFP_SET_FLIP(r,x) ((r&0xEF)|((x&1)<<4)) /* change only bit4 according to x */ + +#define LAEC 0x1F /* Fine AEC Value - defines exposure value less than one row period (Reserved?) */ +#define ADCCTR0 0x20 /* ADC control */ +#define ADCCTR1 0x21 /* reserved */ +#define ADCCTR2 0x22 /* reserved */ +#define ADCCTR3 0x23 /* reserved */ +#define AEW 0x24 /* AGC/AEC - Stable Operating Region (Upper Limit) */ +#define AEB 0x25 /* AGC/AEC - Stable Operating Region (Lower Limit) */ +#define VPT 0x26 /* AGC/AEC Fast Mode Operating Region */ +#define BBIAS 0x27 /* B channel signal output bias (effective only when COM6[3]=1) */ +#define GbBIAS 0x28 /* Gb channel signal output bias (effective only when COM6[3]=1) */ +#define RSVD_29 0x29 /* reserved */ +#define EXHCH 0x2A /* Dummy Pixel Insert MSB */ +#define EXHCL 0x2B /* Dummy Pixel Insert LSB */ +#define RBIAS 0x2C /* R channel signal output bias (effective only when COM6[3]=1) */ +#define ADVFL 0x2D /* LSB of Insert Dummy Rows in Vertical Sync (1 bit equals 1 row) */ +#define ADVFH 0x2E /* MSB of Insert Dummy Rows in Vertical Sync */ +#define YAVE 0x2F /* Y/G Channel Average Value */ +#define HSYST 0x30 /* HSync rising edge delay */ +#define HSYEN 0x31 /* HSync falling edge delay */ +#define HREF 0x32 /* Image Start and Size Control DIFFERENT CONTROL SEQUENCE */ +#define CHLF 0x33 /* Array Current control */ +#define ARBLM 0x34 /* Array reference control */ +#define RSVD_35 0x35 /* Reserved */ +#define RSVD_36 0x36 /* Reserved */ +#define ADC 0x37 /* ADC control */ +#define ACOM 0x38 /* ADC and analog common mode control */ +#define OFON 0x39 /* ADC offset control */ +#define TSLB 0x3A /* Line buffer test option */ + +#define COM11 0x3B /* Common control 11 */ +#define COM11_EXP 0x02 +#define COM11_HZAUTO 0x10 /* Auto detect 50/60 Hz */ + +#define COM12 0x3C /* Common control 12 */ + +#define COM13 0x3D /* Common control 13 */ +#define COM13_GAMMA 0x80 /* Gamma enable */ +#define COM13_UVSAT 0x40 /* UV saturation auto adjustment */ + +#define COM14 0x3E /* Common Control 14 */ + +#define EDGE 0x3F /* edge enhancement adjustment */ +#define COM15 0x40 /* Common Control 15 DIFFERENT CONTROLS */ +#define COM15_SET_RGB565(r,x) ((r&0xEF)|((x&1)<<4)) /* set rgb565 mode */ +#define COM15_RGB565 0x10 /* RGB565 output */ +#define COM15_R00FF 0xC0 /* Output range: [00] to [FF] */ + +#define COM16 0x41 /* Common Control 16 DIFFERENT CONTROLS */ +#define COM16_AWBGAIN 0x08 /* AWB gain enable */ +#define COM17 0x42 /* Common Control 17 */ + +#define AWBC1 0x43 /* Reserved */ +#define AWBC2 0x44 /* Reserved */ +#define AWBC3 0x45 /* Reserved */ +#define AWBC4 0x46 /* Reserved */ +#define AWBC5 0x47 /* Reserved */ +#define AWBC6 0x48 /* Reserved */ + +#define RSVD_49 0x49 /* Reserved */ +#define RSVD_4A 0x4A /* Reserved */ + +#define REG4B 0x4B /* Register 4B */ +#define DNSTH 0x4C /* Denoise strength */ + +#define RSVD_4D 0x4D /* Reserved */ +#define RSVD_4E 0x4E /* Reserved */ + +#define MTX1 0x4F /* Matrix coefficient 1 */ +#define MTX2 0x50 /* Matrix coefficient 2 */ +#define MTX3 0x51 /* Matrix coefficient 3 */ +#define MTX4 0x52 /* Matrix coefficient 4 */ +#define MTX5 0x53 /* Matrix coefficient 5 */ +#define MTX6 0x54 /* Matrix coefficient 6 */ +#define BRIGHTNESS 0x55 /* Brightness control */ +#define CONTRAST 0x56 /* Contrast control */ +#define CONTRASCENTER 0x57 /* Contrast center */ +#define MTXS 0x58 /* Matrix coefficient sign for coefficient 5 to 0*/ + +#define RSVD_59 0x59 /* Reserved */ +#define RSVD_5A 0x5A /* Reserved */ +#define RSVD_5B 0x5B /* Reserved */ +#define RSVD_5C 0x5C /* Reserved */ +#define RSVD_5D 0x5D /* Reserved */ +#define RSVD_5E 0x5E /* Reserved */ +#define RSVD_5F 0x5F /* Reserved */ +#define RSVD_60 0x60 /* Reserved */ +#define RSVD_61 0x61 /* Reserved */ + +#define LCC1 0x62 /* Lens correction option 1 */ + +#define LCC2 0x63 /* Lens correction option 2 */ +#define LCC3 0x64 /* Lens correction option 3 */ +#define LCC4 0x65 /* Lens correction option 4 */ +#define LCC5 0x66 /* Lens correction option 5 */ + +#define MANU 0x67 /* Manual U Value */ +#define MANV 0x68 /* Manual V Value */ +#define GFIX 0x69 /* Fix gain control */ +#define GGAIN 0x6A /* G channel AWB gain */ + +#define DBLV 0x6B /* PLL and clock ? */ + +#define AWBCTR3 0x6C /* AWB Control 3 */ +#define AWBCTR2 0x6D /* AWB Control 2 */ +#define AWBCTR1 0x6E /* AWB Control 1 */ +#define AWBCTR0 0x6F /* AWB Control 0 */ +#define SCALING_XSC 0x70 /* test pattern and horizontal scaling factor */ +#define SCALING_XSC_CBAR(r) (r&0x7F) /* make sure bit7 is 0 for color bar */ +#define SCALING_YSC 0x71 /* test pattern and vertical scaling factor */ +#define SCALING_YSC_CBAR(r,x) ((r&0x7F)|((x&1)<<7)) /* change bit7 for color bar on/off */ +#define SCALING_DCWCTR 0x72 /* DCW control */ +#define SCALING_PCLK_DIV 0x73 /* */ +#define REG74 0x74 /* */ +#define REG75 0x75 /* */ +#define REG76 0x76 /* */ +#define REG77 0x77 /* */ + +#define RSVD_78 0x78 /* Reserved */ +#define RSVD_79 0x79 /* Reserved */ + +#define SLOP 0x7A /* Gamma curve highest segment slope */ +#define GAM1 0x7B /* Gamma Curve 1st Segment Input End Point 0x04 Output Value */ +#define GAM2 0x7C /* Gamma Curve 2nd Segment Input End Point 0x08 Output Value */ +#define GAM3 0x7D /* Gamma Curve 3rd Segment Input End Point 0x10 Output Value */ +#define GAM4 0x7E /* Gamma Curve 4th Segment Input End Point 0x20 Output Value */ +#define GAM5 0x7F /* Gamma Curve 5th Segment Input End Point 0x28 Output Value */ +#define GAM6 0x80 /* Gamma Curve 6rd Segment Input End Point 0x30 Output Value */ +#define GAM7 0x81 /* Gamma Curve 7th Segment Input End Point 0x38 Output Value */ +#define GAM8 0x82 /* Gamma Curve 8th Segment Input End Point 0x40 Output Value */ +#define GAM9 0x83 /* Gamma Curve 9th Segment Input End Point 0x48 Output Value */ +#define GAM10 0x84 /* Gamma Curve 10th Segment Input End Point 0x50 Output Value */ +#define GAM11 0x85 /* Gamma Curve 11th Segment Input End Point 0x60 Output Value */ +#define GAM12 0x86 /* Gamma Curve 12th Segment Input End Point 0x70 Output Value */ +#define GAM13 0x87 /* Gamma Curve 13th Segment Input End Point 0x90 Output Value */ +#define GAM14 0x88 /* Gamma Curve 14th Segment Input End Point 0xB0 Output Value */ +#define GAM15 0x89 /* Gamma Curve 15th Segment Input End Point 0xD0 Output Value */ + +#define RSVD_8A 0x8A /* Reserved */ +#define RSVD_8B 0x8B /* Reserved */ + +#define RGB444 0x8C /* */ + +#define RSVD_8D 0x8D /* Reserved */ +#define RSVD_8E 0x8E /* Reserved */ +#define RSVD_8F 0x8F /* Reserved */ +#define RSVD_90 0x90 /* Reserved */ +#define RSVD_91 0x91 /* Reserved */ + +#define DM_LNL 0x92 /* Dummy line low 8 bit */ +#define DM_LNH 0x93 /* Dummy line high 8 bit */ +#define LCC6 0x94 /* Lens correction option 6 */ +#define LCC7 0x95 /* Lens correction option 7 */ + +#define RSVD_96 0x96 /* Reserved */ +#define RSVD_97 0x97 /* Reserved */ +#define RSVD_98 0x98 /* Reserved */ +#define RSVD_99 0x99 /* Reserved */ +#define RSVD_9A 0x9A /* Reserved */ +#define RSVD_9B 0x9B /* Reserved */ +#define RSVD_9C 0x9C /* Reserved */ + +#define BD50ST 0x9D /* 50 Hz banding filter value */ +#define BD60ST 0x9E /* 60 Hz banding filter value */ +#define HAECC1 0x9F /* Histogram-based AEC/AGC control 1 */ +#define HAECC2 0xA0 /* Histogram-based AEC/AGC control 2 */ + +#define RSVD_A1 0xA1 /* Reserved */ + +#define SCALING_PCLK_DELAY 0xA2 /* Pixel clock delay */ + +#define RSVD_A3 0xA3 /* Reserved */ + +#define NT_CNTRL 0xA4 /* */ +#define BD50MAX 0xA5 /* 50 Hz banding step limit */ +#define HAECC3 0xA6 /* Histogram-based AEC/AGC control 3 */ +#define HAECC4 0xA7 /* Histogram-based AEC/AGC control 4 */ +#define HAECC5 0xA8 /* Histogram-based AEC/AGC control 5 */ +#define HAECC6 0xA9 /* Histogram-based AEC/AGC control 6 */ + +#define HAECC7 0xAA /* Histogram-based AEC/AGC control 7 */ +#define HAECC_EN 0x80 /* Histogram-based AEC algorithm enable */ + +#define BD60MAX 0xAB /* 60 Hz banding step limit */ + +#define STR_OPT 0xAC /* Register AC */ +#define STR_R 0xAD /* R gain for led output frame */ +#define STR_G 0xAE /* G gain for led output frame */ +#define STR_B 0xAF /* B gain for led output frame */ +#define RSVD_B0 0xB0 /* Reserved */ +#define ABLC1 0xB1 /* */ +#define RSVD_B2 0xB2 /* Reserved */ +#define THL_ST 0xB3 /* ABLC target */ +#define THL_DLT 0xB5 /* ABLC stable range */ + +#define RSVD_B6 0xB6 /* Reserved */ +#define RSVD_B7 0xB7 /* Reserved */ +#define RSVD_B8 0xB8 /* Reserved */ +#define RSVD_B9 0xB9 /* Reserved */ +#define RSVD_BA 0xBA /* Reserved */ +#define RSVD_BB 0xBB /* Reserved */ +#define RSVD_BC 0xBC /* Reserved */ +#define RSVD_BD 0xBD /* Reserved */ + +#define AD_CHB 0xBE /* blue channel black level compensation */ +#define AD_CHR 0xBF /* Red channel black level compensation */ +#define AD_CHGb 0xC0 /* Gb channel black level compensation */ +#define AD_CHGr 0xC1 /* Gr channel black level compensation */ + +#define RSVD_C2 0xC2 /* Reserved */ +#define RSVD_C3 0xC3 /* Reserved */ +#define RSVD_C4 0xC4 /* Reserved */ +#define RSVD_C5 0xC5 /* Reserved */ +#define RSVD_C6 0xC6 /* Reserved */ +#define RSVD_C7 0xC7 /* Reserved */ +#define RSVD_C8 0xC8 /* Reserved */ + +#define SATCTR 0xC9 /* Saturation control */ +#define SET_REG(reg, x) (##reg_DEFAULT|x) + +#endif //__OV7670_REG_REGS_H__ diff --git a/esp32-cam/ov7725.c b/esp32-cam/ov7725.c index b4e6710..bb31573 100644 --- a/esp32-cam/ov7725.c +++ b/esp32-cam/ov7725.c @@ -121,6 +121,51 @@ static const uint8_t default_regs[][2] = { {0x00, 0x00}, }; +static int get_reg(sensor_t *sensor, int reg, int mask) +{ + int ret = SCCB_Read(sensor->slv_addr, reg & 0xFF); + if(ret > 0){ + ret &= mask; + } + return ret; +} + +static int set_reg(sensor_t *sensor, int reg, int mask, int value) +{ + int ret = 0; + ret = SCCB_Read(sensor->slv_addr, reg & 0xFF); + if(ret < 0){ + return ret; + } + value = (ret & ~mask) | (value & mask); + ret = SCCB_Write(sensor->slv_addr, reg & 0xFF, value); + return ret; +} + +static int set_reg_bits(sensor_t *sensor, uint8_t reg, uint8_t offset, uint8_t length, uint8_t value) +{ + int ret = 0; + ret = SCCB_Read(sensor->slv_addr, reg); + if(ret < 0){ + return ret; + } + uint8_t mask = ((1 << length) - 1) << offset; + value = (ret & ~mask) | ((value << offset) & mask); + ret = SCCB_Write(sensor->slv_addr, reg & 0xFF, value); + return ret; +} + +static int get_reg_bits(sensor_t *sensor, uint8_t reg, uint8_t offset, uint8_t length) +{ + int ret = 0; + ret = SCCB_Read(sensor->slv_addr, reg); + if(ret < 0){ + return ret; + } + uint8_t mask = ((1 << length) - 1) << offset; + return (ret & mask) >> offset; +} + static int reset(sensor_t *sensor) { @@ -176,6 +221,9 @@ static int set_pixformat(sensor_t *sensor, pixformat_t pixformat) static int set_framesize(sensor_t *sensor, framesize_t framesize) { int ret=0; + if (framesize > FRAMESIZE_VGA) { + return -1; + } uint16_t w = resolution[framesize].width; uint16_t h = resolution[framesize].height; uint8_t reg = SCCB_Read(sensor->slv_addr, COM7); @@ -251,82 +299,208 @@ static int set_colorbar(sensor_t *sensor, int enable) static int set_whitebal(sensor_t *sensor, int enable) { - // Read register COM8 - uint8_t reg = SCCB_Read(sensor->slv_addr, COM8); + if(set_reg_bits(sensor, COM8, 1, 1, enable) >= 0){ + sensor->status.awb = !!enable; + } + return sensor->status.awb; +} - sensor->status.awb = enable; - // Set white bal on/off - reg = COM8_SET_AWB(reg, enable); +static int set_gain_ctrl(sensor_t *sensor, int enable) +{ + if(set_reg_bits(sensor, COM8, 2, 1, enable) >= 0){ + sensor->status.agc = !!enable; + } + return sensor->status.agc; +} - // Write back register COM8 - return SCCB_Write(sensor->slv_addr, COM8, reg); +static int set_exposure_ctrl(sensor_t *sensor, int enable) +{ + if(set_reg_bits(sensor, COM8, 0, 1, enable) >= 0){ + sensor->status.aec = !!enable; + } + return sensor->status.aec; } -static int set_gain_ctrl(sensor_t *sensor, int enable) +static int set_hmirror(sensor_t *sensor, int enable) { - sensor->status.agc = enable; - // Read register COM8 - uint8_t reg = SCCB_Read(sensor->slv_addr, COM8); + if(set_reg_bits(sensor, COM3, 6, 1, enable) >= 0){ + sensor->status.hmirror = !!enable; + } + return sensor->status.hmirror; +} - // Set white bal on/off - reg = COM8_SET_AGC(reg, enable); +static int set_vflip(sensor_t *sensor, int enable) +{ + if(set_reg_bits(sensor, COM3, 7, 1, enable) >= 0){ + sensor->status.vflip = !!enable; + } + return sensor->status.vflip; +} - // Write back register COM8 - return SCCB_Write(sensor->slv_addr, COM8, reg); +static int set_dcw_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = set_reg_bits(sensor, 0x65, 2, 1, !enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set dcw to: %d", enable); + sensor->status.dcw = enable; + } + return ret; } -static int set_exposure_ctrl(sensor_t *sensor, int enable) +static int set_aec2(sensor_t *sensor, int enable) { - sensor->status.aec = enable; - // Read register COM8 - uint8_t reg = SCCB_Read(sensor->slv_addr, COM8); + int ret = 0; + ret = set_reg_bits(sensor, COM8, 7, 1, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set aec2 to: %d", enable); + sensor->status.aec2 = enable; + } + return ret; +} - // Set white bal on/off - reg = COM8_SET_AEC(reg, enable); +static int set_bpc_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = set_reg_bits(sensor, 0x64, 1, 1, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set bpc to: %d", enable); + sensor->status.bpc = enable; + } + return ret; +} - // Write back register COM8 - return SCCB_Write(sensor->slv_addr, COM8, reg); +static int set_wpc_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = set_reg_bits(sensor, 0x64, 0, 1, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set wpc to: %d", enable); + sensor->status.wpc = enable; + } + return ret; } -static int set_hmirror(sensor_t *sensor, int enable) +static int set_raw_gma_dsp(sensor_t *sensor, int enable) { - sensor->status.hmirror = enable; - // Read register COM3 - uint8_t reg = SCCB_Read(sensor->slv_addr, COM3); + int ret = 0; + ret = set_reg_bits(sensor, 0x64, 2, 1, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set raw_gma to: %d", enable); + sensor->status.raw_gma = enable; + } + return ret; +} - // Set mirror on/off - reg = COM3_SET_MIRROR(reg, enable); +static int set_lenc_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = set_reg_bits(sensor, LC_CTR, 0, 1, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set lenc to: %d", enable); + sensor->status.lenc = enable; + } + return ret; +} - // Write back register COM3 - return SCCB_Write(sensor->slv_addr, COM3, reg); +//real gain +static int set_agc_gain(sensor_t *sensor, int gain) +{ + int ret = 0; + ret = set_reg_bits(sensor, COM9, 4, 3, gain % 5); + if (ret == 0) { + ESP_LOGD(TAG, "Set gain to: %d", gain); + sensor->status.agc_gain = gain; + } + return ret; } -static int set_vflip(sensor_t *sensor, int enable) +static int set_aec_value(sensor_t *sensor, int value) { - sensor->status.vflip = enable; - // Read register COM3 - uint8_t reg = SCCB_Read(sensor->slv_addr, COM3); + int ret = 0; + ret = SCCB_Write(sensor->slv_addr, AEC, value & 0xff) | SCCB_Write(sensor->slv_addr, AECH, value >> 8); + if (ret == 0) { + ESP_LOGD(TAG, "Set aec_value to: %d", value); + sensor->status.aec_value = value; + } + return ret; +} + +static int set_awb_gain_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = set_reg_bits(sensor, 0x63, 7, 1, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set awb_gain to: %d", enable); + sensor->status.awb_gain = enable; + } + return ret; +} - // Set mirror on/off - reg = COM3_SET_FLIP(reg, enable); +static int set_brightness(sensor_t *sensor, int level) +{ + int ret = 0; + ret = SCCB_Write(sensor->slv_addr, 0x9B, level); + if (ret == 0) { + ESP_LOGD(TAG, "Set brightness to: %d", level); + sensor->status.brightness = level; + } + return ret; +} - // Write back register COM3 - return SCCB_Write(sensor->slv_addr, COM3, reg); +static int set_contrast(sensor_t *sensor, int level) +{ + int ret = 0; + ret = SCCB_Write(sensor->slv_addr, 0x9C, level); + if (ret == 0) { + ESP_LOGD(TAG, "Set contrast to: %d", level); + sensor->status.contrast = level; + } + return ret; } static int init_status(sensor_t *sensor) { - sensor->status.awb = 0;//get_reg_bits(sensor, BANK_DSP, CTRL1, 3, 1); - sensor->status.aec = 0; - sensor->status.agc = 0; - sensor->status.hmirror = 0; - sensor->status.vflip = 0; - sensor->status.colorbar = 0; + sensor->status.brightness = SCCB_Read(sensor->slv_addr, 0x9B); + sensor->status.contrast = SCCB_Read(sensor->slv_addr, 0x9C); + sensor->status.saturation = 0; + sensor->status.ae_level = 0; + sensor->status.special_effect = get_reg_bits(sensor, 0x64, 5, 1); + sensor->status.wb_mode = get_reg_bits(sensor, 0x6B, 7, 1); + sensor->status.agc_gain = get_reg_bits(sensor, COM9, 4, 3); + sensor->status.aec_value = SCCB_Read(sensor->slv_addr, AEC) | (SCCB_Read(sensor->slv_addr, AECH) << 8); + sensor->status.gainceiling = SCCB_Read(sensor->slv_addr, 0x00); + sensor->status.awb = get_reg_bits(sensor, COM8, 1, 1); + sensor->status.awb_gain = get_reg_bits(sensor, 0x63, 7, 1); + sensor->status.aec = get_reg_bits(sensor, COM8, 0, 1); + sensor->status.aec2 = get_reg_bits(sensor, COM8, 7, 1); + sensor->status.agc = get_reg_bits(sensor, COM8, 2, 1); + sensor->status.bpc = get_reg_bits(sensor, 0x64, 1, 1); + sensor->status.wpc = get_reg_bits(sensor, 0x64, 0, 1); + sensor->status.raw_gma = get_reg_bits(sensor, 0x64, 2, 1); + sensor->status.lenc = get_reg_bits(sensor, LC_CTR, 0, 1); + sensor->status.hmirror = get_reg_bits(sensor, COM3, 6, 1); + sensor->status.vflip = get_reg_bits(sensor, COM3, 7, 1); + sensor->status.dcw = get_reg_bits(sensor, 0x65, 2, 1); + sensor->status.colorbar = get_reg_bits(sensor, COM3, 0, 1); + sensor->status.sharpness = get_reg_bits(sensor, EDGE0, 0, 5); + sensor->status.denoise = SCCB_Read(sensor->slv_addr, 0x8E); return 0; } static int set_dummy(sensor_t *sensor, int val){ return -1; } static int set_gainceiling_dummy(sensor_t *sensor, gainceiling_t val){ return -1; } +static int set_res_raw(sensor_t *sensor, int startX, int startY, int endX, int endY, int offsetX, int offsetY, int totalX, int totalY, int outputX, int outputY, bool scale, bool binning){return -1;} +static int _set_pll(sensor_t *sensor, int bypass, int multiplier, int sys_div, int root_2x, int pre_div, int seld5, int pclk_manual, int pclk_div){return -1;} + +esp_err_t xclk_timer_conf(int ledc_timer, int xclk_freq_hz); +static int set_xclk(sensor_t *sensor, int timer, int xclk) +{ + int ret = 0; + sensor->xclk_freq_hz = xclk * 1000000U; + ret = xclk_timer_conf(timer, sensor->xclk_freq_hz); + return ret; +} int ov7725_init(sensor_t *sensor) { @@ -342,34 +516,35 @@ int ov7725_init(sensor_t *sensor) sensor->set_hmirror = set_hmirror; sensor->set_vflip = set_vflip; + sensor->set_brightness = set_brightness; + sensor->set_contrast = set_contrast; + sensor->set_aec2 = set_aec2; + sensor->set_aec_value = set_aec_value; + sensor->set_awb_gain = set_awb_gain_dsp; + sensor->set_agc_gain = set_agc_gain; + sensor->set_dcw = set_dcw_dsp; + sensor->set_bpc = set_bpc_dsp; + sensor->set_wpc = set_wpc_dsp; + sensor->set_raw_gma = set_raw_gma_dsp; + sensor->set_lenc = set_lenc_dsp; + //not supported - sensor->set_brightness= set_dummy; sensor->set_saturation= set_dummy; + sensor->set_sharpness = set_dummy; + sensor->set_denoise = set_dummy; sensor->set_quality = set_dummy; - sensor->set_gainceiling = set_gainceiling_dummy; - sensor->set_gain_ctrl = set_dummy; - sensor->set_exposure_ctrl = set_dummy; - sensor->set_hmirror = set_dummy; - sensor->set_vflip = set_dummy; - sensor->set_whitebal = set_dummy; - sensor->set_aec2 = set_dummy; - sensor->set_aec_value = set_dummy; sensor->set_special_effect = set_dummy; sensor->set_wb_mode = set_dummy; sensor->set_ae_level = set_dummy; - sensor->set_dcw = set_dummy; - sensor->set_bpc = set_dummy; - sensor->set_wpc = set_dummy; - sensor->set_awb_gain = set_dummy; - sensor->set_agc_gain = set_dummy; - sensor->set_raw_gma = set_dummy; - sensor->set_lenc = set_dummy; - sensor->set_sharpness = set_dummy; - sensor->set_denoise = set_dummy; - - + sensor->set_gainceiling = set_gainceiling_dummy; + sensor->get_reg = get_reg; + sensor->set_reg = set_reg; + sensor->set_res_raw = set_res_raw; + sensor->set_pll = _set_pll; + sensor->set_xclk = set_xclk; + // Retrieve sensor's signature sensor->id.MIDH = SCCB_Read(sensor->slv_addr, REG_MIDH); sensor->id.MIDL = SCCB_Read(sensor->slv_addr, REG_MIDL); diff --git a/esp32-cam/sccb.c b/esp32-cam/sccb.c index 30e725c..cb615bb 100644 --- a/esp32-cam/sccb.c +++ b/esp32-cam/sccb.c @@ -7,6 +7,7 @@ * */ #include +#include #include #include #include "sccb.h" @@ -19,14 +20,11 @@ static const char* TAG = "sccb"; #endif -//#undef CONFIG_SCCB_HARDWARE_I2C - #define LITTLETOBIG(x) ((x<<8)|(x>>8)) -#ifdef CONFIG_SCCB_HARDWARE_I2C #include "driver/i2c.h" -#define SCCB_FREQ 200000 /*!< I2C master frequency*/ +#define SCCB_FREQ 100000 /*!< I2C master frequency*/ #define WRITE_BIT I2C_MASTER_WRITE /*!< I2C master write */ #define READ_BIT I2C_MASTER_READ /*!< I2C master read */ #define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/ @@ -39,16 +37,13 @@ const int SCCB_I2C_PORT = 1; const int SCCB_I2C_PORT = 0; #endif static uint8_t ESP_SLAVE_ADDR = 0x3c; -#else -#include "twi.h" -#endif int SCCB_Init(int pin_sda, int pin_scl) { ESP_LOGI(TAG, "pin_sda %d pin_scl %d\n", pin_sda, pin_scl); -#ifdef CONFIG_SCCB_HARDWARE_I2C //log_i("SCCB_Init start"); i2c_config_t conf; + memset(&conf, 0, sizeof(i2c_config_t)); conf.mode = I2C_MODE_MASTER; conf.sda_io_num = pin_sda; conf.sda_pullup_en = GPIO_PULLUP_ENABLE; @@ -58,15 +53,11 @@ int SCCB_Init(int pin_sda, int pin_scl) i2c_param_config(SCCB_I2C_PORT, &conf); i2c_driver_install(SCCB_I2C_PORT, conf.mode, 0, 0, 0); -#else - twi_init(pin_sda, pin_scl); -#endif return 0; } uint8_t SCCB_Probe() { -#ifdef CONFIG_SCCB_HARDWARE_I2C uint8_t slave_addr = 0x0; while(slave_addr < 0x7f) { i2c_cmd_handle_t cmd = i2c_cmd_link_create(); @@ -82,28 +73,10 @@ uint8_t SCCB_Probe() slave_addr++; } return ESP_SLAVE_ADDR; -#else - uint8_t reg = 0x00; - uint8_t slv_addr = 0x00; - - ESP_LOGI(TAG, "SCCB_Probe start"); - for (uint8_t i = 0; i < 127; i++) { - if (twi_writeTo(i, ®, 1, true) == 0) { - slv_addr = i; - break; - } - - if (i!=126) { - vTaskDelay(10 / portTICK_PERIOD_MS); // Necessary for OV7725 camera (not for OV2640). - } - } - return slv_addr; -#endif } uint8_t SCCB_Read(uint8_t slv_addr, uint8_t reg) { -#ifdef CONFIG_SCCB_HARDWARE_I2C uint8_t data=0; esp_err_t ret = ESP_FAIL; i2c_cmd_handle_t cmd = i2c_cmd_link_create(); @@ -125,28 +98,10 @@ uint8_t SCCB_Read(uint8_t slv_addr, uint8_t reg) ESP_LOGE(TAG, "SCCB_Read Failed addr:0x%02x, reg:0x%02x, data:0x%02x, ret:%d", slv_addr, reg, data, ret); } return data; -#else - uint8_t data=0; - - int rc = twi_writeTo(slv_addr, ®, 1, true); - if (rc != 0) { - data = 0xff; - } else { - rc = twi_readFrom(slv_addr, &data, 1, true); - if (rc != 0) { - data=0xFF; - } - } - if (rc != 0) { - ESP_LOGE(TAG, "SCCB_Read [%02x] failed rc=%d\n", reg, rc); - } - return data; -#endif } uint8_t SCCB_Write(uint8_t slv_addr, uint8_t reg, uint8_t data) { -#ifdef CONFIG_SCCB_HARDWARE_I2C esp_err_t ret = ESP_FAIL; i2c_cmd_handle_t cmd = i2c_cmd_link_create(); i2c_master_start(cmd); @@ -160,23 +115,10 @@ uint8_t SCCB_Write(uint8_t slv_addr, uint8_t reg, uint8_t data) ESP_LOGE(TAG, "SCCB_Write Failed addr:0x%02x, reg:0x%02x, data:0x%02x, ret:%d", slv_addr, reg, data, ret); } return ret == ESP_OK ? 0 : -1; -#else - uint8_t ret=0; - uint8_t buf[] = {reg, data}; - - if(twi_writeTo(slv_addr, buf, 2, true) != 0) { - ret=0xFF; - } - if (ret != 0) { - ESP_LOGE(TAG, "SCCB_Write [%02x]=%02x failed\n", reg, data); - } - return ret; -#endif } uint8_t SCCB_Read16(uint8_t slv_addr, uint16_t reg) { -#ifdef CONFIG_SCCB_HARDWARE_I2C uint8_t data=0; esp_err_t ret = ESP_FAIL; uint16_t reg_htons = LITTLETOBIG(reg); @@ -201,32 +143,11 @@ uint8_t SCCB_Read16(uint8_t slv_addr, uint16_t reg) ESP_LOGE(TAG, "W [%04x]=%02x fail\n", reg, data); } return data; -#else - uint8_t data=0; - uint16_t reg_htons = LITTLETOBIG(reg); - uint8_t *reg_u8 = (uint8_t *)®_htons; - uint8_t buf[] = {reg_u8[0], reg_u8[1]}; - - int rc = twi_writeTo(slv_addr, buf, 2, true); - if (rc != 0) { - data = 0xff; - } else { - rc = twi_readFrom(slv_addr, &data, 1, true); - if (rc != 0) { - data=0xFF; - } - } - if (rc != 0) { - ESP_LOGE(TAG, "R [%04x] fail rc=%d\n", reg, rc); - } - return data; -#endif } uint8_t SCCB_Write16(uint8_t slv_addr, uint16_t reg, uint8_t data) { static uint16_t i = 0; -#ifdef CONFIG_SCCB_HARDWARE_I2C esp_err_t ret = ESP_FAIL; uint16_t reg_htons = LITTLETOBIG(reg); uint8_t *reg_u8 = (uint8_t *)®_htons; @@ -243,18 +164,4 @@ uint8_t SCCB_Write16(uint8_t slv_addr, uint16_t reg, uint8_t data) ESP_LOGE(TAG, "W [%04x]=%02x %d fail\n", reg, data, i++); } return ret == ESP_OK ? 0 : -1; -#else - uint8_t ret=0; - uint16_t reg_htons = LITTLETOBIG(reg); - uint8_t *reg_u8 = (uint8_t *)®_htons; - uint8_t buf[] = {reg_u8[0], reg_u8[1], data}; - - if(twi_writeTo(slv_addr, buf, 3, true) != 0) { - ret = 0xFF; - } - if (ret != 0) { - ESP_LOGE(TAG, "W [%04x]=%02x %d fail\n", reg, data, i++); - } - return ret; -#endif } diff --git a/esp32-cam/sensor.h b/esp32-cam/sensor.h index 3ea7e2c..ad6cd89 100644 --- a/esp32-cam/sensor.h +++ b/esp32-cam/sensor.h @@ -11,11 +11,13 @@ #include #include +#define NT99141_PID (0x14) #define OV9650_PID (0x96) #define OV7725_PID (0x77) #define OV2640_PID (0x26) #define OV3660_PID (0x36) #define OV5640_PID (0x56) +#define OV7670_PID (0x76) typedef enum { PIXFORMAT_RGB565, // 2BPP/RGB565 diff --git a/esp32-cam/to_bmp.c b/esp32-cam/to_bmp.c index 59455de..85f9c88 100644 --- a/esp32-cam/to_bmp.c +++ b/esp32-cam/to_bmp.c @@ -20,6 +20,17 @@ #include "sdkconfig.h" #include "esp_jpg_decode.h" +#include "esp_system.h" +#if ESP_IDF_VERSION_MAJOR >= 4 // IDF 4+ +#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 +#include "esp32/spiram.h" +#else +#error Target CONFIG_IDF_TARGET is not supported +#endif +#else // ESP32 Before IDF 4.0 +#include "esp_spiram.h" +#endif + #if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) #include "esp32-hal-log.h" #define TAG "" diff --git a/esp32-cam/twi.h b/esp32-cam/twi.h index 60624f8..71f9907 100644 --- a/esp32-cam/twi.h +++ b/esp32-cam/twi.h @@ -35,4 +35,4 @@ uint8_t twi_readFrom(unsigned char address, unsigned char * buf, unsigned int le } #endif -#endif \ No newline at end of file +#endif