From 95152b9b76e7cb2cb6752edcf150bb5503c4596a Mon Sep 17 00:00:00 2001 From: Anatoli Arkhipenko Date: Sun, 17 May 2020 09:46:09 -0400 Subject: [PATCH] Initial release --- README.md | 36 + esp32-cam/README.md | 16 + esp32-cam/camera.c | 1512 +++++++++++++++++++++++++++++ esp32-cam/camera_common.h | 49 + esp32-cam/camera_pins.h | 99 ++ esp32-cam/esp32-cam.ino | 492 ++++++++++ esp32-cam/esp32-camera-master.zip | Bin 0 -> 97227 bytes esp32-cam/esp_camera.h | 195 ++++ esp32-cam/esp_jpg_decode.c | 128 +++ esp32-cam/esp_jpg_decode.h | 43 + esp32-cam/img_converters.h | 126 +++ esp32-cam/jpge.cpp | 723 ++++++++++++++ esp32-cam/jpge.h | 142 +++ esp32-cam/ov2640.c | 580 +++++++++++ esp32-cam/ov2640.h | 13 + esp32-cam/ov2640_regs.h | 216 +++++ esp32-cam/ov2640_settings.h | 485 +++++++++ esp32-cam/ov3660.c | 1033 ++++++++++++++++++++ esp32-cam/ov3660.h | 16 + esp32-cam/ov3660_regs.h | 211 ++++ esp32-cam/ov3660_settings.h | 318 ++++++ esp32-cam/ov5640.c | 1105 +++++++++++++++++++++ esp32-cam/ov5640.h | 9 + esp32-cam/ov5640_regs.h | 213 ++++ esp32-cam/ov5640_settings.h | 334 +++++++ esp32-cam/ov7725.c | 382 ++++++++ esp32-cam/ov7725.h | 14 + esp32-cam/ov7725_regs.h | 335 +++++++ esp32-cam/sccb.c | 260 +++++ esp32-cam/sccb.h | 18 + esp32-cam/sensor.c | 28 + esp32-cam/sensor.h | 191 ++++ esp32-cam/to_bmp.c | 315 ++++++ esp32-cam/to_jpg.cpp | 241 +++++ esp32-cam/twi.c | 432 +++++++++ esp32-cam/twi.h | 38 + esp32-cam/xclk.c | 61 ++ esp32-cam/xclk.h | 7 + esp32-cam/yuv.c | 298 ++++++ esp32-cam/yuv.h | 29 + 40 files changed, 10743 insertions(+) create mode 100644 README.md create mode 100644 esp32-cam/README.md create mode 100644 esp32-cam/camera.c create mode 100644 esp32-cam/camera_common.h create mode 100644 esp32-cam/camera_pins.h create mode 100644 esp32-cam/esp32-cam.ino create mode 100644 esp32-cam/esp32-camera-master.zip create mode 100644 esp32-cam/esp_camera.h create mode 100644 esp32-cam/esp_jpg_decode.c create mode 100644 esp32-cam/esp_jpg_decode.h create mode 100644 esp32-cam/img_converters.h create mode 100644 esp32-cam/jpge.cpp create mode 100644 esp32-cam/jpge.h create mode 100644 esp32-cam/ov2640.c create mode 100644 esp32-cam/ov2640.h create mode 100644 esp32-cam/ov2640_regs.h create mode 100644 esp32-cam/ov2640_settings.h create mode 100644 esp32-cam/ov3660.c create mode 100644 esp32-cam/ov3660.h create mode 100644 esp32-cam/ov3660_regs.h create mode 100644 esp32-cam/ov3660_settings.h create mode 100644 esp32-cam/ov5640.c create mode 100644 esp32-cam/ov5640.h create mode 100644 esp32-cam/ov5640_regs.h create mode 100644 esp32-cam/ov5640_settings.h create mode 100644 esp32-cam/ov7725.c create mode 100644 esp32-cam/ov7725.h create mode 100644 esp32-cam/ov7725_regs.h create mode 100644 esp32-cam/sccb.c create mode 100644 esp32-cam/sccb.h create mode 100644 esp32-cam/sensor.c create mode 100644 esp32-cam/sensor.h create mode 100644 esp32-cam/to_bmp.c create mode 100644 esp32-cam/to_jpg.cpp create mode 100644 esp32-cam/twi.c create mode 100644 esp32-cam/twi.h create mode 100644 esp32-cam/xclk.c create mode 100644 esp32-cam/xclk.h create mode 100644 esp32-cam/yuv.c create mode 100644 esp32-cam/yuv.h diff --git a/README.md b/README.md new file mode 100644 index 0000000..dcc2078 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# MJPEG Multiclient Streaming Server + +## With latest ESP-CAM drivers + +### Procedure: + +1. Download latest ZIP file from https://github.com/espressif/esp32-camera.git into the esp32-cam subfolder + +2. unzip using `unzip -j esp32-cam-master.zip` command. This will place all files in the same folder + **NOTE:** please observe the `-j` flag: the sketch assumes all files are in the same folder. + +3. In **esp32-cam.ino** sketch select your camera pin assignment. The choices are: + + 1. CAMERA_MODEL_WROVER_KIT + 2. CAMERA_MODEL_ESP_EYE + 3. CAMERA_MODEL_M5STACK_PSRAM + 4. CAMERA_MODEL_M5STACK_WIDE + 5. CAMERA_MODEL_AI_THINKER + +4. Compile the **esp32-cam.ino** sketch using the following settings: + + ESP32 Dev Module + CPU Freq: 240 + Flash Freq: 80 + Flash mode: QIO + Flash Size: 4Mb + Partition: Minimal SPIFFS (or any other that would fit the sketch) + PSRAM: Enabled + +### Results: + +I was able to run multiple browser windows, multiple VLC windows and connect multiple Blynk video widgets (max: 10) to ESP-EYE chip. The delay on the browser window was almost unnoticeable. In VLC you notice a 1 second delay probably due to buffering. Blynk performance all depends on the phone, so no comments there. + +This is incredible considering the size of this thing! The camera on ESP-EYE is actually quite good. + +### Enjoy! \ No newline at end of file diff --git a/esp32-cam/README.md b/esp32-cam/README.md new file mode 100644 index 0000000..d773b9f --- /dev/null +++ b/esp32-cam/README.md @@ -0,0 +1,16 @@ +# ESP32 MJPEG Multiclient Streaming Server + +This is a simple MJPEG streaming webserver implemented for AI-Thinker ESP32-CAM or ESP-EYE modules. + +This is tested to work with **VLC** and **Blynk** video widget. + + + +**This version uses FreeRTOS tasks to enable streaming to up to 10 connected clients** + + + +Inspired by and based on this Instructable: [$9 RTSP Video Streamer Using the ESP32-CAM Board](https://www.instructables.com/id/9-RTSP-Video-Streamer-Using-the-ESP32-CAM-Board/) + + + diff --git a/esp32-cam/camera.c b/esp32-cam/camera.c new file mode 100644 index 0000000..d2ac7a3 --- /dev/null +++ b/esp32-cam/camera.c @@ -0,0 +1,1512 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include +#include +#include +#include "time.h" +#include "sys/time.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "soc/soc.h" +#include "soc/gpio_sig_map.h" +#include "soc/i2s_reg.h" +#include "soc/i2s_struct.h" +#include "soc/io_mux_reg.h" +#include "driver/gpio.h" +#include "driver/rtc_io.h" +#include "driver/periph_ctrl.h" +#include "esp_intr_alloc.h" +#include "esp_system.h" +#include "nvs_flash.h" +#include "nvs.h" +#include "sensor.h" +#include "sccb.h" +#include "esp_camera.h" +#include "camera_common.h" +#include "xclk.h" +#if CONFIG_OV2640_SUPPORT +#include "ov2640.h" +#endif +#if CONFIG_OV7725_SUPPORT +#include "ov7725.h" +#endif +#if CONFIG_OV3660_SUPPORT +#include "ov3660.h" +#endif +#if CONFIG_OV5640_SUPPORT +#include "ov5640.h" +#endif + +typedef enum { + CAMERA_NONE = 0, + CAMERA_UNKNOWN = 1, + CAMERA_OV7725 = 7725, + CAMERA_OV2640 = 2640, + CAMERA_OV3660 = 3660, + CAMERA_OV5640 = 5640, +} camera_model_t; + +#define REG_PID 0x0A +#define REG_VER 0x0B +#define REG_MIDH 0x1C +#define REG_MIDL 0x1D + +#define REG16_CHIDH 0x300A +#define REG16_CHIDL 0x300B + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define TAG "" +#else +#include "esp_log.h" +static const char* TAG = "camera"; +#endif +static const char* CAMERA_SENSOR_NVS_KEY = "sensor"; +static const char* CAMERA_PIXFORMAT_NVS_KEY = "pixformat"; + +typedef void (*dma_filter_t)(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst); + +typedef struct camera_fb_s { + uint8_t * buf; + size_t len; + size_t width; + size_t height; + pixformat_t format; + struct timeval timestamp; + size_t size; + uint8_t ref; + uint8_t bad; + struct camera_fb_s * next; +} camera_fb_int_t; + +typedef struct fb_s { + uint8_t * buf; + size_t len; + struct fb_s * next; +} fb_item_t; + +typedef struct { + camera_config_t config; + sensor_t sensor; + + camera_fb_int_t *fb; + size_t fb_size; + size_t data_size; + + size_t width; + size_t height; + size_t in_bytes_per_pixel; + size_t fb_bytes_per_pixel; + + size_t dma_received_count; + size_t dma_filtered_count; + size_t dma_per_line; + size_t dma_buf_width; + size_t dma_sample_count; + + lldesc_t *dma_desc; + dma_elem_t **dma_buf; + size_t dma_desc_count; + size_t dma_desc_cur; + + i2s_sampling_mode_t sampling_mode; + dma_filter_t dma_filter; + intr_handle_t i2s_intr_handle; + QueueHandle_t data_ready; + QueueHandle_t fb_in; + QueueHandle_t fb_out; + + SemaphoreHandle_t frame_ready; + TaskHandle_t dma_filter_task; +} camera_state_t; + +camera_state_t* s_state = NULL; + +static void i2s_init(); +static int i2s_run(); +static void IRAM_ATTR vsync_isr(void* arg); +static void IRAM_ATTR i2s_isr(void* arg); +static esp_err_t dma_desc_init(); +static void dma_desc_deinit(); +static void dma_filter_task(void *pvParameters); +static void dma_filter_grayscale(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst); +static void dma_filter_grayscale_highspeed(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst); +static void dma_filter_yuyv(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst); +static void dma_filter_yuyv_highspeed(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst); +static void dma_filter_jpeg(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst); +static void i2s_stop(bool* need_yield); + +static bool is_hs_mode() +{ + return s_state->config.xclk_freq_hz > 10000000; +} + +static size_t i2s_bytes_per_sample(i2s_sampling_mode_t mode) +{ + switch(mode) { + case SM_0A00_0B00: + return 4; + case SM_0A0B_0B0C: + return 4; + case SM_0A0B_0C0D: + return 2; + default: + assert(0 && "invalid sampling mode"); + return 0; + } +} + +static int IRAM_ATTR _gpio_get_level(gpio_num_t gpio_num) +{ + if (gpio_num < 32) { + return (GPIO.in >> gpio_num) & 0x1; + } else { + return (GPIO.in1.data >> (gpio_num - 32)) & 0x1; + } +} + +static void IRAM_ATTR vsync_intr_disable() +{ + gpio_set_intr_type(s_state->config.pin_vsync, GPIO_INTR_DISABLE); +} + +static void vsync_intr_enable() +{ + gpio_set_intr_type(s_state->config.pin_vsync, GPIO_INTR_NEGEDGE); +} + +static int skip_frame() +{ + if (s_state == NULL) { + return -1; + } + int64_t st_t = esp_timer_get_time(); + while (_gpio_get_level(s_state->config.pin_vsync) == 0) { + if((esp_timer_get_time() - st_t) > 1000000LL){ + goto timeout; + } + } + while (_gpio_get_level(s_state->config.pin_vsync) != 0) { + if((esp_timer_get_time() - st_t) > 1000000LL){ + goto timeout; + } + } + while (_gpio_get_level(s_state->config.pin_vsync) == 0) { + if((esp_timer_get_time() - st_t) > 1000000LL){ + goto timeout; + } + } + return 0; + +timeout: + ESP_LOGE(TAG, "Timeout waiting for VSYNC"); + return -1; +} + +static void camera_fb_deinit() +{ + camera_fb_int_t * _fb1 = s_state->fb, * _fb2 = NULL; + while(s_state->fb) { + _fb2 = s_state->fb; + s_state->fb = _fb2->next; + if(_fb2->next == _fb1) { + s_state->fb = NULL; + } + free(_fb2->buf); + free(_fb2); + } +} + +static esp_err_t camera_fb_init(size_t count) +{ + if(!count) { + return ESP_ERR_INVALID_ARG; + } + + camera_fb_deinit(); + + ESP_LOGI(TAG, "Allocating %u frame buffers (%d KB total)", count, (s_state->fb_size * count) / 1024); + + camera_fb_int_t * _fb = NULL, * _fb1 = NULL, * _fb2 = NULL; + for(size_t i = 0; i < count; i++) { + _fb2 = (camera_fb_int_t *)malloc(sizeof(camera_fb_int_t)); + if(!_fb2) { + goto fail; + } + memset(_fb2, 0, sizeof(camera_fb_int_t)); + _fb2->size = s_state->fb_size; + _fb2->buf = (uint8_t*) calloc(_fb2->size, 1); + if(!_fb2->buf) { + ESP_LOGI(TAG, "Allocating %d KB frame buffer in PSRAM", s_state->fb_size/1024); + _fb2->buf = (uint8_t*) heap_caps_calloc(_fb2->size, 1, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + } else { + ESP_LOGI(TAG, "Allocating %d KB frame buffer in OnBoard RAM", s_state->fb_size/1024); + } + if(!_fb2->buf) { + free(_fb2); + ESP_LOGE(TAG, "Allocating %d KB frame buffer Failed", s_state->fb_size/1024); + goto fail; + } + memset(_fb2->buf, 0, _fb2->size); + _fb2->next = _fb; + _fb = _fb2; + if(!i) { + _fb1 = _fb2; + } + } + if(_fb1) { + _fb1->next = _fb; + } + + s_state->fb = _fb;//load first buffer + + return ESP_OK; + +fail: + while(_fb) { + _fb2 = _fb; + _fb = _fb->next; + free(_fb2->buf); + free(_fb2); + } + return ESP_ERR_NO_MEM; +} + +static esp_err_t dma_desc_init() +{ + assert(s_state->width % 4 == 0); + size_t line_size = s_state->width * s_state->in_bytes_per_pixel * + i2s_bytes_per_sample(s_state->sampling_mode); + ESP_LOGD(TAG, "Line width (for DMA): %d bytes", line_size); + size_t dma_per_line = 1; + size_t buf_size = line_size; + while (buf_size >= 4096) { + buf_size /= 2; + dma_per_line *= 2; + } + size_t dma_desc_count = dma_per_line * 4; + s_state->dma_buf_width = line_size; + s_state->dma_per_line = dma_per_line; + s_state->dma_desc_count = dma_desc_count; + ESP_LOGD(TAG, "DMA buffer size: %d, DMA buffers per line: %d", buf_size, dma_per_line); + ESP_LOGD(TAG, "DMA buffer count: %d", dma_desc_count); + ESP_LOGD(TAG, "DMA buffer total: %d bytes", buf_size * dma_desc_count); + + s_state->dma_buf = (dma_elem_t**) malloc(sizeof(dma_elem_t*) * dma_desc_count); + if (s_state->dma_buf == NULL) { + return ESP_ERR_NO_MEM; + } + s_state->dma_desc = (lldesc_t*) malloc(sizeof(lldesc_t) * dma_desc_count); + if (s_state->dma_desc == NULL) { + return ESP_ERR_NO_MEM; + } + size_t dma_sample_count = 0; + for (int i = 0; i < dma_desc_count; ++i) { + ESP_LOGD(TAG, "Allocating DMA buffer #%d, size=%d", i, buf_size); + dma_elem_t* buf = (dma_elem_t*) malloc(buf_size); + if (buf == NULL) { + return ESP_ERR_NO_MEM; + } + s_state->dma_buf[i] = buf; + ESP_LOGV(TAG, "dma_buf[%d]=%p", i, buf); + + lldesc_t* pd = &s_state->dma_desc[i]; + pd->length = buf_size; + if (s_state->sampling_mode == SM_0A0B_0B0C && + (i + 1) % dma_per_line == 0) { + pd->length -= 4; + } + dma_sample_count += pd->length / 4; + pd->size = pd->length; + pd->owner = 1; + pd->sosf = 1; + pd->buf = (uint8_t*) buf; + pd->offset = 0; + pd->empty = 0; + pd->eof = 1; + pd->qe.stqe_next = &s_state->dma_desc[(i + 1) % dma_desc_count]; + } + s_state->dma_sample_count = dma_sample_count; + return ESP_OK; +} + +static void dma_desc_deinit() +{ + if (s_state->dma_buf) { + for (int i = 0; i < s_state->dma_desc_count; ++i) { + free(s_state->dma_buf[i]); + } + } + free(s_state->dma_buf); + free(s_state->dma_desc); +} + +static inline void IRAM_ATTR i2s_conf_reset() +{ + const uint32_t lc_conf_reset_flags = I2S_IN_RST_M | I2S_AHBM_RST_M + | I2S_AHBM_FIFO_RST_M; + I2S0.lc_conf.val |= lc_conf_reset_flags; + I2S0.lc_conf.val &= ~lc_conf_reset_flags; + + const uint32_t conf_reset_flags = I2S_RX_RESET_M | I2S_RX_FIFO_RESET_M + | I2S_TX_RESET_M | I2S_TX_FIFO_RESET_M; + I2S0.conf.val |= conf_reset_flags; + I2S0.conf.val &= ~conf_reset_flags; + while (I2S0.state.rx_fifo_reset_back) { + ; + } +} + +static void i2s_init() +{ + camera_config_t* config = &s_state->config; + + // Configure input GPIOs + gpio_num_t pins[] = { + config->pin_d7, + config->pin_d6, + config->pin_d5, + config->pin_d4, + config->pin_d3, + config->pin_d2, + config->pin_d1, + config->pin_d0, + config->pin_vsync, + config->pin_href, + config->pin_pclk + }; + gpio_config_t conf = { + .mode = GPIO_MODE_INPUT, + .pull_up_en = GPIO_PULLUP_ENABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE + }; + 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); + } + + // Route input GPIOs to I2S peripheral using GPIO matrix + gpio_matrix_in(config->pin_d0, I2S0I_DATA_IN0_IDX, false); + gpio_matrix_in(config->pin_d1, I2S0I_DATA_IN1_IDX, false); + gpio_matrix_in(config->pin_d2, I2S0I_DATA_IN2_IDX, false); + gpio_matrix_in(config->pin_d3, I2S0I_DATA_IN3_IDX, false); + gpio_matrix_in(config->pin_d4, I2S0I_DATA_IN4_IDX, false); + gpio_matrix_in(config->pin_d5, I2S0I_DATA_IN5_IDX, false); + gpio_matrix_in(config->pin_d6, I2S0I_DATA_IN6_IDX, false); + gpio_matrix_in(config->pin_d7, I2S0I_DATA_IN7_IDX, false); + gpio_matrix_in(config->pin_vsync, I2S0I_V_SYNC_IDX, false); + gpio_matrix_in(0x38, I2S0I_H_SYNC_IDX, false); + gpio_matrix_in(config->pin_href, I2S0I_H_ENABLE_IDX, false); + gpio_matrix_in(config->pin_pclk, I2S0I_WS_IN_IDX, false); + + // Enable and configure I2S peripheral + periph_module_enable(PERIPH_I2S0_MODULE); + // Toggle some reset bits in LC_CONF register + // Toggle some reset bits in CONF register + i2s_conf_reset(); + // Enable slave mode (sampling clock is external) + I2S0.conf.rx_slave_mod = 1; + // Enable parallel mode + I2S0.conf2.lcd_en = 1; + // Use HSYNC/VSYNC/HREF to control sampling + I2S0.conf2.camera_en = 1; + // Configure clock divider + I2S0.clkm_conf.clkm_div_a = 1; + I2S0.clkm_conf.clkm_div_b = 0; + I2S0.clkm_conf.clkm_div_num = 2; + // FIFO will sink data to DMA + I2S0.fifo_conf.dscr_en = 1; + // FIFO configuration + I2S0.fifo_conf.rx_fifo_mod = s_state->sampling_mode; + I2S0.fifo_conf.rx_fifo_mod_force_en = 1; + I2S0.conf_chan.rx_chan_mod = 1; + // Clear flags which are used in I2S serial mode + I2S0.sample_rate_conf.rx_bits_mod = 0; + I2S0.conf.rx_right_first = 0; + I2S0.conf.rx_msb_right = 0; + I2S0.conf.rx_msb_shift = 0; + I2S0.conf.rx_mono = 0; + I2S0.conf.rx_short_sync = 0; + I2S0.timing.val = 0; + I2S0.timing.rx_dsync_sw = 1; + + // Allocate I2S interrupt, keep it disabled + ESP_ERROR_CHECK(esp_intr_alloc(ETS_I2S0_INTR_SOURCE, + ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM, + &i2s_isr, NULL, &s_state->i2s_intr_handle)); +} + +static void IRAM_ATTR i2s_start_bus() +{ + s_state->dma_desc_cur = 0; + s_state->dma_received_count = 0; + //s_state->dma_filtered_count = 0; + esp_intr_disable(s_state->i2s_intr_handle); + i2s_conf_reset(); + + I2S0.rx_eof_num = s_state->dma_sample_count; + I2S0.in_link.addr = (uint32_t) &s_state->dma_desc[0]; + I2S0.in_link.start = 1; + I2S0.int_clr.val = I2S0.int_raw.val; + I2S0.int_ena.val = 0; + I2S0.int_ena.in_done = 1; + + esp_intr_enable(s_state->i2s_intr_handle); + I2S0.conf.rx_start = 1; + if (s_state->config.pixel_format == PIXFORMAT_JPEG) { + vsync_intr_enable(); + } +} + +static int i2s_run() +{ + for (int i = 0; i < s_state->dma_desc_count; ++i) { + lldesc_t* d = &s_state->dma_desc[i]; + ESP_LOGV(TAG, "DMA desc %2d: %u %u %u %u %u %u %p %p", + i, d->length, d->size, d->offset, d->eof, d->sosf, d->owner, d->buf, d->qe.stqe_next); + memset(s_state->dma_buf[i], 0, d->length); + } + + // wait for frame + camera_fb_int_t * fb = s_state->fb; + while(s_state->config.fb_count > 1) { + while(s_state->fb->ref && s_state->fb->next != fb) { + s_state->fb = s_state->fb->next; + } + if(s_state->fb->ref == 0) { + break; + } + vTaskDelay(2); + } + + //todo: wait for vsync + ESP_LOGV(TAG, "Waiting for negative edge on VSYNC"); + + int64_t st_t = esp_timer_get_time(); + while (_gpio_get_level(s_state->config.pin_vsync) != 0) { + if((esp_timer_get_time() - st_t) > 1000000LL){ + ESP_LOGE(TAG, "Timeout waiting for VSYNC"); + return -1; + } + } + ESP_LOGV(TAG, "Got VSYNC"); + i2s_start_bus(); + return 0; +} + +static void IRAM_ATTR i2s_stop_bus() +{ + esp_intr_disable(s_state->i2s_intr_handle); + vsync_intr_disable(); + i2s_conf_reset(); + I2S0.conf.rx_start = 0; +} + +static void IRAM_ATTR i2s_stop(bool* need_yield) +{ + if(s_state->config.fb_count == 1 && !s_state->fb->bad) { + i2s_stop_bus(); + } else { + s_state->dma_received_count = 0; + } + + size_t val = SIZE_MAX; + BaseType_t higher_priority_task_woken; + BaseType_t ret = xQueueSendFromISR(s_state->data_ready, &val, &higher_priority_task_woken); + if(need_yield && !*need_yield) { + *need_yield = (ret == pdTRUE && higher_priority_task_woken == pdTRUE); + } +} + +static void IRAM_ATTR signal_dma_buf_received(bool* need_yield) +{ + size_t dma_desc_filled = s_state->dma_desc_cur; + s_state->dma_desc_cur = (dma_desc_filled + 1) % s_state->dma_desc_count; + s_state->dma_received_count++; + if(!s_state->fb->ref && s_state->fb->bad){ + *need_yield = false; + return; + } + BaseType_t higher_priority_task_woken; + BaseType_t ret = xQueueSendFromISR(s_state->data_ready, &dma_desc_filled, &higher_priority_task_woken); + if (ret != pdTRUE) { + if(!s_state->fb->ref) { + s_state->fb->bad = 1; + } + //ESP_EARLY_LOGW(TAG, "qsf:%d", s_state->dma_received_count); + //ets_printf("qsf:%d\n", s_state->dma_received_count); + //ets_printf("qovf\n"); + } + *need_yield = (ret == pdTRUE && higher_priority_task_woken == pdTRUE); +} + +static void IRAM_ATTR i2s_isr(void* arg) +{ + I2S0.int_clr.val = I2S0.int_raw.val; + bool need_yield = false; + signal_dma_buf_received(&need_yield); + if (s_state->config.pixel_format != PIXFORMAT_JPEG + && s_state->dma_received_count == s_state->height * s_state->dma_per_line) { + i2s_stop(&need_yield); + } + if (need_yield) { + portYIELD_FROM_ISR(); + } +} + +static void IRAM_ATTR vsync_isr(void* arg) +{ + GPIO.status1_w1tc.val = GPIO.status1.val; + GPIO.status_w1tc = GPIO.status; + bool need_yield = false; + //if vsync is low and we have received some data, frame is done + if (_gpio_get_level(s_state->config.pin_vsync) == 0) { + if(s_state->dma_received_count > 0) { + signal_dma_buf_received(&need_yield); + //ets_printf("end_vsync\n"); + if(s_state->dma_filtered_count > 1 || s_state->fb->bad || s_state->config.fb_count > 1) { + i2s_stop(&need_yield); + } + //ets_printf("vs\n"); + } + if(s_state->config.fb_count > 1 || s_state->dma_filtered_count < 2) { + I2S0.conf.rx_start = 0; + I2S0.in_link.start = 0; + I2S0.int_clr.val = I2S0.int_raw.val; + i2s_conf_reset(); + s_state->dma_desc_cur = (s_state->dma_desc_cur + 1) % s_state->dma_desc_count; + //I2S0.rx_eof_num = s_state->dma_sample_count; + I2S0.in_link.addr = (uint32_t) &s_state->dma_desc[s_state->dma_desc_cur]; + I2S0.in_link.start = 1; + I2S0.conf.rx_start = 1; + s_state->dma_received_count = 0; + } + } + if (need_yield) { + portYIELD_FROM_ISR(); + } +} + +static void IRAM_ATTR camera_fb_done() +{ + camera_fb_int_t * fb = NULL, * fb2 = NULL; + BaseType_t taskAwoken = 0; + + if(s_state->config.fb_count == 1) { + xSemaphoreGive(s_state->frame_ready); + return; + } + + fb = s_state->fb; + if(!fb->ref && fb->len) { + //add reference + fb->ref = 1; + + //check if the queue is full + if(xQueueIsQueueFullFromISR(s_state->fb_out) == pdTRUE) { + //pop frame buffer from the queue + if(xQueueReceiveFromISR(s_state->fb_out, &fb2, &taskAwoken) == pdTRUE) { + //free the popped buffer + fb2->ref = 0; + fb2->len = 0; + //push the new frame to the end of the queue + xQueueSendFromISR(s_state->fb_out, &fb, &taskAwoken); + } else { + //queue is full and we could not pop a frame from it + } + } else { + //push the new frame to the end of the queue + xQueueSendFromISR(s_state->fb_out, &fb, &taskAwoken); + } + } else { + //frame was referenced or empty + } + + //return buffers to be filled + while(xQueueReceiveFromISR(s_state->fb_in, &fb2, &taskAwoken) == pdTRUE) { + fb2->ref = 0; + fb2->len = 0; + } + + //advance frame buffer only if the current one has data + if(s_state->fb->len) { + s_state->fb = s_state->fb->next; + } + //try to find the next free frame buffer + while(s_state->fb->ref && s_state->fb->next != fb) { + s_state->fb = s_state->fb->next; + } + //is the found frame buffer free? + if(!s_state->fb->ref) { + //buffer found. make sure it's empty + s_state->fb->len = 0; + *((uint32_t *)s_state->fb->buf) = 0; + } else { + //stay at the previous buffer + s_state->fb = fb; + } +} + +static void IRAM_ATTR dma_finish_frame() +{ + size_t buf_len = s_state->width * s_state->fb_bytes_per_pixel / s_state->dma_per_line; + + if(!s_state->fb->ref) { + // is the frame bad? + if(s_state->fb->bad){ + s_state->fb->bad = 0; + s_state->fb->len = 0; + *((uint32_t *)s_state->fb->buf) = 0; + if(s_state->config.fb_count == 1) { + i2s_start_bus(); + } + //ets_printf("bad\n"); + } else { + s_state->fb->len = s_state->dma_filtered_count * buf_len; + if(s_state->fb->len) { + //find the end marker for JPEG. Data after that can be discarded + if(s_state->fb->format == PIXFORMAT_JPEG){ + uint8_t * dptr = &s_state->fb->buf[s_state->fb->len - 1]; + while(dptr > s_state->fb->buf){ + if(dptr[0] == 0xFF && dptr[1] == 0xD9 && dptr[2] == 0x00 && dptr[3] == 0x00){ + dptr += 2; + s_state->fb->len = dptr - s_state->fb->buf; + if((s_state->fb->len & 0x1FF) == 0){ + s_state->fb->len += 1; + } + if((s_state->fb->len % 100) == 0){ + s_state->fb->len += 1; + } + break; + } + dptr--; + } + } + //send out the frame + camera_fb_done(); + } else if(s_state->config.fb_count == 1){ + //frame was empty? + i2s_start_bus(); + } else { + //ets_printf("empty\n"); + } + } + } else if(s_state->fb->len) { + camera_fb_done(); + } + s_state->dma_filtered_count = 0; +} + +static void IRAM_ATTR dma_filter_buffer(size_t buf_idx) +{ + //no need to process the data if frame is in use or is bad + if(s_state->fb->ref || s_state->fb->bad) { + return; + } + + //check if there is enough space in the frame buffer for the new data + size_t buf_len = s_state->width * s_state->fb_bytes_per_pixel / s_state->dma_per_line; + size_t fb_pos = s_state->dma_filtered_count * buf_len; + if(fb_pos > s_state->fb_size - buf_len) { + //size_t processed = s_state->dma_received_count * buf_len; + //ets_printf("[%s:%u] ovf pos: %u, processed: %u\n", __FUNCTION__, __LINE__, fb_pos, processed); + return; + } + + //convert I2S DMA buffer to pixel data + (*s_state->dma_filter)(s_state->dma_buf[buf_idx], &s_state->dma_desc[buf_idx], s_state->fb->buf + fb_pos); + + //first frame buffer + if(!s_state->dma_filtered_count) { + //check for correct JPEG header + 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); + s_state->fb->bad = 1; + return; + } + } + //set the frame properties + s_state->fb->width = resolution[s_state->sensor.status.framesize].width; + s_state->fb->height = resolution[s_state->sensor.status.framesize].height; + s_state->fb->format = s_state->sensor.pixformat; + + uint64_t us = (uint64_t)esp_timer_get_time(); + s_state->fb->timestamp.tv_sec = us / 1000000UL; + s_state->fb->timestamp.tv_usec = us % 1000000UL; + } + s_state->dma_filtered_count++; +} + +static void IRAM_ATTR dma_filter_task(void *pvParameters) +{ + s_state->dma_filtered_count = 0; + while (true) { + size_t buf_idx; + if(xQueueReceive(s_state->data_ready, &buf_idx, portMAX_DELAY) == pdTRUE) { + if (buf_idx == SIZE_MAX) { + //this is the end of the frame + dma_finish_frame(); + } else { + dma_filter_buffer(buf_idx); + } + } + } +} + +static void IRAM_ATTR dma_filter_jpeg(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst) +{ + size_t end = dma_desc->length / sizeof(dma_elem_t) / 4; + // manually unrolling 4 iterations of the loop here + for (size_t i = 0; i < end; ++i) { + dst[0] = src[0].sample1; + dst[1] = src[1].sample1; + dst[2] = src[2].sample1; + dst[3] = src[3].sample1; + src += 4; + dst += 4; + } +} + +static void IRAM_ATTR dma_filter_grayscale(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst) +{ + size_t end = dma_desc->length / sizeof(dma_elem_t) / 4; + for (size_t i = 0; i < end; ++i) { + // manually unrolling 4 iterations of the loop here + dst[0] = src[0].sample1; + dst[1] = src[1].sample1; + dst[2] = src[2].sample1; + dst[3] = src[3].sample1; + src += 4; + dst += 4; + } +} + +static void IRAM_ATTR dma_filter_grayscale_highspeed(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst) +{ + size_t end = dma_desc->length / sizeof(dma_elem_t) / 8; + for (size_t i = 0; i < end; ++i) { + // manually unrolling 4 iterations of the loop here + dst[0] = src[0].sample1; + dst[1] = src[2].sample1; + dst[2] = src[4].sample1; + dst[3] = src[6].sample1; + src += 8; + dst += 4; + } + // the final sample of a line in SM_0A0B_0B0C sampling mode needs special handling + if ((dma_desc->length & 0x7) != 0) { + dst[0] = src[0].sample1; + dst[1] = src[2].sample1; + } +} + +static void IRAM_ATTR dma_filter_yuyv(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst) +{ + size_t end = dma_desc->length / sizeof(dma_elem_t) / 4; + for (size_t i = 0; i < end; ++i) { + dst[0] = src[0].sample1;//y0 + dst[1] = src[0].sample2;//u + dst[2] = src[1].sample1;//y1 + dst[3] = src[1].sample2;//v + + dst[4] = src[2].sample1;//y0 + dst[5] = src[2].sample2;//u + dst[6] = src[3].sample1;//y1 + dst[7] = src[3].sample2;//v + src += 4; + dst += 8; + } +} + +static void IRAM_ATTR dma_filter_yuyv_highspeed(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst) +{ + size_t end = dma_desc->length / sizeof(dma_elem_t) / 8; + for (size_t i = 0; i < end; ++i) { + dst[0] = src[0].sample1;//y0 + dst[1] = src[1].sample1;//u + dst[2] = src[2].sample1;//y1 + dst[3] = src[3].sample1;//v + + dst[4] = src[4].sample1;//y0 + dst[5] = src[5].sample1;//u + dst[6] = src[6].sample1;//y1 + dst[7] = src[7].sample1;//v + src += 8; + dst += 8; + } + if ((dma_desc->length & 0x7) != 0) { + dst[0] = src[0].sample1;//y0 + dst[1] = src[1].sample1;//u + dst[2] = src[2].sample1;//y1 + dst[3] = src[2].sample2;//v + } +} + +static void IRAM_ATTR dma_filter_rgb888(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst) +{ + size_t end = dma_desc->length / sizeof(dma_elem_t) / 4; + uint8_t lb, hb; + for (size_t i = 0; i < end; ++i) { + hb = src[0].sample1; + lb = src[0].sample2; + dst[0] = (lb & 0x1F) << 3; + dst[1] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3; + dst[2] = hb & 0xF8; + + hb = src[1].sample1; + lb = src[1].sample2; + dst[3] = (lb & 0x1F) << 3; + dst[4] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3; + dst[5] = hb & 0xF8; + + hb = src[2].sample1; + lb = src[2].sample2; + dst[6] = (lb & 0x1F) << 3; + dst[7] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3; + dst[8] = hb & 0xF8; + + hb = src[3].sample1; + lb = src[3].sample2; + dst[9] = (lb & 0x1F) << 3; + dst[10] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3; + dst[11] = hb & 0xF8; + src += 4; + dst += 12; + } +} + +static void IRAM_ATTR dma_filter_rgb888_highspeed(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst) +{ + size_t end = dma_desc->length / sizeof(dma_elem_t) / 8; + uint8_t lb, hb; + for (size_t i = 0; i < end; ++i) { + hb = src[0].sample1; + lb = src[1].sample1; + dst[0] = (lb & 0x1F) << 3; + dst[1] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3; + dst[2] = hb & 0xF8; + + hb = src[2].sample1; + lb = src[3].sample1; + dst[3] = (lb & 0x1F) << 3; + dst[4] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3; + dst[5] = hb & 0xF8; + + hb = src[4].sample1; + lb = src[5].sample1; + dst[6] = (lb & 0x1F) << 3; + dst[7] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3; + dst[8] = hb & 0xF8; + + hb = src[6].sample1; + lb = src[7].sample1; + dst[9] = (lb & 0x1F) << 3; + dst[10] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3; + dst[11] = hb & 0xF8; + + src += 8; + dst += 12; + } + if ((dma_desc->length & 0x7) != 0) { + hb = src[0].sample1; + lb = src[1].sample1; + dst[0] = (lb & 0x1F) << 3; + dst[1] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3; + dst[2] = hb & 0xF8; + + hb = src[2].sample1; + lb = src[2].sample2; + dst[3] = (lb & 0x1F) << 3; + dst[4] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3; + dst[5] = hb & 0xF8; + } +} + +/* + * Public Methods + * */ + +esp_err_t camera_probe(const camera_config_t* config, camera_model_t* out_camera_model) +{ + if (s_state != NULL) { + return ESP_ERR_INVALID_STATE; + } + + s_state = (camera_state_t*) calloc(sizeof(*s_state), 1); + if (!s_state) { + return ESP_ERR_NO_MEM; + } + + 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_pwdn >= 0) { + ESP_LOGD(TAG, "Resetting camera by power down line"); + gpio_config_t conf = { 0 }; + conf.pin_bit_mask = 1LL << config->pin_pwdn; + conf.mode = GPIO_MODE_OUTPUT; + gpio_config(&conf); + + // carefull, logic is inverted compared to reset pin + gpio_set_level(config->pin_pwdn, 1); + vTaskDelay(10 / portTICK_PERIOD_MS); + gpio_set_level(config->pin_pwdn, 0); + vTaskDelay(10 / portTICK_PERIOD_MS); + } + + if(config->pin_reset >= 0) { + ESP_LOGD(TAG, "Resetting camera"); + gpio_config_t conf = { 0 }; + conf.pin_bit_mask = 1LL << config->pin_reset; + conf.mode = GPIO_MODE_OUTPUT; + gpio_config(&conf); + + gpio_set_level(config->pin_reset, 0); + vTaskDelay(10 / portTICK_PERIOD_MS); + gpio_set_level(config->pin_reset, 1); + vTaskDelay(10 / portTICK_PERIOD_MS); + } + + ESP_LOGD(TAG, "Searching for camera address"); + vTaskDelay(10 / portTICK_PERIOD_MS); + uint8_t slv_addr = SCCB_Probe(); + if (slv_addr == 0) { + *out_camera_model = CAMERA_NONE; + camera_disable_out_clock(); + return ESP_ERR_CAMERA_NOT_DETECTED; + } + + //slv_addr = 0x30; + ESP_LOGD(TAG, "Detected camera at address=0x%02x", slv_addr); + sensor_id_t* id = &s_state->sensor.id; + +#if CONFIG_OV2640_SUPPORT + if (slv_addr == 0x30) { + ESP_LOGD(TAG, "Resetting OV2640"); + //camera might be OV2640. try to reset it + SCCB_Write(0x30, 0xFF, 0x01);//bank sensor + SCCB_Write(0x30, 0x12, 0x80);//reset + vTaskDelay(10 / portTICK_PERIOD_MS); + slv_addr = SCCB_Probe(); + } +#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(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 { +#endif + id->PID = SCCB_Read(s_state->sensor.slv_addr, REG_PID); + id->VER = SCCB_Read(s_state->sensor.slv_addr, REG_VER); + id->MIDL = SCCB_Read(s_state->sensor.slv_addr, REG_MIDL); + id->MIDH = SCCB_Read(s_state->sensor.slv_addr, REG_MIDH); + vTaskDelay(10 / portTICK_PERIOD_MS); + 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) + } +#endif + + + switch (id->PID) { +#if CONFIG_OV2640_SUPPORT + case OV2640_PID: + *out_camera_model = CAMERA_OV2640; + ov2640_init(&s_state->sensor); + break; +#endif +#if CONFIG_OV7725_SUPPORT + case OV7725_PID: + *out_camera_model = CAMERA_OV7725; + ov7725_init(&s_state->sensor); + break; +#endif +#if CONFIG_OV3660_SUPPORT + case OV3660_PID: + *out_camera_model = CAMERA_OV3660; + ov3660_init(&s_state->sensor); + break; +#endif +#if CONFIG_OV5640_SUPPORT + case OV5640_PID: + *out_camera_model = CAMERA_OV5640; + ov5640_init(&s_state->sensor); + break; +#endif + default: + id->PID = 0; + *out_camera_model = CAMERA_UNKNOWN; + camera_disable_out_clock(); + ESP_LOGE(TAG, "Detected camera not supported."); + return ESP_ERR_CAMERA_NOT_SUPPORTED; + } + + ESP_LOGD(TAG, "Doing SW reset of sensor"); + s_state->sensor.reset(&s_state->sensor); + + return ESP_OK; +} + +esp_err_t camera_init(const camera_config_t* config) +{ + if (!s_state) { + return ESP_ERR_INVALID_STATE; + } + if (s_state->sensor.id.PID == 0) { + return ESP_ERR_CAMERA_NOT_SUPPORTED; + } + memcpy(&s_state->config, config, sizeof(*config)); + esp_err_t err = ESP_OK; + framesize_t frame_size = (framesize_t) config->frame_size; + pixformat_t pix_format = (pixformat_t) config->pixel_format; + + switch (s_state->sensor.id.PID) { +#if CONFIG_OV2640_SUPPORT + case OV2640_PID: + if (frame_size > FRAMESIZE_UXGA) { + frame_size = FRAMESIZE_UXGA; + } + break; +#endif +#if CONFIG_OV7725_SUPPORT + case OV7725_PID: + if (frame_size > FRAMESIZE_VGA) { + frame_size = FRAMESIZE_VGA; + } + break; +#endif +#if CONFIG_OV3660_SUPPORT + case OV3660_PID: + if (frame_size > FRAMESIZE_QXGA) { + frame_size = FRAMESIZE_QXGA; + } + break; +#endif +#if CONFIG_OV5640_SUPPORT + case OV5640_PID: + if (frame_size > FRAMESIZE_QSXGA) { + frame_size = FRAMESIZE_QSXGA; + } + break; +#endif + default: + return ESP_ERR_CAMERA_NOT_SUPPORTED; + } + + s_state->width = resolution[frame_size].width; + s_state->height = resolution[frame_size].height; + + 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 (is_hs_mode()) { + 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 = 1; // camera sends Y8 + } else { + if (is_hs_mode() && s_state->sensor.id.PID != OV7725_PID) { + s_state->sampling_mode = SM_0A00_0B00; + s_state->dma_filter = &dma_filter_grayscale_highspeed; + } else { + s_state->sampling_mode = SM_0A0B_0C0D; + s_state->dma_filter = &dma_filter_grayscale; + } + s_state->in_bytes_per_pixel = 2; // camera sends YU/YV + } + 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 + } 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; + s_state->dma_filter = &dma_filter_rgb888_highspeed; + } else { + s_state->sampling_mode = SM_0A0B_0C0D; + s_state->dma_filter = &dma_filter_rgb888; + } + 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) { + ESP_LOGE(TAG, "JPEG format is only supported for ov2640, ov3660 and ov5640"); + err = ESP_ERR_NOT_SUPPORTED; + goto fail; + } + int qp = config->jpeg_quality; + int compression_ratio_bound = 1; + if (qp > 10) { + compression_ratio_bound = 16; + } else if (qp > 5) { + compression_ratio_bound = 10; + } else { + compression_ratio_bound = 4; + } + (*s_state->sensor.set_quality)(&s_state->sensor, qp); + s_state->in_bytes_per_pixel = 2; + s_state->fb_bytes_per_pixel = 2; + s_state->fb_size = (s_state->width * s_state->height * s_state->fb_bytes_per_pixel) / compression_ratio_bound; + s_state->dma_filter = &dma_filter_jpeg; + s_state->sampling_mode = SM_0A00_0B00; + } else { + ESP_LOGE(TAG, "Requested format is not supported"); + err = ESP_ERR_NOT_SUPPORTED; + goto fail; + } + + ESP_LOGD(TAG, "in_bpp: %d, fb_bpp: %d, fb_size: %d, mode: %d, width: %d height: %d", + s_state->in_bytes_per_pixel, s_state->fb_bytes_per_pixel, + s_state->fb_size, s_state->sampling_mode, + s_state->width, s_state->height); + + i2s_init(); + + err = dma_desc_init(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize I2S and DMA"); + goto fail; + } + + //s_state->fb_size = 75 * 1024; + err = camera_fb_init(s_state->config.fb_count); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to allocate frame buffer"); + goto fail; + } + + s_state->data_ready = xQueueCreate(16, sizeof(size_t)); + if (s_state->data_ready == NULL) { + ESP_LOGE(TAG, "Failed to dma queue"); + err = ESP_ERR_NO_MEM; + goto fail; + } + + if(s_state->config.fb_count == 1) { + s_state->frame_ready = xSemaphoreCreateBinary(); + if (s_state->frame_ready == NULL) { + ESP_LOGE(TAG, "Failed to create semaphore"); + err = ESP_ERR_NO_MEM; + goto fail; + } + } else { + s_state->fb_in = xQueueCreate(s_state->config.fb_count, sizeof(camera_fb_t *)); + s_state->fb_out = xQueueCreate(1, sizeof(camera_fb_t *)); + if (s_state->fb_in == NULL || s_state->fb_out == NULL) { + ESP_LOGE(TAG, "Failed to fb queues"); + err = ESP_ERR_NO_MEM; + goto fail; + } + } + + //ToDo: core affinity? +#if CONFIG_CAMERA_CORE0 + if (!xTaskCreatePinnedToCore(&dma_filter_task, "dma_filter", 4096, NULL, 10, &s_state->dma_filter_task, 0)) +#elif CONFIG_CAMERA_CORE1 + if (!xTaskCreatePinnedToCore(&dma_filter_task, "dma_filter", 4096, NULL, 10, &s_state->dma_filter_task, 1)) +#else + if (!xTaskCreate(&dma_filter_task, "dma_filter", 4096, NULL, 10, &s_state->dma_filter_task)) +#endif + { + ESP_LOGE(TAG, "Failed to create DMA filter task"); + err = ESP_ERR_NO_MEM; + goto fail; + } + + 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; + } + err = gpio_isr_handler_add(s_state->config.pin_vsync, &vsync_isr, NULL); + if (err != ESP_OK) { + ESP_LOGE(TAG, "vsync_isr_handler_add failed (%x)", err); + goto fail; + } + + s_state->sensor.status.framesize = frame_size; + s_state->sensor.pixformat = pix_format; + ESP_LOGD(TAG, "Setting frame size to %dx%d", s_state->width, s_state->height); + if (s_state->sensor.set_framesize(&s_state->sensor, frame_size) != 0) { + ESP_LOGE(TAG, "Failed to set frame size"); + err = ESP_ERR_CAMERA_FAILED_TO_SET_FRAME_SIZE; + goto fail; + } + s_state->sensor.set_pixformat(&s_state->sensor, pix_format); + + if (s_state->sensor.id.PID == OV2640_PID) { + s_state->sensor.set_gainceiling(&s_state->sensor, GAINCEILING_2X); + s_state->sensor.set_bpc(&s_state->sensor, false); + s_state->sensor.set_wpc(&s_state->sensor, true); + s_state->sensor.set_lenc(&s_state->sensor, true); + } + + if (skip_frame()) { + err = ESP_ERR_CAMERA_FAILED_TO_SET_OUT_FORMAT; + goto fail; + } + //todo: for some reason the first set of the quality does not work. + if (pix_format == PIXFORMAT_JPEG) { + (*s_state->sensor.set_quality)(&s_state->sensor, config->jpeg_quality); + } + s_state->sensor.init_status(&s_state->sensor); + return ESP_OK; + +fail: + esp_camera_deinit(); + return err; +} + +esp_err_t esp_camera_init(const camera_config_t* config) +{ + camera_model_t camera_model = CAMERA_NONE; + 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"); + 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"); + } else if (camera_model == CAMERA_OV3660) { + ESP_LOGD(TAG, "Detected OV3660 camera"); + } else if (camera_model == CAMERA_OV5640) { + ESP_LOGD(TAG, "Detected OV5640 camera"); + } else { + ESP_LOGE(TAG, "Camera not supported"); + err = ESP_ERR_CAMERA_NOT_SUPPORTED; + goto fail; + } + err = camera_init(config); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Camera init failed with error 0x%x", err); + return err; + } + return ESP_OK; + +fail: + free(s_state); + s_state = NULL; + camera_disable_out_clock(); + return err; +} + +esp_err_t esp_camera_deinit() +{ + if (s_state == NULL) { + return ESP_ERR_INVALID_STATE; + } + if (s_state->dma_filter_task) { + vTaskDelete(s_state->dma_filter_task); + } + if (s_state->data_ready) { + vQueueDelete(s_state->data_ready); + } + if (s_state->fb_in) { + vQueueDelete(s_state->fb_in); + } + if (s_state->fb_out) { + vQueueDelete(s_state->fb_out); + } + if (s_state->frame_ready) { + vSemaphoreDelete(s_state->frame_ready); + } + gpio_isr_handler_remove(s_state->config.pin_vsync); + if (s_state->i2s_intr_handle) { + esp_intr_disable(s_state->i2s_intr_handle); + esp_intr_free(s_state->i2s_intr_handle); + } + dma_desc_deinit(); + camera_fb_deinit(); + free(s_state); + s_state = NULL; + camera_disable_out_clock(); + periph_module_disable(PERIPH_I2S0_MODULE); + return ESP_OK; +} + +#define FB_GET_TIMEOUT (4000 / portTICK_PERIOD_MS) + +camera_fb_t* esp_camera_fb_get() +{ + if (s_state == NULL) { + return NULL; + } + if(!I2S0.conf.rx_start) { + if(s_state->config.fb_count > 1) { + ESP_LOGD(TAG, "i2s_run"); + } + if (i2s_run() != 0) { + return NULL; + } + } + bool need_yield = false; + if (s_state->config.fb_count == 1) { + if (xSemaphoreTake(s_state->frame_ready, FB_GET_TIMEOUT) != pdTRUE){ + i2s_stop(&need_yield); + ESP_LOGE(TAG, "Failed to get the frame on time!"); + return NULL; + } + return (camera_fb_t*)s_state->fb; + } + camera_fb_int_t * fb = NULL; + if(s_state->fb_out) { + if (xQueueReceive(s_state->fb_out, &fb, FB_GET_TIMEOUT) != pdTRUE) { + i2s_stop(&need_yield); + ESP_LOGE(TAG, "Failed to get the frame on time!"); + return NULL; + } + } + return (camera_fb_t*)fb; +} + +void esp_camera_fb_return(camera_fb_t * fb) +{ + if(fb == NULL || s_state == NULL || s_state->config.fb_count == 1 || s_state->fb_in == NULL) { + return; + } + xQueueSend(s_state->fb_in, &fb, portMAX_DELAY); +} + +sensor_t * esp_camera_sensor_get() +{ + if (s_state == NULL) { + return NULL; + } + return &s_state->sensor; +} + +esp_err_t esp_camera_save_to_nvs(const char *key) +{ +#ifdef ESP_IDF_VERSION_MAJOR + nvs_handle_t handle; +#else + nvs_handle handle; +#endif + esp_err_t ret = nvs_open(key,NVS_READWRITE,&handle); + + if (ret == ESP_OK) { + sensor_t *s = esp_camera_sensor_get(); + if (s != NULL) { + ret = nvs_set_blob(handle,CAMERA_SENSOR_NVS_KEY,&s->status,sizeof(camera_status_t)); + if (ret == ESP_OK) { + uint8_t pf = s->pixformat; + ret = nvs_set_u8(handle,CAMERA_PIXFORMAT_NVS_KEY,pf); + } + return ret; + } else { + return ESP_ERR_CAMERA_NOT_DETECTED; + } + nvs_close(handle); + return ret; + } else { + return ret; + } +} + +esp_err_t esp_camera_load_from_nvs(const char *key) +{ +#ifdef ESP_IDF_VERSION_MAJOR + nvs_handle_t handle; +#else + nvs_handle handle; +#endif + uint8_t pf; + + esp_err_t ret = nvs_open(key,NVS_READWRITE,&handle); + + if (ret == ESP_OK) { + sensor_t *s = esp_camera_sensor_get(); + camera_status_t st; + if (s != NULL) { + size_t size = sizeof(camera_status_t); + ret = nvs_get_blob(handle,CAMERA_SENSOR_NVS_KEY,&st,&size); + if (ret == ESP_OK) { + s->set_ae_level(s,st.ae_level); + s->set_aec2(s,st.aec2); + s->set_aec_value(s,st.aec_value); + s->set_agc_gain(s,st.agc_gain); + s->set_awb_gain(s,st.awb_gain); + s->set_bpc(s,st.bpc); + s->set_brightness(s,st.brightness); + s->set_colorbar(s,st.colorbar); + s->set_contrast(s,st.contrast); + s->set_dcw(s,st.dcw); + s->set_denoise(s,st.denoise); + s->set_exposure_ctrl(s,st.aec); + s->set_framesize(s,st.framesize); + s->set_gain_ctrl(s,st.agc); + s->set_gainceiling(s,st.gainceiling); + s->set_hmirror(s,st.hmirror); + s->set_lenc(s,st.lenc); + s->set_quality(s,st.quality); + s->set_raw_gma(s,st.raw_gma); + s->set_saturation(s,st.saturation); + s->set_sharpness(s,st.sharpness); + s->set_special_effect(s,st.special_effect); + s->set_vflip(s,st.vflip); + s->set_wb_mode(s,st.wb_mode); + s->set_whitebal(s,st.awb); + s->set_wpc(s,st.wpc); + } + ret = nvs_get_u8(handle,CAMERA_PIXFORMAT_NVS_KEY,&pf); + if (ret == ESP_OK) { + s->set_pixformat(s,pf); + } + } else { + return ESP_ERR_CAMERA_NOT_DETECTED; + } + nvs_close(handle); + return ret; + } else { + ESP_LOGW(TAG,"Error (%d) opening nvs key \"%s\"",ret,key); + return ret; + } +} diff --git a/esp32-cam/camera_common.h b/esp32-cam/camera_common.h new file mode 100644 index 0000000..8a2db5c --- /dev/null +++ b/esp32-cam/camera_common.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include +#include "esp_err.h" +#include "esp_intr_alloc.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/task.h" +#include "esp_camera.h" +#include "sensor.h" + +#include "esp_system.h" +#if ESP_IDF_VERSION_MAJOR >= 4 // IDF 4+ +#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 +#include "esp32/rom/lldesc.h" +#else +#error Target CONFIG_IDF_TARGET is not supported +#endif +#else // ESP32 Before IDF 4.0 +#include "rom/lldesc.h" +#endif + +typedef union { + struct { + uint8_t sample2; + uint8_t unused2; + uint8_t sample1; + uint8_t unused1; + }; + uint32_t val; +} dma_elem_t; + +typedef enum { + /* camera sends byte sequence: s1, s2, s3, s4, ... + * fifo receives: 00 s1 00 s2, 00 s2 00 s3, 00 s3 00 s4, ... + */ + SM_0A0B_0B0C = 0, + /* camera sends byte sequence: s1, s2, s3, s4, ... + * fifo receives: 00 s1 00 s2, 00 s3 00 s4, ... + */ + SM_0A0B_0C0D = 1, + /* camera sends byte sequence: s1, s2, s3, s4, ... + * fifo receives: 00 s1 00 00, 00 s2 00 00, 00 s3 00 00, ... + */ + SM_0A00_0B00 = 3, +} i2s_sampling_mode_t; + diff --git a/esp32-cam/camera_pins.h b/esp32-cam/camera_pins.h new file mode 100644 index 0000000..7855722 --- /dev/null +++ b/esp32-cam/camera_pins.h @@ -0,0 +1,99 @@ + +#if defined(CAMERA_MODEL_WROVER_KIT) +#define PWDN_GPIO_NUM -1 +#define RESET_GPIO_NUM -1 +#define XCLK_GPIO_NUM 21 +#define SIOD_GPIO_NUM 26 +#define SIOC_GPIO_NUM 27 + +#define Y9_GPIO_NUM 35 +#define Y8_GPIO_NUM 34 +#define Y7_GPIO_NUM 39 +#define Y6_GPIO_NUM 36 +#define Y5_GPIO_NUM 19 +#define Y4_GPIO_NUM 18 +#define Y3_GPIO_NUM 5 +#define Y2_GPIO_NUM 4 +#define VSYNC_GPIO_NUM 25 +#define HREF_GPIO_NUM 23 +#define PCLK_GPIO_NUM 22 + +#elif defined(CAMERA_MODEL_ESP_EYE) +#define PWDN_GPIO_NUM -1 +#define RESET_GPIO_NUM -1 +#define XCLK_GPIO_NUM 4 +#define SIOD_GPIO_NUM 18 +#define SIOC_GPIO_NUM 23 + +#define Y9_GPIO_NUM 36 +#define Y8_GPIO_NUM 37 +#define Y7_GPIO_NUM 38 +#define Y6_GPIO_NUM 39 +#define Y5_GPIO_NUM 35 +#define Y4_GPIO_NUM 14 +#define Y3_GPIO_NUM 13 +#define Y2_GPIO_NUM 34 +#define VSYNC_GPIO_NUM 5 +#define HREF_GPIO_NUM 27 +#define PCLK_GPIO_NUM 25 + +#elif defined(CAMERA_MODEL_M5STACK_PSRAM) +#define PWDN_GPIO_NUM -1 +#define RESET_GPIO_NUM 15 +#define XCLK_GPIO_NUM 27 +#define SIOD_GPIO_NUM 25 +#define SIOC_GPIO_NUM 23 + +#define Y9_GPIO_NUM 19 +#define Y8_GPIO_NUM 36 +#define Y7_GPIO_NUM 18 +#define Y6_GPIO_NUM 39 +#define Y5_GPIO_NUM 5 +#define Y4_GPIO_NUM 34 +#define Y3_GPIO_NUM 35 +#define Y2_GPIO_NUM 32 +#define VSYNC_GPIO_NUM 22 +#define HREF_GPIO_NUM 26 +#define PCLK_GPIO_NUM 21 + +#elif defined(CAMERA_MODEL_M5STACK_WIDE) +#define PWDN_GPIO_NUM -1 +#define RESET_GPIO_NUM 15 +#define XCLK_GPIO_NUM 27 +#define SIOD_GPIO_NUM 22 +#define SIOC_GPIO_NUM 23 + +#define Y9_GPIO_NUM 19 +#define Y8_GPIO_NUM 36 +#define Y7_GPIO_NUM 18 +#define Y6_GPIO_NUM 39 +#define Y5_GPIO_NUM 5 +#define Y4_GPIO_NUM 34 +#define Y3_GPIO_NUM 35 +#define Y2_GPIO_NUM 32 +#define VSYNC_GPIO_NUM 25 +#define HREF_GPIO_NUM 26 +#define PCLK_GPIO_NUM 21 + +#elif defined(CAMERA_MODEL_AI_THINKER) +#define PWDN_GPIO_NUM 32 +#define RESET_GPIO_NUM -1 +#define XCLK_GPIO_NUM 0 +#define SIOD_GPIO_NUM 26 +#define SIOC_GPIO_NUM 27 + +#define Y9_GPIO_NUM 35 +#define Y8_GPIO_NUM 34 +#define Y7_GPIO_NUM 39 +#define Y6_GPIO_NUM 36 +#define Y5_GPIO_NUM 21 +#define Y4_GPIO_NUM 19 +#define Y3_GPIO_NUM 18 +#define Y2_GPIO_NUM 5 +#define VSYNC_GPIO_NUM 25 +#define HREF_GPIO_NUM 23 +#define PCLK_GPIO_NUM 22 + +#else +#error "Camera model not selected" +#endif diff --git a/esp32-cam/esp32-cam.ino b/esp32-cam/esp32-cam.ino new file mode 100644 index 0000000..9141921 --- /dev/null +++ b/esp32-cam/esp32-cam.ino @@ -0,0 +1,492 @@ +/* + + This is a simple MJPEG streaming webserver implemented for AI-Thinker ESP32-CAM + and ESP-EYE modules. + This is tested to work with VLC and Blynk video widget and can support up to 10 + simultaneously connected streaming clients. + Simultaneous streaming is implemented with FreeRTOS tasks. + + Inspired by and based on this Instructable: $9 RTSP Video Streamer Using the ESP32-CAM Board + (https://www.instructables.com/id/9-RTSP-Video-Streamer-Using-the-ESP32-CAM-Board/) + + Board: AI-Thinker ESP32-CAM or ESP-EYE + Compile as: + ESP32 Dev Module + CPU Freq: 240 + Flash Freq: 80 + Flash mode: QIO + Flash Size: 4Mb + Partrition: Minimal SPIFFS + PSRAM: Enabled +*/ + +// ESP32 has two cores: APPlication core and PROcess core (the one that runs ESP32 SDK stack) +#define APP_CPU 1 +#define PRO_CPU 0 + +#include "esp_camera.h" +#include "ov2640.h" +#include +#include +#include + +#include +#include +#include +#include + +// Select camera model +//#define CAMERA_MODEL_WROVER_KIT +//#define CAMERA_MODEL_ESP_EYE +//#define CAMERA_MODEL_M5STACK_PSRAM +//#define CAMERA_MODEL_M5STACK_WIDE +#define CAMERA_MODEL_AI_THINKER + +#include "camera_pins.h" +#include "home_wifi_multi.h" + +//OV2640 cam; + +WebServer server(80); + +// ===== rtos task handles ========================= +// Streaming is implemented with 3 tasks: +TaskHandle_t tMjpeg; // handles client connections to the webserver +TaskHandle_t tCam; // handles getting picture frames from the camera and storing them locally +TaskHandle_t tStream; // actually streaming frames to all connected clients + +// frameSync semaphore is used to prevent streaming buffer as it is replaced with the next frame +SemaphoreHandle_t frameSync = NULL; + +// Queue stores currently connected clients to whom we are streaming +QueueHandle_t streamingClients; + +// We will try to achieve 25 FPS frame rate +const int FPS = 25; + +// We will handle web client requests every 50 ms (20 Hz) +const int WSINTERVAL = 50; + + +// ======== Server Connection Handler Task ========================== +void mjpegCB(void* pvParameters) { + TickType_t xLastWakeTime; + const TickType_t xFrequency = pdMS_TO_TICKS(WSINTERVAL); + + // Creating frame synchronization semaphore and initializing it + frameSync = xSemaphoreCreateBinary(); + xSemaphoreGive( frameSync ); + + // Creating a queue to track all connected clients + streamingClients = xQueueCreate( 10, sizeof(WiFiClient*) ); + + //=== setup section ================== + + // Creating RTOS task for grabbing frames from the camera + xTaskCreatePinnedToCore( + camCB, // callback + "cam", // name + 4096, // stacj size + NULL, // parameters + 2, // priority + &tCam, // RTOS task handle + APP_CPU); // core + + // Creating task to push the stream to all connected clients + xTaskCreatePinnedToCore( + streamCB, + "strmCB", + 4096, + NULL, //(void*) handler, + 2, + &tStream, + APP_CPU); + + // Registering webserver handling routines + server.on("/mjpeg/1", HTTP_GET, handleJPGSstream); + server.on("/jpg", HTTP_GET, handleJPG); + server.onNotFound(handleNotFound); + + // Starting webserver + server.begin(); + + //=== loop() section =================== + xLastWakeTime = xTaskGetTickCount(); + for (;;) { + server.handleClient(); + + // After every server client handling request, we let other tasks run and then pause + taskYIELD(); + vTaskDelayUntil(&xLastWakeTime, xFrequency); + } +} + + +// Commonly used variables: +volatile size_t camSize; // size of the current frame, byte +volatile char* camBuf; // pointer to the current frame + + +// ==== RTOS task to grab frames from the camera ========================= +void camCB(void* pvParameters) { + + TickType_t xLastWakeTime; + + // A running interval associated with currently desired frame rate + const TickType_t xFrequency = pdMS_TO_TICKS(1000 / FPS); + + // Mutex for the critical section of swithing the active frames around + portMUX_TYPE xSemaphore = portMUX_INITIALIZER_UNLOCKED; + + // Pointers to the 2 frames, their respective sizes and index of the current frame + char* fbs[2] = { NULL, NULL }; + size_t fSize[2] = { 0, 0 }; + int ifb = 0; + + //=== loop() section =================== + xLastWakeTime = xTaskGetTickCount(); + + for (;;) { + + // Grab a frame from the camera and query its size + camera_fb_t* fb = NULL; + + fb = esp_camera_fb_get(); + size_t s = fb->len; + + // If frame size is more that we have previously allocated - request 125% of the current frame space + if (s > fSize[ifb]) { + fSize[ifb] = s * 4 / 3; + fbs[ifb] = allocateMemory(fbs[ifb], fSize[ifb]); + } + + // Copy current frame into local buffer + char* b = (char *)fb->buf; + memcpy(fbs[ifb], b, s); + esp_camera_fb_return(fb); + + // Let other tasks run and wait until the end of the current frame rate interval (if any time left) + taskYIELD(); + vTaskDelayUntil(&xLastWakeTime, xFrequency); + + // Only switch frames around if no frame is currently being streamed to a client + // Wait on a semaphore until client operation completes + xSemaphoreTake( frameSync, portMAX_DELAY ); + + // Do not allow interrupts while switching the current frame + taskENTER_CRITICAL(&xSemaphore); + camBuf = fbs[ifb]; + camSize = s; + ifb = (++ifb) & 1; // this should produce 1, 0, 1, 0, 1 ... sequence + taskEXIT_CRITICAL(&xSemaphore); + + // Let anyone waiting for a frame know that the frame is ready + xSemaphoreGive( frameSync ); + + // Technically only needed once: let the streaming task know that we have at least one frame + // and it could start sending frames to the clients, if any + xTaskNotifyGive( tStream ); + + // Immediately let other (streaming) tasks run + taskYIELD(); + + // If streaming task has suspended itself (no active clients to stream to) + // there is no need to grab frames from the camera. We can save some juice + // by suspedning the tasks + if ( eTaskGetState( tStream ) == eSuspended ) { + vTaskSuspend(NULL); // passing NULL means "suspend yourself" + } + } +} + + +// ==== Memory allocator that takes advantage of PSRAM if present ======================= +char* allocateMemory(char* aPtr, size_t aSize) { + + // Since current buffer is too smal, free it + if (aPtr != NULL) free(aPtr); + + + size_t freeHeap = ESP.getFreeHeap(); + char* ptr = NULL; + + // If memory requested is more than 2/3 of the currently free heap, try PSRAM immediately + if ( aSize > freeHeap * 2 / 3 ) { + if ( psramFound() && ESP.getFreePsram() > aSize ) { + ptr = (char*) ps_malloc(aSize); + } + } + else { + // Enough free heap - let's try allocating fast RAM as a buffer + ptr = (char*) malloc(aSize); + + // If allocation on the heap failed, let's give PSRAM one more chance: + if ( ptr == NULL && psramFound() && ESP.getFreePsram() > aSize) { + ptr = (char*) ps_malloc(aSize); + } + } + + // Finally, if the memory pointer is NULL, we were not able to allocate any memory, and that is a terminal condition. + if (ptr == NULL) { + ESP.restart(); + } + return ptr; +} + + +// ==== STREAMING ====================================================== +const char HEADER[] = "HTTP/1.1 200 OK\r\n" \ + "Access-Control-Allow-Origin: *\r\n" \ + "Content-Type: multipart/x-mixed-replace; boundary=123456789000000000000987654321\r\n"; +const char BOUNDARY[] = "\r\n--123456789000000000000987654321\r\n"; +const char CTNTTYPE[] = "Content-Type: image/jpeg\r\nContent-Length: "; +const int hdrLen = strlen(HEADER); +const int bdrLen = strlen(BOUNDARY); +const int cntLen = strlen(CTNTTYPE); + + +// ==== Handle connection request from clients =============================== +void handleJPGSstream(void) +{ + // Can only acommodate 10 clients. The limit is a default for WiFi connections + if ( !uxQueueSpacesAvailable(streamingClients) ) return; + + + // Create a new WiFi Client object to keep track of this one + WiFiClient* client = new WiFiClient(); + *client = server.client(); + + // Immediately send this client a header + client->write(HEADER, hdrLen); + client->write(BOUNDARY, bdrLen); + + // Push the client to the streaming queue + xQueueSend(streamingClients, (void *) &client, 0); + + // Wake up streaming tasks, if they were previously suspended: + if ( eTaskGetState( tCam ) == eSuspended ) vTaskResume( tCam ); + if ( eTaskGetState( tStream ) == eSuspended ) vTaskResume( tStream ); +} + + +// ==== Actually stream content to all connected clients ======================== +void streamCB(void * pvParameters) { + char buf[16]; + TickType_t xLastWakeTime; + TickType_t xFrequency; + + // Wait until the first frame is captured and there is something to send + // to clients + ulTaskNotifyTake( pdTRUE, /* Clear the notification value before exiting. */ + portMAX_DELAY ); /* Block indefinitely. */ + + xLastWakeTime = xTaskGetTickCount(); + for (;;) { + // Default assumption we are running according to the FPS + xFrequency = pdMS_TO_TICKS(1000 / FPS); + + // Only bother to send anything if there is someone watching + UBaseType_t activeClients = uxQueueMessagesWaiting(streamingClients); + if ( activeClients ) { + // Adjust the period to the number of connected clients + xFrequency /= activeClients; + + // Since we are sending the same frame to everyone, + // pop a client from the the front of the queue + WiFiClient *client; + xQueueReceive (streamingClients, (void*) &client, 0); + + // Check if this client is still connected. + + if (!client->connected()) { + // delete this client reference if s/he has disconnected + // and don't put it back on the queue anymore. Bye! + delete client; + } + else { + + // Ok. This is an actively connected client. + // Let's grab a semaphore to prevent frame changes while we + // are serving this frame + xSemaphoreTake( frameSync, portMAX_DELAY ); + + client->write(CTNTTYPE, cntLen); + sprintf(buf, "%d\r\n\r\n", camSize); + client->write(buf, strlen(buf)); + client->write((char*) camBuf, (size_t)camSize); + client->write(BOUNDARY, bdrLen); + + // Since this client is still connected, push it to the end + // of the queue for further processing + xQueueSend(streamingClients, (void *) &client, 0); + + // The frame has been served. Release the semaphore and let other tasks run. + // If there is a frame switch ready, it will happen now in between frames + xSemaphoreGive( frameSync ); + taskYIELD(); + } + } + else { + // Since there are no connected clients, there is no reason to waste battery running + vTaskSuspend(NULL); + } + // Let other tasks run after serving every client + taskYIELD(); + vTaskDelayUntil(&xLastWakeTime, xFrequency); + } +} + + + +const char JHEADER[] = "HTTP/1.1 200 OK\r\n" \ + "Content-disposition: inline; filename=capture.jpg\r\n" \ + "Content-type: image/jpeg\r\n\r\n"; +const int jhdLen = strlen(JHEADER); + +// ==== Serve up one JPEG frame ============================================= +void handleJPG(void) +{ + WiFiClient client = server.client(); + camera_fb_t* fb = esp_camera_fb_get(); + + if (!client.connected()) return; + client.write(JHEADER, jhdLen); + client.write((char*)fb->buf, fb->len); + esp_camera_fb_return(fb); +} + + +// ==== Handle invalid URL requests ============================================ +void handleNotFound() +{ + String message = "Server is running!\n\n"; + message += "URI: "; + message += server.uri(); + message += "\nMethod: "; + message += (server.method() == HTTP_GET) ? "GET" : "POST"; + message += "\nArguments: "; + message += server.args(); + message += "\n"; + server.send(200, "text / plain", message); +} + + + +// ==== SETUP method ================================================================== +void setup() +{ + + // Setup Serial connection: + Serial.begin(115200); + delay(1000); // wait for a second to let Serial connect + + + // Configure the camera +// camera_config_t config; +// config.ledc_channel = LEDC_CHANNEL_0; +// config.ledc_timer = LEDC_TIMER_0; +// config.pin_d0 = Y2_GPIO_NUM; +// config.pin_d1 = Y3_GPIO_NUM; +// config.pin_d2 = Y4_GPIO_NUM; +// config.pin_d3 = Y5_GPIO_NUM; +// config.pin_d4 = Y6_GPIO_NUM; +// config.pin_d5 = Y7_GPIO_NUM; +// config.pin_d6 = Y8_GPIO_NUM; +// config.pin_d7 = Y9_GPIO_NUM; +// config.pin_xclk = XCLK_GPIO_NUM; +// config.pin_pclk = PCLK_GPIO_NUM; +// config.pin_vsync = VSYNC_GPIO_NUM; +// config.pin_href = HREF_GPIO_NUM; +// config.pin_sscb_sda = SIOD_GPIO_NUM; +// config.pin_sscb_scl = SIOC_GPIO_NUM; +// config.pin_pwdn = PWDN_GPIO_NUM; +// config.pin_reset = RESET_GPIO_NUM; +// config.xclk_freq_hz = 20000000; +// config.pixel_format = PIXFORMAT_JPEG; +// +// // Frame parameters: pick one +// // config.frame_size = FRAMESIZE_UXGA; +// // config.frame_size = FRAMESIZE_SVGA; +// // config.frame_size = FRAMESIZE_QVGA; +// config.frame_size = FRAMESIZE_VGA; +// config.jpeg_quality = 12; +// config.fb_count = 2; + + static camera_config_t camera_config = { + .pin_pwdn = PWDN_GPIO_NUM, + .pin_reset = RESET_GPIO_NUM, + .pin_xclk = XCLK_GPIO_NUM, + .pin_sscb_sda = SIOD_GPIO_NUM, + .pin_sscb_scl = SIOC_GPIO_NUM, + .pin_d7 = Y9_GPIO_NUM, + .pin_d6 = Y8_GPIO_NUM, + .pin_d5 = Y7_GPIO_NUM, + .pin_d4 = Y6_GPIO_NUM, + .pin_d3 = Y5_GPIO_NUM, + .pin_d2 = Y4_GPIO_NUM, + .pin_d1 = Y3_GPIO_NUM, + .pin_d0 = Y2_GPIO_NUM, + .pin_vsync = VSYNC_GPIO_NUM, + .pin_href = HREF_GPIO_NUM, + .pin_pclk = PCLK_GPIO_NUM, + + .xclk_freq_hz = 20000000, + .ledc_timer = LEDC_TIMER_0, + .ledc_channel = LEDC_CHANNEL_0, + .pixel_format = PIXFORMAT_JPEG, + .frame_size = FRAMESIZE_VGA, + .jpeg_quality = 12, + .fb_count = 2 + }; + +#if defined(CAMERA_MODEL_ESP_EYE) + pinMode(13, INPUT_PULLUP); + pinMode(14, INPUT_PULLUP); +#endif + + if (esp_camera_init(&camera_config) != ESP_OK) { + Serial.println("Error initializing the camera"); + delay(10000); + ESP.restart(); + } + + + // Configure and connect to WiFi + IPAddress ip; + + WiFi.mode(WIFI_STA); + WiFi.begin(SSID1, PWD1); + Serial.print("Connecting to WiFi"); + while (WiFi.status() != WL_CONNECTED) + { + delay(500); + Serial.print(F(".")); + } + ip = WiFi.localIP(); + Serial.println(F("WiFi connected")); + Serial.println(""); + Serial.print("Stream Link: http://"); + Serial.print(ip); + Serial.println("/mjpeg/1"); + + + // Start mainstreaming RTOS task + xTaskCreatePinnedToCore( + mjpegCB, + "mjpeg", + 4096, + NULL, + 2, + &tMjpeg, + APP_CPU); +} + + +void loop() { + // loop() runs in the RTOS Idle Task. + // If loop has a chance to run, there is nothing else for the CPU to do + // so we can nap for 1 ms + + // esp_sleep_enable_timer_wakeup((uint64_t) 1000); + // esp_light_sleep_start(); +} diff --git a/esp32-cam/esp32-camera-master.zip b/esp32-cam/esp32-camera-master.zip new file mode 100644 index 0000000000000000000000000000000000000000..b3dbe9a0687980f5d51db3d51a8500fe022814ff GIT binary patch literal 97227 zcmb4q1CSKwr$(CU0t?q+wQWvtnHcE`DSM){)pX-yf@Cxh{*!Uj0|i{91ZAf44j-z92rzqzyLsS zV%@q5|81*{muv>i->v^P@c+eDdNT`W3o~0gN0Wb)P}7Sj>nS_`eFXiV`XK-PD9fSS z+joC;sR9E4#Q#E{u&jZViHwDlvlG3uyYoLfZQ|vXgBTHqZ@i&nkCn15&(?w9rzmUs zJ*VePHpPg>jg^<|7!C+1_mkImz20?Ap{|H`_^w+#GWj0@heJ-@Q;_H~5huW9+k*oT z`}QrxBqyRHd@v60K+aozzZ{gxH&mV7SOi-YSw*c@o7byXc!9|}t&ydaUOX)DOws(7 z%gD^Hpu@ztzh3pjK+xb7eV!`LtMSH9x2rd$5baMNX2%|!S zESnN3uZVAZ0RP=`_^3sScR&CDB{%?p^DitXZDeO_YGL+|$!bz$?XmM?N4b1L7trW37kBTQLm}>fVr997uq@I zxij6qf<$1%oZOJ@c8hE@`n|OO-H+X zQOO%)Lf;4Bp;cghq$7g=?lMhH2cb3y0Dvy-zjhfJNnuerWzm0JW?NIsX>$b4_qle> zu~gXtyflZ^0_P;6{wTCY*g~Y;mOLqptd6i1yl#x1F!J+}H?2FM*x2n&O}6xhF8TJM3rH z&)TQ=PwfRZC!^L{?45~j&j6U4+8bZZsNr*|u5Jw-x|=lis%CdLCknpmS{Mg6A1^nj z$9+0qojK*7laHU<_hASxa8NIn^uJVUxOx2@ZNCPrSRN=cp<0t5sLi!7$PXqAOT~YX=98S8V}J zR0B1(Q0!JS4Q4c=OX7oMj}k zbd7|tlx7H=U=jdk{*yn&H+rajm^4AhU4gCvM9q}QfclTqtr>|7u^v)?{Y<3Qw=lm0La)xUN< zGG=z)NOaX=t2HZ)eyDUep;DP+aEVn_Tf)afY*R3Affs@u7AMtoz{;%xL|RhG&?1Pd z66VV(qzYV;E0R`siId;4{Zm+`}`>y~nF-}8btSMuk&{thV_(o>5^z_k>#D0z| zn3wgJ7!$pB9;_>&*3;~Qd`WWw5ubx&unSNLYN|C5qd7vbm#-Wn!vAnnQ5TY`O!$Iw z%n%VYwQ0h!-*R+TIkp&0yo8XkE{&FBr{x^fJv!rF6L7j zEJkvi&fpQC>fq9cDtZ&5o+dCFk6h8> zvZ&7Qq#$+}G4M&y4U=Mvx5L^ixD9iG5=HBo4okQ``)tOfrBM}xR2|9PX=Om!4S<;3NUH|zp`4Ha1wBtxlEse`M>uop1Y7ZvmX1?o7B4uV8VX6L zg2h9gnZz;!UtxbIDJT87WWG!msP?DV?qhDbae4al{^}7n@y3DB%V^`3H{Q|g{b)T! zurEV~v!3>q1_e!19*Qp+?J=F|bV;L;m{KzP!PJna9Etf>@Dy@5ZS4n&)6Rg|l~g}C zGd3Asi}Gvo4uj-6^bjPQ56V)q;HdG1ae<^tyP3-iAH;)X{ILfwo&b~%MRtE;yDZwR zN>BZ8MT*Iu=X&zwGq_o`3V{hU667ghU4w8=jo~gn>@*Ah#A%L^q+S6zbM76_%^+N_ z%yi~M_9#H>jN>Q>pZG`Zm_q6K%NFqvmyGE{z4ch-fS=Z-+kN;{G3au4NDnCrbW0D2 z_>ozmFZ3vmYDtHsHHb})hztPz`a6(2jwuEfHxyGpv~gTyk@_?v9b)Z{<7z$A9*q+i^xesr>PSMi*b0|HyBVf8_EkG2&}R%w8NGoYq1Fzz=vZ)a-N`ST(6kF!-VI+V}y%~*_JDJ#jfn)>hig7 zp;Db4sZf0v4<1=9LDax^6bFF1X$%(wk~WfFt;b6D*!AFMk^wDCc_#-_+?F}F3oYmb z0e*ox@JvvJm02L{bxR&bQ7QWVT@usMnSp5I27J#tN{_%U!v*~gQJMTPhm#Rb3=Y4* z;`z0zhaGuQ_-R)rT^O%Nk3Zws39g4nhqt#4U3$B8qp~_bG{1i5s8{=<`|17q<$rD7 z1LQk&RxV93A55dlMlOpb;-rLSe-4nP&xph(_JXe0pL#$3e7^v$?(uBb?(Wq3_2v1w z_;}uf$gi{8{fX41{ZZ%L+x49xbh>@->(c#u6#`v%Pj3$4u7l$AS}=J=}aqoWcj$(Dd&T9vf$P4kUc`zGm0p z%b8@ZmT}nguiTW<`d85l3zo_H=QK`lc=BPN;fXZ^YDBF3$5RD50Vi%`@ug^3c4#=H=-D-F=oYFXF?TRP(cF6j7 zKQZVjKdev}Erjxy!5c<|aQK2N#1gn(HLFS;fuTW3xr4sI?WoX1eqW~PnIXNHGw)Yz z6M>=>RNlNb$a&J!|B`tQd+8BKEfRdWp$BuhSSsbp$3TP< zaAa*0gUvOlN#F5)OVd-3h2+7zIqW_j=(fV0EhY+UI)=Ba=EPhuIbeWzc~|`q@(juq zZ>{cKIOLP4tTxLGl~3qJq^7?Oc$qmqex@Y;Ljnn^-ShFnGv~hj%9fnZB&Jf#LG1Er zD!jGO%KQc9*bFqe0L|CPgnniR7nw`)Hj9rO`UhWYX> z(F<}%-dK~8S&kT(Z4NgCi(9i9i)b$Zl~3zDYWMVNA$f3d{YU!&;~VI|r|WqS<}dVr z3vI*1007>3W~^z(%TsSSHiAw;j|%v{5_+`MAS@`aA*^q)8_pXv~F+&Aneq|7-d-mr&JkNFWrpv)}4HX%ai38%W>Vg9#kXMOxMAE6fY&_sl!V5m`! z7a)oNX+wMse3E>*Hr@xJcrz_YnQ`KKV;hSQcPI<3D!WoOWW1X5Hd99XQi5;t8SV3&^62$RD3V-2DQf>@4FbchS-5C-WU8SD&&6K1L%YM5`i zbQ^NVfE|$lFdas)&BLY^tT^C2(C3NZ7pC9Ny5JSsCRmB1`GISwoIGbs$@f!%CViQh z%0#XfSTO<&UGWLRW{h!AM(ObJQE$Adpimo!3j5fiIB5ltN$%v`4P$A+LhIp6wQZ1O zeT0^5F^8&lq6m;x_m8`Hx~nr(i1;O~z&M5p5)=}MAq|+~a|Wu+e=5*m^aQ|B?;0%7 zV8=$1pZ3xy>+d+lC{KkzsgN2lrVwZ|MBf19>kBw=XsXaii2JCA<|#J(|vubl~W{0+)6~&Y>3s4PsWf2@e`ud*v)?l&o@^Y9(q{BvLBxB&p zG9#VafYs^?<>>3sP!2!_}4U{q(zT~rh6r)F4 zKnNM!$Yo;%=g_xYEXeDEEvQynDpjw#V0V zKw#Iq<>R?-aFx_3KeA_Qh9NeR48Y1l@usrbCvv3KF2=h^EGY}L)BP4 zZ4B?g#$u^e9Q5uBBi;+tu8NSeTAAJT70tH_hp-7zF85^aA{P4w!bT5;0vV!}JWH2a z3`rTy_5p*N?)aeGuESj;kIjQLH~Z9G#YLETCt(q9HA6RAlIQHOUnbI`{54cb$1NQOA4Qd^aJLhBCxWuYXP%ctz>k z_*&k}^LcnXw1AlD_50p$QuaX5lVFgzizZ~BDuS9)4}^6j5UGr(0ox66w)B+*Sih2l z+p`KG*ps#)~R8%MYWB4eB zJ$jqkI<|_X=GIw-l5k*=azYa`70Wicry^MTLr$n!ED8reiH?ad9GFkvKkzq0ULA`G zFdGG&Nn!f#jd7|c8Br%qUfkV!YIA8eaPBMxHM9q$xTHK^-itBum582|utZy`L2LfT z{eUr;9LaEg5QT3^*0!G&aIM1&t@C*OCBh7GJH38vv5V=ZpTJPbjU=b&Xy~C(C^`+- zURQ9PJ$&Ab>Z{_u5f9pYZR(+LLA>K$$2Y7s3}eziE`1<(4?keFwhud9VRn7={>!=K z_tnl`ebUSxt&8LaL{*I4dODySQqjQhqObjiT!vOM5M>2xYfr`@k8w-#_y%Tnzmd6r z6l=ID#5Jr~Y#GEVN{sg#^00qdJOKMhOGoow)uXS-2mN9?TV;+^!<1XhT>l{*XL>QZ5<&AAkeRkKPR zW)Jrhe%+PyR1uDJ7;tU-O*(IajC(bzPVFTa*EaV7HjFJlBxuSHqHApE>QdugUG}+G zf&r*AXF1R{gh$)(V019*C3^+*G<2MUxshmFL^da4vMLx5#MC&c*{IU4y~ zb4dCtL=*cb^>*k(j|!FjKk(ZE69Ukj_ferER7r(Om5i5^;@f)=PSJ2^JxI{UClLw+ zzrA#?q)>Ju%(kXOF5ec(S&rgJ4~{^!X_E#$J@A+&jzW^v-ojv~FT7h?(V{N(3h=#v zqbc!hVv|wzrok>GtBSWFe?7vMZ*MSZej=!Resl5o$ofj|bO-qTWpTq;Wb=8>?fjzVUGhR1i#lR zUX|ZL@(dR4Y7)DFKN`-z*TQ9^?mf_|lZ!U0-Jfzan4p03!mA43aKK%h)pZT;qngm0#98=J z7kExeYECy1p#!8}CP~|H4jFQ?f&%ik89#~&CJmb%V$ZH^a0;p73K_gaB3GU2ZHmpr zuG2NEC{;)3@S>U2ph^}}mDWmhiTY&Tuo@Wy(Ouz$r@x!#CyTeKPwIZjs7dYhK2Z0X z8e5vuNRQ~Dk8}-GC$^&A6I_w87cNPd*d{-JzHI|3zpU>(^7jNDt>3Lu+=-_fHE7c? z%go3f*=stQipa`brSH)v&a1KIK733FN9$F>t->8D)n`AbY`Xnnt#C1iu`(6cP*5I6=@W0im1y`u;FT=>w$G5TE(JpNw|vQpi2Xpg_$=$pTT@LxpSMs_y#cD5$A z&h$1`{{-DmPtS0-&rXjp5pK>75zl{|o}gSDT<&0@)#@5lC8#E7rRf%Cr=(}7WF=K4 zW@bSgqgWmuA`J|+XY4V_3`H#t-^$ z?b#&6Ti&?aL1;}jXwHlG_byRVQq1lD^o0LsWB*^D-`3T{(aFNj*6DvX`TwISuc)EA zdJ6^sG{FM^jQ`RM|E1{FFwCcO+$ywDJ$hqIO8)17FO5BkYg-p%3J2ca^;lksPU#LN? zY7R^)I^5lOxkCrnp!P_OAe(B*I8X$yS-|&ja5E z+WdP?IwMX`?QlSxo+V+(-q9VDj@evOb@2>&h;5w6P%9h-%wq&e*tiar2*UT|K_wu( zU%rqPmG+6#I#~0+95Ex&Fri9w$`y6~p%O?eW_a#$TA#ye;C;uM!wKqW(cbq*d(L3lj`If2 zY*-MMOsPE|)^pgyzHxLfeLnjaEJ5gD4-76z==&{qj%pzlWhT#DL6(DVQJoBI3GH!@ zIy8Y!)K5+=2X!+3w&-~hiS}p_F^%bXeF4UT@L)r!DEt?fHFtVL{wWX{zFDOkt&V%( zYYHHKZFU^537J(~s$3&5cvJSBI=_7%Tp6>|%xAM3Y#mrJtMo?gx*hCqGA!Aar|Z zmr{{5bRNZw_hQJ?W_s+de!y%RG%R$cH#6So2rY7TV0c(%DB}BA7jP)=)n@R_Maexo ztzM!pEwdemBdB+@*Y4$kuVHAI@xio*%@^L-%93#oD&2j6rI&T2Vvm_mD_49bgahCK zi^>zsckEZl_b&l5`hq(yIL``h0-lwd?x*y?elR>bKr>zcUoRdBTommjFtB|2g ziHRPSQydbGDZimpJua0AF00=@AY6gcBL|+v$k&7-H7Qr%gq@^@notKvHB<)7UXi&K zZq)^cibQOQWE1Pm6_E_I_$+PwY0rU=wXZL2(~XgSvLc2n_9s$_^&u&{466{ps|6WV z=)Q1QIjBOevIj{Y`4Dt$y}%;;tpPv9-b!5T|z7^WtUC z?h82Tal$1*4siHJVY;Q`C^gLr^{8xjIBHR**?o+3xYHb)@Tsi)k)LqRH zGsgD&%4Z-ci7S=)Vt-{T` z9@Mzv<0{$~H*3_&81E!5UqVrxBv07w?c(F(db3PFFKmdo;Zyu^RAK|<$-`PfmA%X? zEX{7^5o#V&5Sn@ZTJU2Gd3gA>z+}TLS#5@jj{R@QP0rL+7PNk44!?WLCF+9g?p`l- z=ez2u;Vd!F^~-e=?0z#O4fOP~va+}8aT zR@YdCb=W}EU5{qju*!dq-g9vNm%zEv46fFW7-%GUuFL-1$F;x(=1w zc*2S{!ZPv%^cReDkPFun=I}v+RMe%!;68L&*Oh{#c)jKL)MA8=Obwj5#NY>l#35>G<4DihJ`q#(00}p~&gD#bB zGWd-A>`w07E*NNsTKSJzB3+9h7`V?i<3dU-{?H2zK+^i!9naf9a2X}5>0CH0S~_xn zt%lU-$l-vzN~Sc>$)NJ-rvJ{!GIvh{D$ROdV2ywTNQ~IQ9=4+{(H`^g1^^>|LR80r zTq!s;tD&mV082AA3@$7iM{GYD*|OufGoWuutm!j(Wa);d)@GCF0u-Tyy2}!;vRje^ zx{!l|Xg;f0OL1OCVE-nR#?rk9SS_hMd0NtPSTi14y&&FOOmn{GAT(+KjLvD1IV6?* zaD$T1Zfa*LmrUuT6NvQ(dH%^L;t21{(Wt?tW@4xPlZz*J5cl`*kvLNW#KAw{VvFXu zGXoYPt+RXL2)De^BlLu_(dT;&UX^O(t&2m|I6Bd76hvuOk{u(-Cp;x9BJ*{?1@Up^ zpe~?4D#2EtDlC!HH8LX^+6+pAbRT~>$GXeu!%HISQSAQeB|X$<#e*1M9pWU;hT%DC zu+ULUTv08$n(Z$kLYu>R+^cm8CO&MBL9tjihP-JWr!-Rc-5PCUtgUNLUdCS&)}bh0 zs=MTj)mS#SDl$h`8NZH3!-)Hkj2Bs?wAa%=d1gppuZ^GlIfn3!&vz-xA%9y%kDSv0 zjpEd-TKGx;oX>yy0gWZgDS0ybDL&4E2~c)jT@lPK?RG9G=wUeHN@sud(+dgQ=`vH} z4l$GKtcT#Bn_LGop?AAnj@`aqJC<9H#u6*Zp4NtHNUcdWpk}$WLu>Dp6?W@*_Zir5 z{9Ak2a5SJa}n|?ul@cQL2WKF3^ z?l3kYl?8{>>gcoSTOm7n!yUD1q%A6z;9rO?p0>EZOBrSJVD(% zTBq@Xow=4bUjjtv9#B+T0;xEZ&3%|k!lWob2dPm5{!{)Z76}rm4nR&8rpsI|$@s%< z6-Gw^2Iez!1%0jN%ZTX}Nu^e%GI!k&DE3J@8Hk)GrBlG7xVxHJ2N_3bav_;ZFf_^ z-xHuGc4!0vss_7?)QVP_Tv}`ySx22iF{BBtXsf&svd5f=+nPE|76mr+YF5C!g@h_p zqJ&b3jRH$0L#%9(UJ=Unv}|=X1_?oOD^4KqE%V2cBLkiy*YCX5)n41wa1*s%RhN$; zQEAamdJB6Bcq54IV}1ikgOa3S(p@PO*msFsByQ)j?2#!WBc`5kHmSM4WiDh^gBtZ> zdZQ*)12r&p_1~FZ+5Lj>mD$GscPAqAVCqA} zSo{NB>!wY?$!tI(rcb|om=u8*`DnGAdj_s}(+;l|`~SxVxKWqtpB;v)WBgInp)~0$ zY`CxYo;Bc>WHJ#SY?Y`>-yKJ|o#Xvyi(!5QuAuZ^i^02*KbV?%A`7fD7-PgV*o=6t- zpCxy^6QIYNJvByx5ExYlwE-bJSJS)T@j}#5FXWh*2SUQrc zv}WIZcX6-%bhGc&`r;K>|NIB+|KmIL2as%5fdc>-IQ*p~|39Gf-+b|Jrf6^f4;pXt zzI$!5CH9`w3V80OC0S4G3l|ns-u*J7kabNWmtEL7_WpWNL^YW&!6&a+v(5V4v@`94 z00br{wsdYwYi0pV!5;VV9OpSomOBa2NH9gTm-TzL^cLTn2t#@0VKW)oEFz zGK=QjTpiE1L|&aaBZ_^d7bOHWuut^R=$D&}xnx9qymEUdCYW)fe%+w{FOYL_NLNWN z#+u;VFhL}yF|ejtsZe-5+M*gqqqIL-K5#^auynM6lh2+WADBl1|zzKxJSX(!Y>)a?$jv|jRr*x(9gxD03AYuUeY0to%< z-)5uf2HZiMV>?11<>&)d$L*Q3fL7038#O4fEZn=t3r+s8EcL9-oJjkDo#%$oK9o76 zV+%c8+8#3qUP&tmJC1%}2H&yNL17-44mbSJsy*N~y+dbK8O%%K&}rl}ht3_)db&*N zl_EHjNfrd@hgwOXXVq!js@9Kf4mY zeC2A_HXF0RHnl#p$rX1PW77y!-Tk~hM^l3Ot#DSNf&4Zp9vA4I6KYN5u%0@RL=)C0vPZTeoG*SE{2M%16!$f4hNTce9k!&;on{w(b5igO@Pw`FCLFn_Zzj4hE%#vc4K>49@#f?0Otekjfs%0lla0~86e=$*SKcceLg+V z-W$Vmy_&*~@@>V;sT$fo>!#768yfWp2OELqc}P~yvDn5QDbrFW=<0m4<0n!@xC6Xy zM+r^agH(xMhP7uZ9%a-%BTsMT(q~^oA6weoVP2y~#$j39LH2A=f1Sm+?H)C^>mJZ6 z$(c1r#z6^MVVCDoEC1Dbh@-4vQg-MT$GvF#%y4@PNq{<*whI*>XENr+IF?Vr4@pJY zpm)73?DcyGsm5NUPX5At1io(jFxyoLvrufTm?AL*$?g7pIZOHG&r!|u*VUvOMeTa6;r8j-B!b6oUe*zD z{clN3SB?jq_%KC}#T)?ke!$`PDaE{J);$%mwoMvUbe0BwTP!5B70_$*^VhT!ii-YD zZf0MR+tUFEgF&?aNSX>4j=`CRBKAYebCs=0W0^Xa$9pP@k7>g!O8sD?^35_r zZTBL(*(~DrK)cAV1_;C?pvynQo$R&pDiS5B{{6jO82mhb*WKSQZ)aIQyL>5^Q2tDQ zdw|2m#=u5E!>?4a8fjrlBU*IjeNH;H`T@oFGA`CdCjcoAt<}Me*5&iCUiR#-h4M1$ z=Qhz6+4Uva2#_}G3_3*jSo{$e+hxw~F#uuR;|R5ag@c($HHd>X)Bj^r#LwpMvgZhZ zjNNYwg(LN!>#Hj;$DWxx1#y_fjE)8ZfK2q+1Mno`V)*uebr$V`^6mZv%=FpY47x!3 zNG}A~r@}1sV?G~TEp+a@RaGaWkb+7oMDl&IDw=w`o}NGsk7P1IBLD{$-vXu%{CrTs zjDdXxfz3sI>5gF9gkNcqQQIzXgf}raN9==Vug5Uq;M}5R0gQoi)AdXZb*?;&BXd;| zTsMn$h>igyQ+4QEi~G3OASpbimkgZqNk!0&2qqiP0p;6T}grq(xB(hxPj%V?-ap zC;Yp9ni()|xnRaO7RejrN%<1Iu^OkfP%Qcr(S12y28RgdCTm3D zK)eUkaH8P9PL+eA;Qp3)3Z-3ygN65q(CLZ~^c{kSnZ9nJY=iB?H%C{fR=|P-y5Z_< zLxy}5+#~yxL34?3Q3F6g;So6m z_Vfvvu?f}u)qxDb!qEs`tw)9-L5@n}xNDRJT=Cy|_YZ?r{xk(Z4&zEeCZda8K~`9cqSRW-oEnLTw|Fw}N*>owPT^F{^82;N&X8(`NHslz@>92VO=E&xg8?Rdf7A6* zJ0>g4$FO*OA8+W{K|WXl*S96%{9KcaJ$!YMwp<%HSzT|b@exGapqw6(waOvEFds7+ zQP3+mTrVC)<_*|}loE`0i%{tmj)`l=ip*1gb4i>}nNB%;`9CvPFxrO`B4rmYuzOKg z-q)huUU4mTxpnOXWd$NQgDOB#Sqx_$-v_wNzoM|0BtRb+4|hOP4XFeKIc&fO`qY&c zj)rkL!at8ogD%AkBMGK8C=BB=sMM5%OT_O1=z*7>N4C%;4b>7xR{LM#;P7|4;CT_N`7suOyX>OgR% z!UQe>qw_?FTz!JhW1~W8cm%@t>&tUZ@#L+QCUJlOg@WLDF zf4?;&e0NMX`1|U@!o0Maw;9`0U>=TK>7$diJi{um(QX~m%n0guQR@cl717?u^CmZr zjP@X9bBTXQ9`JdGYcoQt!D{5cs8fM9-rpfWo$4$z}7dVt$Ti#7(<4$>F>z4SAx&LsqR<@ z=Q;D%1#0sdvDF%qW47k;Ti*?y;pv{(HP7HVBStg}qhLAp%o?dqfp`Fqm@eq?J$8{! zKv`s$xEUH*U)#%EjnNhlyyIE}WD+Y?VF^zHtQ!_hEPNQK#Xqt|4l1m~cfC~qlLv8a zw{W8+zonjrQJ#y|BT%h#H;icJQFkTioX*WShKBX0^$18)z>g**J4!bH3c5}FI1?!8 z*uErBVYBe9cz+X+$`Q8FL&1kkUYZguq_3x+lpk=D?=!oUYGpJJfla)YQX{@_(1i+0iNu5mI`qR{WZSB!}Oc| zpD^f^L2K^zF$Gq{@=!p(_$X-gN83CN1Z&Oetdgx%w005kknb}~of2;{$(}fX?g& zZs8{SQq}LoTB`u!hU!XJcbpZ&Z1@plP_>WeT7#9SFTWl7BIED@0kF6CGv4A#Fw zxMl+x3*K)bqL3pMv{ehmG$#PJ;qLeY(iUexA&7B{F1Q1gONW7dleU<|hJTDu(IlW( zv$hbBJCucTCd@TZ_9tFvz|caJ!&xd~9Dgu6UXGgR!pJxA#qHIi1d|tJ2d^YdAcLZ_ zY0Vg7DN3=rvC6oqdH{a{_|F2_y%NAQq2KTYq0tso+(Jc?o~%4Mm)J!@Zn&J1eAYy+ zx=(>*<)l$~{!wg3+Ja}aO+C9Voi}uso@WVysb3VAUWh}g<`9N*Ip9uy`2F`SqE9{w ztv(gzWbT^=<0~)RdB_yqD(1A-DLT6(s9ZuQjXFe_gQ&l^zoAkj?ax2@3A&W|pgkIU zyvea^71VLy!Mp+act4=!z{kNxPX-6c+ogR8itbIyS&{ER;phfUuuXHeFqnFPeqM0a z0;oD6o&^4q(_aR(Op3jQOJXHmPpCnrd1vSVvo2K+GY;$~P7eK5x#1fMfsJ!E0S_X~ zKEfPhwN$}rx@|((<3-{R?pgF*k8DAT7qS5&>CTpewliGP!GZt=_!F$o?_fPBj|Y&U zLV{c`ci&p*=06`CGYq62M|Krywn6^8UY-p_pZy?Rr+;!ay+%3R&~U`M{nVewwJpsI z2;V{n5n(vH^zAehVzQI)sNvF(5Zfl**Kq13mQMtS2lt8o5Dqb_xYU;2-xl|Xmo{&H znNrX~3iB}kbVwWCmoj1B9ZDL6!h<})Yt$nWK_Zjzu}JMZTD=YufvO9M;D7oK~rHE=nS=AKh@eG2dcUn<^&OWQzR_PykFa}dyScsIxr=tkzb)~ndHouhtoEghQ}tYtr_`x1 zoP?%8nUI=7c842F3?0(iuJR;^_kmJ1nN|dOc=Q>}ICf`95Ce0%_w<9sm^8`;=_w(SAD~BTVIKEbI*>~=yR`Y0oO8>b6*GPcB0Rw3$A_lZkxG}dHp|L0 zHY6F|xb`DjR0f42!K^JKZm6VQW!!MN=fapBf$sjc_y=FV>@{a-2!o12d{jHBJ8B9y z!8}u~k0)=dE$$aU1Gv&sC&<*)UFfwqnpNbSkfF=Y^xcz3_wWW24bKzTLZt)tU>n(wUFsL>dYmgT!Gi1Q&fEj{UoKe6v4n|?gjomG^ zwK_b}Xrg#x_Cub1#crROp>8DZTHj<+Ni9Vk&yq6asdYs85)JznUL8V>X&13C;ef@JvqS3=v9)-L&Ulm-u z@QS)A44UpI6~c3B0mCDU(i;|yv$&MU7WJS&u?-bl3o4FQWK7jl4s)9le`Gh^@J_1X zO{EUaph98I+M1HJH5J>hjZNb|rpyp&o?mwqtm$Pa*i-7S%S7ieflCDF*}e}`&2BI2 z?8Ubik$U*_#$B~AU>!ZrSid{pcFZ6z{!Z@K=U#aQWfxv~2Fr8Lx`%|jDzIkpNFfg% zs3PeoE-z=Exx@wo#hkxKJAbYoxpm}7LgBat&=vmBTY2k2Dh!@+!CZL&yeTn)Cf80oF$7oJREnk44+t+ZR zi*bCjMzeEz)2e9#29;SzkxpT{N@XEL0@}x5Q|M3TXGpy(q>)bw;wr$(&vTfVeeg5yc zXXebAnRVZFzvS8(5o_<*$jpeyU+hq@mXm6Yr*DJ2ez0#)Mb&yd(eH0Ne{4jK?HC8; z>4Et*Dy@x(9XZ!ovu}916!WX6b5l<3Ia7`vc2(HDsy<30X)_!`^&T|&I{I+Zt%{$Z zrD^iiQ|&&mcI{$|wMDL!W}y`8sMTw;_BG|XR3~YTnD5wwUQINq-TBR#Cn~)v!tws8 zjncBM^kT`dt(Rjf*0-@0;;v0ak!oy845-^8Pcbapct{dQpJdwlsMyX3j&wG!t>Ubt zqrc2Dio-nFg|gWgx*cA#Wl(t%R+S6KGcKiK)IWz^x-zFIFNt_Ge8x?FGl z@wxZaSL(`6Lr~C*x!s!KBVDI}qB`uDB30Av;=}?{Lev4Hgg~rmA(33eH9I7XsQ!x9 zzbZIB-S+cr0Ct+FhegIm^_j68}(vMfZL?r=3ZY%CQs;YY!` zM5S1zo-ye?{E-egQm#$V-)0F<#;veyuH3ixDc?RnVoFMMrw>QGD4|v%y-%F8if386 z4I3`WqCp5Mhqi-EyodW2D_hTgVXXX0U&Ka;O>=yRJXRZXF^J?O%u|`Uxi3 zeQUqTRDH&k#u9R&04j2Vj@qbtz}H$VUEU#niz-zyQpl+x9&P7F0p+U`i-F^MyU1F! z5hE=1b{C9E(pS|}qusfAeCKv6fmv00kI^5#Y#CI80JX052Df`2#)Qr0y+=P^VEp=; zG-8!&m;dolb zz8hY+?i)k?a)&~{p$rZcrc-;PO8p@fXwd30hcJ&sIVSB+nzc)>xT zRe>==OmYG?5uLW9Fk|Q$&^}1**@GS=vTh@i!s8q;txGGiN8@@w*Y`;%>n2woH(Xvv z{ET+gj5bQ1L0NIAd3_i$5Bo)@6;#_=wa|*LCgpdcRSGfAC&hEzi|UX!IW!Jp#74?RtNn|6#3T%{d;Byz&=>jwWw? zYymF3<{K!t9<5Orzgg@6*%QlxJNP{J%-PP)xYpFXw9|7#$8_aj$>MhszCf)55&%;ANtIX#2 zNXmy)*p+GMc)7#87{_u+mUib`l24f+-vc{+A$!B%bMDI@mPUD2IQjy#FJ`({!Xe%H zAs@q|Qi+Tt3|pOYdN(L~b86ZLnQ8?w`rvZAhjL}O*t5TXJ?X4Ja|4~OipN|cc;oF@ zK_~Ca>UVFdSYo`d8|$|W-}w6IOSkoqAov-7cMG{RNx$|SHmsPmhGqD~7r(SPT2!b2 zCe|c1>L@Ik9w|t2S|pg#+y;M-bi1`?F6PG50<Dl)WWagt{jIE=^5wj((ovFh(;VlEGDe8{$|WbqXSVSJOQ`h2Hgxly|3C2w z|2xR%-@%I7IhecXI~nW#e}H|sca?{@AOUG=(13v0{*B*>|DEl>elPw{&_k1&we30^ z;@6d~{V%^s89a8GwOr|BeeL8Xy!PVraoW5CCH|)K*v`DFK!BGYg(5#1{VrWg2`;3c^tmC^PZ1wwLxZ?Y@nH zcuz!X8o`IRP`{9g#J#Bod{s%VI?`EuTDv=oOJ3=<7}?}hlK49ke)z%j?Hsk@bY;xB zV!(dJPE^ckoz(-5oT^Q_T+HbN>iw~i$A)P4n2iGY(7tqqryO*g7 zoU-6RMrLF*^&EMw;|d^FjXUb3ks+wd@aqH|!9Cre>@;ut|1@$M??#)v%C;G3WSLAp*k#KJoUb zMz~L7ZkR^6u0|Kv^Mbi3!4g)IRv3oVa)co*yA_n3P;|zG8|}ndOD4g0cL1soIz3U|Q@*EELxYXZ>i|Dv*_sZWu6vW=?h)2)+qFHP#ZD z2%EsKPHNESXIjGMbtJaQu?d87)F`m7XDzKFd6RdyMp*~F zfvW+7wUhXqq4MR*QEse1W!1aJWavcal3~;83dYrnJKd-<>5H|9b@YLDEfv!4yYoY) z7KDh46TsAE;iLJF%|y!zcCI^Y;vogiB|gpw9m+TPu`2m%`~|EWn|F9~cU9A) z&|RR~SqiN06uug{h*N(`In>%EEWr9MQ#HG3X|;$kZNGN!uoP3N_7IzB1(unbDY0{Y z9S;2H`{`6=e~5w)3vvSzu8 z5mJOP7S){admYYJ|owg2A*;@qt=nl1;im6ruF!) z2+;Ll!YrwZbG+j|j~2D3A!GW?SCCHz-mflxm+Y*vLX5|<1h@Ysg52a>Rv zE@V5aRGPOaT1GLB#%vqclFU4jg~eV^Vxf}q{LvW7tP?kg&KBkc2Sz6k$y}+9TLvDQ zWrU2+Zq4=Xa}sr91r=38yGrhQH!dI8t8u=v>=TVs zj?hi%1jkS4SX( zY=satEtdl_seR}i_P}Z~2e%f7cW+AjSSlfjL^k8l`Rio5g%Fmpj$e$rB|>lMu1&7& zvZb?EYO=pNd6VL91PEZZo)MI#Z=*o^fd1##{y$JYhvIrnpMa!;4xm6ltp7hg%kIuD z{{@O@9S;EDG9Zp@-&4xm2qH;y6-v*uNgMPon4w95mAVPaIEc%@xR`JUmae&Oa~Anq z2#b3@ZGL`sGz0(@ms_wN1_j*?gv305yn{I^nzIBmg=!ey>gge(2_6OIa=&zC56pWU zfdlv42t!K*P%bcuz%~C$RU+Emy};^2Zz3eszu@of1%H^T-r7_ppDo9R&j&A~S%6Rv zfHhJg+M(?G3nKn|A&*LY6fBQW)9F1^*|VgM+p=^81|B0*?v|zl9!!6F=8CeisvAsT zo~8T5*Q@A0*AtGlPmv>wKL3?hC?|of{vg|Tn4i_0j>ASk2{tJ-RbFz|ckZV{)J4Nz z0rqs7F;6g2<7Oo6EGda$i*U>LYE#=#tygCtSLxp;(nL@N_#dgl`YaAa)4Ol>ZR!K} zMosB7OS?S&hcLzCLo`|; zXk>}<{Y7J)=$wMp(WRuSU}$HcSgz&4z>4IGbAFYzn{z(SuR1wQIum-+vFoo?4vFuZ zCLKN-f4p2`2Q&CmRt_%{$}6MZd3e;1e771Q>2(Nns=nZLV9eN=nM`sJP*@=F&fXn zOW}O~7KigT(jW9fNQZ=@h!d0#4?je2Q+stxO{2c?1ilZ=L%{bN(1x%wZ}zFX9sN zy;fItI3-8YM@NBugFZ%iAsc{anW6?f zCR2S6Jk03%>l12m(`3Pye=rsFfbpav(rNB4xg2ZNB4efNiO|7^6Vv?kGX$nASbZki zu)g7lIXojGYWfB-nb!%JKC({2|X;~oD;J1JJ&Eo5LGjSDk6qsN=Jt;6&_d7oi z|E!X|#HYmU>XYU?|1NFgXWjH#mK7v#w%hLZBkE>iH)!)LG!aHzSKHJf>CH~pCuQRb z$iIQJL`T&$yNqg694&W{?H#(zx-FaUK7)P(ZocL~LV+koxgm9&WMEO{Y+`J&3mRTPPc$XfxT#Au z2`P?jpI;l=6~l$CrBF}AX>9Gyhud#$i(TN~OFsI;37w;FZkx78V!Ym6{IMEudB+9@RWKQyl zfQ!6TVONJ?dUbgJOKZ@d9Ev=-nU=v>*&v3GKxim+*U76xSS_FMlM*BTLWt}r5Aw8m zLD|x7{pt}SW!U(A3Ukvr{R457NkH1(x>9qA6Bi)y*7LHjw4G2A8;Nlw)%Yr9S437N z25LyMoeKn<1wqA&c)}b%PmURk+7A;7Cgku|AN*+?P0>)YAB8q`*hSnnqK^TH7QlN9@UgmVPyN4gjs#@38fVzk;)zLllF(9`sPgn%pM zu|ydkM$U-rB9Fu@c?n$(vZ(k^lI6Oiu-q&*CtqyT@(JXh~R2OMaABK}{MOyA$$jh}gM96k1^(+TP)TM6G1?*MGl;Ymg zD4olV*NT;~+E(d+8>?9yK5#4bSRE>mf%$Q8P?%`>EpJxh*lP+})r2({ zuJ{m!v|5aFKC^vnB4JL3H9PNjrT}M6V=eQh$5h8G@e4U7bib^;O{>3&#!=U=#eRd)Ah3hcK z@4V$d=qEH0o>-69*EW%}#vRLyZvo!Ns=>O~mf~*^U1Mx%Knk;VrKNl&(l3$$Yh&*b z&p2|PVVbk=nv}Ed*mUsqlWAB$m!{f8$GKXi=qYB`In+m;I!L7QO5BuAtVtI5*PUZ* z-Cf3MP7Sc%qMHb9Qc8GLj1zz7)7O@BtryRge&T27b+{)k|K`uFs6G6#JIzFg{1r_H z-%#xg6Ek-dQmIyOn*mfT#TTKcLMeGZHdb$M57;hR2t1ikU|8DcD&5+CZc>?STff!{ zWpz}!4c?U;LR|DUJNRk z4dyg04BFW`&0BS@=kr@C>|o3J+k+Q#tNA+W`kIUVO`cqFNv6zMg$fUgVOwnhIu)_g zbPvv!Zss(3fYmuY=2@dg3)H98R%%GvD5OnIVQMp4_dOyz#U zLLyS3O1~+cRQifeUu3FFqev#W{Eym3$g(j9uQIGt#~F*fwI@ae{(h0Px+}IZ2miJ? zZB&iy-Gepz9Q(l2vD>6B@RLpEw(o6)e4}^U1*@K?_sgwNLz(HT0q>C9qMivIXL;o! zvjj}9asfnz(8dL;+eTtA>n~9&>!G+0+aSBe6~A525G&_ap_kMIW7^}G1t|#@oOE%x zCx%Df39o6Y9{W5pZlhhtTLhn$7mJTm(s+py;JAg9)6=&OMcVJK(;yTB$daPY?oXrK zzyuy;e|w-qb|JV!&UUiKHQ@9uOb0O)LF`9cb4&lS6=QR2xjai7XX@?0S~+HZ%|3YY z-s9&8A_}@fT6zfsbxi|@X0+Abkn6D4J3=`V>_4;B|$?bT|=zsbUT-e>Abcx+XDULnX* zL3B9ZtXFpkjILD)|9lI1ULmwUeM>Bp*vEOX3@f;>qa}WoruKSL*6> zziE4!2fUxWyiV@oVka)-5@{bhysuA|YqV>G=%G}RGUz!Pu_s%^UT)#v>@jCKOz5Ok z7)t;^*)ji@dvJZ_){G~8j;^(e;Ca&<7sYVl|QDV^azc@iMBMl#5H4iFoU(e7&M!Nf+0Wxv7#D*t z@VkICLPW(7>;#GxUz16uRcr00qWKx&XkBMdjw7aRU#_LOm7+?dw7F3w&)y7as$eZ& zK`jcjLq?khg*yM9NgRrX0-81>@Rn?hOcFcpj!ScjmiuOwHd;iZ_!D``+}${dCp?I? zt8RlRd_L&wUZO>!N$#PQ zwDmyf(`E(-Pdh8IYS#m|K9u6m!hq+lDysfeF-f{&cbV!!;);enaxn1QX|C@zgK9*{ z(@wm>QJH_JNY9Z`Uny!bpB{diEM5V3cRRsGbmdj+vre0Q(C24lB_VY6^Jbo2ZE!)x zS-Yv&k3BqhzrW@2LFUg8RBQ9Bztfq~MNId-pAF%k{DmPEN)(b0vwuauJ-+v-U2j%;IOhrG*@SH%mNr`6Q{QkACpgMz<~voH$AlWBU{ zLsw3IF0aknT&QKRqE20LG{yC2f@0)1r6g%gl^Gv%;7GqF%F}F@*MqH($|lyQ8M$$H z0@J#2+bg@8jH=ojfCGNGKV8jEsUDng;mrT4z>jaC@eRu+;94HV)@Ib|WR+Yt0-DuEFhj zh2==LABP=7qXDe#czlH=$`%(CW*o=7|A@&PH{N0QsrLlTtUmc^ntYe+3`yhRv10J?Vy7@P=&QK%5lQ*)9IAv zk%+AgM9f|v?nJYXI_jjYI-P!RN^Cysp|n5>$v{C#If-&C`ys>K)tWi4LlLuEwL=0X zmoA)tD}9Y=we?X~+$QS`9t6!83R}*j+$FghbMHP%NFLj*q~1bZd5W4kyRlHwQa>6f z+Nu`VYPYu#Lbj=hDZ?>La)u&QizPgdRPd-IwZ_f7@ky1Ya+<{$DA-i`BUiq~HMg+> z$6)DuG~f5G|C~UTu<5W(%}JXQMi@{3taZj&R&?=QMsJFZ%7OP9zM=S(@Sbd!pD~@D zP(rgPCt_&STWKh#RK_uwRg!?4Eo^*_q1ju(*y%YhXIDP}Chp82ti-!%6NsYd z;u-U%OqoC~p@C``;7c59hFFY_LWCo(ATaHiC^(bX_nt+Hk5o8VXQDto3#_D}v-TF~ zl+sDsA8ht_#Lxpc-0BD8XVA!DH0)`BLs#w{ge4SkpL+So9W;Gh&EqbrWUzjZ9ZySx zeBaQL)Vy3N0XU5-*|qT1g&2=GyBfZptBaH0kbmrr0E9A+S?eBSK;$$|t>ZJ2L;Ls5 zFsg6`!Mc}vH>mPp(PcBqG}ZO`llEA7o)!Xqkf|VfE}eRe!}2LyeszW0Zq%#02c0&i zU}^ZfoF+5VSW}hyR)+DtCWniSG`Pd^5~{Q+kp?&Fne!RBPAerhrs6A7*_#3lA3Pd}8J%9=dEpcNpOQeA-2 z^VC6$=#RM4+~}8iC5IX|8YHyg#n&gSPA$JnhM!uTnkI(*MwLxOy4CCFP)|IvAZ6?z z96X6K!&iw0#Ssk8_EIq8(fy7?1)c`9zeVw(%W_{Hxplz8LfT3=h#|&22}wNyEEhWq zS1+0gJ)&1+s2;Cn;M1d)pdm^+9~GPXTcKV%)n?*i!qS8x4>0c@DTO07 zlD-b&NN9^7z;6C~R?9CIPZeUoAr!TYKtQDb#;E3>CBy%jQ6@^`+7@R7>9e}WVAr!w zp~{*2ncD`tG&%xC`mjG94@%^x+*}x6Z?RH*JQ3K}_8)dz*DWTP83<^hxER;>qsgm@ z?ydo$=cA`9b6z@r>nzl~r+LZ!Et{*Q0Tbq}*7=f2mzR2bdRcy2m2ue!`|Z#3i>mk2 zi;=E@fm7Y*c{kQdwt=P&U2O!*mQR+}f{vlZhn|;7zjuyG_WjGhV-@{fnZWdWs`O8~ z(;&aAB?_V41?C^dH$ z-NWvk!~PYYdtuD3e5UvFP7UN*-bmbW)%$J*XUZpUIEZBmKYHcelY zm9s)Pr)F`hS)ED-YMBEwG-1sh*|nnB_ZNk&n>uxAAX_@UFqbOA;yQ4abcZJ^&rGm) z9zD~h_fzaUR?ftx-MZG^O|-PVx_hz*ecFB89#2J{`fA%c)T~<0U+23n=Qr(mH(Dtn zo>euU*RvzOBP*=U=$fx~KOXO=`KrH91`Y{4Uv?4rJ3YIe7o|ZvR;CPeDs2gLuP?1l zxKtI%s@T>y>0g(7ifp{?w@2)!%AEOnUR#%z4?o_3Yf|Wly5&!7r(S39TFv{FA?LT9 z8M}gCyHeU6brDR=1k5Vr((;sRW)$+I10e))f`VbGV5*3epsGsf1-Gdi70u*Ih0Vmx z%4XOC&|zqa8i|xp*Dx0*U2hafHCXcm^058NCv(G8gv|10m;x+b%7rVW<&OUZ5rJ?; z>I|roYT)JFvkESo%E9R-nLP%w!CW$(;PO$TRLLP$_+29Ua}A_x`K?IQIR`J&P@vTP z94v>y^Usw(XNCJaNRv2N4y!#{n+|E5k4|Nv98wLSp$s;pn2(i0(d{XiEA1^q7tfom zgBL5~nDooi=Wm-?>#~mg0|Se!WcFu>;JGNdq=RS3X`CIgw^f)bO@z$mJ#%ejoGp6RCb#1_g@th;~_l zO`QRW4XHCq6g~P#=ukL7bcUH{-Cklknza@qa&mi^cYrd9!G3sL?Sk|}bgDVG+P>(f zaGKszBK9GA!(yvE`z#b84l!FbNST5h`|OM0X6Vpae5>+8)q-uVAb`I|B?z?SrbHL9 z+9lxI9^Rhx7tEDDNk3pU{a=lsfYIt`O8piK^#e$y>M}fGrZG|8Q&DV@NCTV1I}NQu{-+Uk-VCB9_4;3cEQuy4<={((3V^Q4 zjRqbM4F*+bnX>>z8j;`e?PFmxTALQ6 zsXMO$b^=J%I4jNy5R;5h$7tZUw=7%lB$8fX1BflcuPd@y>vDT&M2dhF=QWVr#9qu7 z!VQPb@EA;PH-|jr9;+Qyu)HYKthg1C#i_hcZ=eJOgo|hQC9{>E6(F{XvkJvzaaP8p zRugL%s+4IpqCi!0LZvsj&WblH|EYPD!ion3M9s#_PcM!)+N z*rR*_t?EW&2dfHf+CgvN_GZ+Svz254oT&ia`d17oX~$%<8Az691NgYIcMITJRAhHE z145a3qM#&d1&9;{!1Cw~!d@>aBT-6|_pF$?zo2BZkIaVGc^)sXM$-TsNp{2Ithixr zIaJ6T%|wA5pv065fNKRq0vEt&PBrJ2g9290&rtv~w+B6)n7h^vN41|y998fQEQPuO z`l(IG+k8~_R{#%S@CDqP6LVtp0)WS*Rn23x&39l<%)L-iWm0@%irM^;u-Bz9igC}v zN4%{dtvXHxp_XcD`|P`DRkmPb%RZ1QiG_8~TR~pKbp%9e6-L;4otu?j!oe*4S&L7s zh&hHwdg1!0ps{!G7@(WM&Q(t+@JyR(e%BJKiG?oF``gnaUqGu|ofP37hfQxFDk4h9 zhy@&AOM1(42W~;PQ^wF4Kw5#BXM;)W#nfL|NSezE1zthF@yBT3^tGlS6?!4};tf&z z_xXdB{mc)i+NT;vq$l|eo`;cq33bK50IDcZ2)87lYNCf+=>PjSjT%zqpSYiBgPpxF zh<$qcVCMOHnmdeDc%U4cI=ynHa8&!ORN> zY2#~{YIlwyiv358VmMGg!pH@vLaieau+(&Up%wb-UwCP{uv1}%%ib>jMynS48&_+` z!Ho?|agz?B6%bAAAEU#)QQD_Otzz^sKEq0Z?^7+C?6lV6q{0}~#5|&ahU&FvDIrE! zkBXi%qKL=BB&&-In8lG#n5I+B{_T|t)1xr}C|6O55vF(X`*$piih2X21(KA%5`#)F zA7}wAmH;;OEJ%O&0pt6g$s9l-XEhSg0x}!{4eGqVY|==$)<6J@aA>VcJ)9V|^37|7 zEn%sA0X(D7#}z$u5Gsjf33HhwXDTWJR-jHIK!usr7wfmXpYRhcGMP9i+LD!;1}|C8 zw0i@71!~R!nsxmRELsc!V2-wev%1<5Av{pUzKwElO!t17U^+>&lc0CC1s0}@II{-f z0ksAX3_wp2wf`s^>wN}#;YB4I*|R5S)dv~<E2Idg4&t+r+EB{IUGp2>x8)K9Mzx49*Bwi?3}oA--f`=q^BzFA0p911M{ zPC9$;1U%tT!@#9Tg^$Px$of3FgoE`n0E=B*WcK%B=VXJG>6wO*+PjPY6M|V;Djhh}tc50ORjNfQ1Vuqe*+t3SkJ2Ol{eui& zV}K%b=M_c6)DK_E_fP1i57;tHv!h%(!|g%maeQbL(`5HA46^Le=XgI&@D_kmI8H~3 z@o}!8nhh1-7(kZMI^Lj`9KSDknu*mC2s9s|mc$U;W+chU_=bI}xOhN#KJ{d*bJ2*w zNhp|VkAl^E{d8cni$XhWIndfa<}~41427+4THmMoOf(?d8S^{#Tl1pz@KW1${ahkZ z-F|_D71p%Z&YK`S7#8+2V{1b!ZBU5MprX8m%ZgDC3=OB3}9IPyNDK0h% zUrmGOFVuq(${SzCm z0k&=*H3M-G0jT)(Bc*%)HU@6ppF7$*NEpL7ct zpsDa}6%w4=+28zsUPgPG4cvEYr^YdPGYLqj+v#u!Q#tj=nkQpWXXYqOwf~AY2@;Jx zynm@c5yt@~oNu9-%6ldxD=J4ci!5eW1c){O{WybcSWv=i^ks*PCcJ3*2D?E4c;*w3 zl)9STAjklc)&do|rRc1y>yH}&+$~Tv_Mom%|9QZhplKOR@?8u(>!tox0lxyYBoi4^ z3C)Wo&}QBNTiK@176Is8A`Why{yI)ymS343ifY*ZC|5$n%@hFy;3^HkWOArn!a}-D zga`M*|GJw&7OOoH6$gkB$%_cV_{W+*!$APDmo`cPFGPdWVMc zpd3^*4<`R2?rcZk{}f$0e0x(q-YXiL9jV6}U80)BJJL&T0&un->(%@J=!mpT;|BgT zt=UKh@2;e*C=2JT19|PyuvG(x?|D~E&GS=t8@|o(;?5L*rcbBa{nf$4#pmqp*m#K7 zvxCR0W8JsV?dJIFrEanA#WzEjC;MkzPs_6%w@%0Yv)Feh2N(AretsO<^TWgOcl)MG zz?9^)x2WaW^S!raySm|d{NnTQ3qQL;2En3c1hggds{^;>c1pKW?Ahyy;c1?}27hWZ zr(PDnSaxn;cl;Z?JBRPJ*7NKAW%T9n`|*98qtSL5<;}HDE9Sj*p=JD7m+o>LaMhX8 z_1=7^|4+ou|Blc7za0~51i<9}pN@R}56lmwuxXt(IRJ*+4g^H}f0?lVza0MK2-*KK zUElPwbVX^aKRM3OB}@fhh9`3Y@v~y8O%cbvA*`|HX2@Kv)Uno#elNUa+q~p?!n(5M zSN3o^KNZtU^@>@Btt(O}Q&Of%lBn^>!X;_wU}?uaHY%y>dPn_o7tmOxDN;v|`u$8d zeo8`|bj6XW6FpEWG4+SmVS{_4ba|KG0T=Fsr9g-&2i{!bj*)ayox_)(s6>=+jwK?V za&^@r76ya|RUM(?J#>#QEkWE(tXN#;2n!rnUQ|?G3c3q#MqMVK5{o239XDtmn3P?f z&*Tw`Ml*E3p*QZ~9K2J_p`LeBzO|zirjY_=Ek|BKhV$3>Z z)x6C)B_b`1wv^!xwD{?=d!k^#yDLd1uzWx*LgDZ(oVWtAPBH4;9avbmcmYqk&eHGX z$h!=NqEHh|V|VPwfS#k<;7(#tEMyXIAESIf`?#g6B%i+nLYR_?4L z^P0`G_y%}R4sKqn=QY!koAXc>S^fls%lQH9yk5rAkS(S%qBJCj1bD3T_CyPkNMsoW zwt@gNRJdI8`EhqQ@iAiu>3Pb=&_x&&Q?)F8(GY z&5l`RVkeOXaDCX4>rCa7jOLH)Ax~Xwn34Zb@Hn0FHJO5JBc_KYyVva4V2Jt!RVivJ znvMV>iw;Gb9u**vJ?z0ZX(GTfIsY@#u)#ewg^@V7M7E|u4>e?UMS^V&WKUL#4<5p+ zBUykW&%Ctcz7fddn1wiC&TerkG}ix-@&Z5S3px@5qt z5X0lu!E{+2irF(LyUT628X7>jcpY4x9atZ@GIJFY3zpnNzh1*NszOdCsFYx3W@j~z zTF#Z0g<+Hs$V~HP3BENaPpw0Yy^M?sME`>SVsxklj}7pN%+%H~NlAJY4Rj(Kiyao| z?y0-|F@q~jdD^%IetaoJ#22+LsyP0>az^29+tiuxwEIqE``$ajO?MC91k<7m26=6~ zI(*p>tWz?|N7t}z!?0@?t6#Lr*=d%O4L&*5_ny1R>(i`A8bg4wDi{p)*zFz()OWku zQC>YT?UEfm@M4~MBSUR*&AJ^ILb1+K5Ipr`6c+9nXt>fmIiZh3id{W#xgL~fBPD&; z7j^Em-_CgnT87^x0HJDHE!mZq;+)=T>+ABQ(sAyh?9gPEHWE02AtXPDOox0nR*5Yn zfx$PAv&1}Bl^U10cs5nW-!sAJG*@)b?RNw4*C5S@4zVKx!yg*T@d_9no9{xZs5b$! z2W`}!8HM}=mkot9Zx}&|sWMI5?6V96mXwC!@NlwJ8la~i5kQA%`#bh{!x!{7=1lX5 zyE$G_nTgR4f!-Voh~>1aB#ItC9=#M5@-WY-AwZX`+7wDF!Rn4FAaQs{v{L6!p&%+U zZ|Fft=h3a0-$5u6k>hRZInOLn3Bkc*gx44*gyP!UQ-u{+;NDz0CqXbPK@jnxCeJFZ zsw`$&65%N#U4_h!Jj)H~o0c=8LJd!vR3Ju)YBPK}vCO2UNcobf{{XEf^7^Jwb-=7{ zpsLY&R+eUPWwWNt>BEC9!Dk~g4^Z$ux~f-PH2y`%9)s|-i;Sn3h5gL z{qkY&1rsO`%P2Xrh50Y_Vn2^ygSrD(4pkX!`zBxlHK|i0RNc@)$)TpM(HAL=T%4k# zrV@gKY|SsNs9X-g3ac8&S=enAL|sW#&)LC;5SJF4`T9evcvqVZ%J2U92>qg@0$aUEVeu+Qf>^psSoFFhs7C&CSD2IeG8dz`ciU=E_qNv;uU5YZmez;Pg zsJ(B*FY)N!iR#bjJi90EieL(tFV@Vm-Ut0%uoe3Yq=%-Te7bpQC(Galkc!q4)c2?c zb7~vHFj}$_>~DtMi)^qclL91`EjH{Lwn9S+GtX{YkZzIQfJGn#>=5*wXohen)T#Z> z3HS!sV(%mf4=;oQN)GT|<5m16#7I@lQEZ;WjDDg<;vyudm5rPDBLS%zb1i-Tz6!{# z(VBSvok;Z6166E?V&_xoZRS^NTS8VbS_leZr>wt1h!d&b(bb#!d1W*hys_bB2F#c{Si#r={{BzqH7K~v9g{61RcGDm6$%(x zU$Nyfn%5d3*AL}#daKGTKUOf62YF>&)AFb&J&(@ zwlg|&{&vl=SwV5w7!MZKDf^9SNAXGZ6mh^t$loH}i6TnVG8|`pXAGMB_u0d2fH7i{K%@67gYhBhatK#2)b<`AmXk@{w#m7wUKk3h0 zbS5-mP0)X`D&JTWzK{j43X@W91te z9jR8N+wkZa5J$_f!4#|%funE%&rc6(6GvgI>%ggE4YNx}C5>4^L*)0Wh;LWG@EBK7 z#x0CC7r<$eM+MU$@}e|Sd1*0oT=)O};lLhN`Gc^cmG>apJal$J9laH*CNAOV_I6dk zZiWY;{g|URfq$W~tK~9VA?{FaqeR(szC}D0e$2x9y&2nblFt@#StG2Kc!rTnD{i;K zf|NL=%w)_%5~q!IkU%4vA-~;2#AIEBeTi_9F%)Ohx}d{meGz8eIPLb+gIA`X5KDw| zLWQG+Z9yM;)$<8>0-ot0i76Cps&8OZ#<5aupckhwSD%mfhl~dKyUe<+RAW#ZhK$dw z2l_5y%&6lPA`nFQ#3@C`_Yu|HlPQAYYnVvHlUvfkX0foEqYNWU)WvQYFY&73@xWk0RB-H~Bh~(?1c2~wwu!%#ZFYZ0h zQcLsFkjGHe`mK$`%P5|JRWUy#zZJsZDwWzMKi5WthHgruO)WGunn0dj#|~K6HVF^( z5y%=?(`Eh)m4H@#q@w_pY5x7AnNxx&gr{GlKP8@>uIi2V4MYi*(5|SBK*W)Br(ic_ zImSd}zA$~S1U6$b*!QxIRHC$KkRON73uXS4lo_+W%tLbX2?M8QIA?T!Bw4ve_yF21 zn8EXeY$Wv$wiAyREp5gWtz=Yor%J@)qCzU2lnZpeDLK31Ok)2MMp3+#QHcg1B{)g2 z4GV~EiIR1i40*%HR9q&9#j8Y1{RcH%pyVE6*3@i$hUe`XXukbyaBx z9wL>JT2e)Y>z`osij0Q4lRb{N&_k?ypR65_rKy4Ts8S3?nUQpGQCa`Qd}hr+G__Ie z#obkS4?$3Hs1`phlbM4j8I0m#fs*wfb`pO48Q0s{H()tGjC>N~#V!k&bu!t1Fkx%v z-_dF{eeO3!pd;MuFCTn!=4nemG}hc}&X_xE8vh{lQyf!NwIZWmXO4SxGLqn4K&LmW zUQ%JtyxGO%n2)J?V8awC5cYi<=u0Y3%l5Tv^DP$3K3KaeR?}K~Ytphc0Y7VfM)a9h zgjwUrIHr-wsFNZEfa;1@uQ}urS?qaaxAlB&U+Rx!e0IgR9}D@P3;FL0uifTacu(P6 zxe(;J8-^+I+8j*+Uc$$XdyG<=D{F2oBVzP;;JqDOR{Xs^yl1KHZ6}ZHaVPYvS(5aQ z8Rm3*L%qZE+)*5$3c`T@xILXJi2^dg6YGZb^8!h$70ew;+kmm=O=-gt6-eR1i z`Iw{nw5izFEetVw!rI~Ko4*1^fV}DlpB5rC?@Fl0$p*AwX2T@@vJ>tz664-;29`dp zvuc4p&aG~o0#!W-{sNTy%pbumUWl!A&lgNYPFRSNN#N=AM?b+*)a+{DN? zX0f@TG4Of!aH#U@5qbGN9^lrr5_gkbdvF zfNj917vHPv5UVCm{Z2%wYtdI5T++yzQYYtd+va+an`NI+)###Yp`U0u&DM5;N5%O4 z`V$|*(P_hTq#Ii71d$BLYjZfcyS!^sFy~7E{-&LO z8fJNmF)XWuw5Q3+>)9Ausm4<8jcx+Da;I`bbI*FXdt^A73aB2^RUQ+@Unl+`lxAGm^iJ26c{8 zFfWs9C9jpZ>NMQ`AIiQdNVH(fvg}(nZ`rnO+qP}nb?cUG+qP}nwp~*_-5vd2N6hQ# zkNnJx6S4Qnja+-J{1twC$$dZD^dUBtR^1QYDa~so*|l2b;VgCwO{!oAZ`lQ*RfcA+ z2h$)_dap5p&Oa_8)pj^rAGRWNduGp#B_a{*Qy9Yp)OYA?~=hLV9I z50a@?Va7tD*kROUsLGo4kQ*X>Eu%}Ui|nm+7=D%Q>hEk(lXwCza#7DytU{Wi4NVbT z-oP=xZPMrG)ReA!E%U0uEWr8;hO~YypaxVzY1=RQNc2;p1z2v#dWr4dRMi>Q(a463 zmdE)@B|^7XmrRfv=T9Z*1V{vai-aa4PoJW2y1JRZ-LnLI_GGG@+la;C8D49Sp4!qh zbxJzmMAMI%(XS58&61~@2hO(TyQY|P;m@tr7Q%oqQRT1se zO@;La|J6Cp3qJf{PPB`e9glTH)$V(siY=ds8`P;yFW=Y!^7l1wBRPbGlMIVER;I5F zQ%2nGX`PVuyerVV4J-`7X`CIlVsWOe1iD^0Gnky7Aq`|NKSf=R2M_*mhPJdf?oAnD zE2wr!+?VT|B0-v*x;1V1n;jJWkru7PJ=cW4DxeT$*D0f42R4~{lpjI%F@5SCNxDo( zR$*Q7%CBvf`*&e_C`_lWZhT^j<;7`wnHA4+n5bX}&7%`esyf+G{t}r6y4=aqXF__D zu;B8;4&4Pja~sNHlf@Pc>0tVD=~mxvft?ew#*vCuziM7e0QRB5oLU^Z@Uw6i`E%;Z3@6>!>lpbw;X%9#*k-Miq=QNu6q(A68|9!myC|4O+??*LA>s zNCr0dqk9=i?>EJY>^f~csgVFeqj0zAEMX(N#NV);SrH=xY6N>|~CZM95e|7q#bRjub(L{J?N3)N1-+w`70q z;;Wn9-FiGpWd z98L2B5{;s*5=J&-nqvSmY?VHoB5k^zm&HF^9gu4%2H= zh@LZQqU?HQSFTU4sc8=Q6w~7roLZ8gBRa~(7@aVceg_cd8gyW@$}~1Kw5oPk0AL&Z z%@yQr41RTBKN&Oz=m^;#o`99C#`e)Zf7)P(k_7Q0v{^G48QhSRqh&p?Ux2HG zU09MMs*XPeHke*psoFJstO%6%5QVc}JFbHv^oo1<5Pr%|I3 z9gap^t!3pANL18duQNr5lwP(%J?QndiAVX5o&7;|{-|2DMkn3a;i;mlczZ%75Fyxo z<&nF&1odynkOL?5GNY(<;EANye31+2+p#YB1x&#bhH>Gh^oq8 zk|Js@tT1>Llw&iPzibcvG^=HiP`Lz2Es{upJ4TQ|ho6}J2~~so^T^wUP-tH#?5vzT z98P4}_e7t}N&AzPj=jmtV`SoA4^xcl?ff*=7wy&Cgw1!?(6SD*4|vH+a#yHEm#`N6 zB=b^ChF;}cixE|xg@v32h@L!R5Z3Kr^4q4qzQ{Zu&Ax((mI+f-jawLVRSbr+siyEi zMA3@D-IgukcdGZF%%QvO-x3ic_d$Qp8hJ?1XIKZ$O~D{eoeLNxN`sS@KkCky*Yo@&eo`sepl4-VF=)@q}Fvj*(Yzjj2o9q zC)uGiu--BAul9?)-Otiwe-lnM3^0;4Q`B zk#Mi5NpGPbBF$43!|&It1jkFLhs$H6bTgoghst^Q-9Tj^4gYb@%!eT z8t09Vj^81qGBpPHF0qPM_I9$$E1&xY5v2p!8#Hm;!aB3cwW2=k2eh{;Dsh{x+Dyf= zcYi?-#oZ1+N}ZqDHE<<@#ovfBrux~s2=C3)2CN+49T5TT_u}k&m)OKjKsq5+VlKfN zuCoC#D<*yU$kW;N^LHXw<}1)yK~|Cg)^YuCAZzvk-ADkl*a3dy6a!2*5=e|$^u2wx zqx<>|EbV-F2fueKS@7=kja>iT-s-OX@Y*9kc=k&D(0Ms*K2FXyBW7HmWkN1iBV(a7 zO19ro66Tz$)_xc<(ZKKp1ca*p47`JYfh9(fWI0*rYD!E08T@4s$?d_2a4#p67C zY!68%2AzQgnB3%OsKj4$IiGEcIPnk!_PB!g6P~LAHHv@e(|4wd)6w9|AQEi1K5uBQ zQu**TO<7uolYYYeTu~S1WeR_N*ng#Xt34+@4el>{ecUgUiOa=ndR$km+=io98r_)hxtt4=DD7uX5s-}cA4ocLb3Jtn|r7%d#=zsKCQb( zlJCAxk5AfB`|r2j zX>j=6+J#N^s-^k39HOXuGShqIkK$WJlU7h!k}4e`f&;(V;W_3i-oA~ni$7QhfDb3z zm!Eakq+_d#)`p8eI2iB{T*tP+hKa$(JvBm8u{>_D{R6Gg+9hsB;z}DhxGnI+qK)8a z9Y60#h<@}+X&`#U7v8MSCv0N+E;6mbVp|5k7o$on=3N=d5d62a7>Tn8{y9xqedN}w@U*(Oy?q>JJ7y61V06h2B2n_JAFane0SQi> zt3ZDOZT$Vax*rsdqY5@GJ>+8bqlyfSEo+Z(Ph*JA`;MR=>sSU37^!x=aU>n}wq?YJY#eR=VJMUvAgQ|Xv(ceN$u+TWGxO;j9Zu9Cy~XVH4K~;$19?cqs+&ize)%%TNh7SX zsea(IX0VHwul@F5gJ9Y`;Q4TXZmZF%rPk?I>!7oZzv!0PV2Y-c-ZS z%%&kSxOSC+MPXe6epz+vVJ1sNg-4x$0yWSrbuG* zZr!*c&%FGYAmqwj-LwTtdW05~O0|m-E-2eIv(ML5#>q$8iQID{6nQ!g{*lzAo_5j& zg8p~hji7|E;I!_;+ong)qiIW;`n4-m?GL|9gQ=3bIMgAk;szsp!PVC&XQ9-oyq)Xd zwTL58zG3TJxCJ(}Wy9J-;9Zx|2#VBbZxfqDxv}?)g}8h>v)~LjybV2Y-Kwb5r)$RN zca!!Hc|-3B*)%+OWzb0V>w9JUeu;?~m?bNxphn=ayU;oH{}$OWvYC7#q@R9}eOWyH zfMPU1=F&-O_O7NTk?6SM5Uzu~kGS+7 z+^^pQ`izKEr1pwO3_?@QP7Jvo#4#_(J5Wk&Rd6pH=&~{2#MZQNOZ2hC(j|v2G`1a) z2Va?Z>eBvrsybc!D54ht-(L261O`1vHwm^{XQRb`-`7y`?O>fvT2%>al72?a`>w^U z{V~%k-ylvUrI;Kv)!K!g?(7(!4ZcO&%6JBcan$wc?GVMskf<=aU)va&gPs zx7HyS&h%*O7e&b43(F);Zb+GmxvhWiZ3o&_37Q@i$LI$*mv-XcX-`m8({w3F zv(OA@aUQoywTWyB@?nwTZVfF^@#o~#7*!Lr$7wuPtAt1yqEm{TOy99P5UR+9puLLJ z?HI4FmlrQn@b?C|y!)%mtH{l3CdSyh{PZ-u+^}c4zW4Nq!Q(X5*9_!GXI8Dj;0YE4N_{pJyStG0RCesN z2bKd#q2#>K**=5k<=s;WVl24bkX=79c4Csk{+Q8atW_Svv9R?(dJ)QM!S8QXykizmLmh&)(dopo zu6B_Ppw9;Hk$yzJMm0WvrhN}>b8=Eb)YV+B?lBXRw%`K>bw{B~L?TDfEXIaEO~Q)R z(6*p(5&8Cbs6ws$2bLkXXJNv9Mz?d>dkAzxZj+b*4^u!wTIhP0LDWK8Dbe8K_TaeL zU5wAr+@1n=>abpJpurO`M!G`AdtzF6f(wzvKOc0_w^9|s~gt_s%FakWXKG(5gwS>Fp zbzHJlEMa$qm3;NAE}t{2Q6{IqL~9}|ph0=$JCOVMw#YnPr(*twMr32(RkK#-ByJ5< zk1zrX4VIWQEL%$td+*nyj`q~UbVAi1^WX`|!$IJeE5nr*61~z%2N(`ccIS-0L(Z7k z-)UU9kF6%gym75{=g)Ed2Pj(seBx|aofl) z+QN+Pi6LW2C?K9LfPKb)qlg0%Wxw=F__z+yO~P9`A~rdQl8##fQpLLd+Ci_(cY z*Cak?Y22cP@sn%{IDRm!C(yAd%ze%XghZmS~pnu7s(^YfG${@(8!2Y zwheN!seX^M6r@4F9N~jCJ$dn;RD@C!TPFA=Bnz6ssP4lHU}9?& z0!4vVsQ^Y|R_XR$jZv>WHtD6NRiU@4Q z9j=twW)hq|evCH-R2pm*@H=IAI7r=IHEND!;Fr)+MrfBPWxl7~_zVaq2=c$xHJD(?n(ly!Ql(z)#^x9?|)dO#<>6z+_p@6U>;rPV*2KJ*x z=e+>53%iudmWk`Vij^e+eX?jx9e+2jYF&N2NrJeXt0tTNwiC6XPm#n9`6=`+HUr`X6{;1BIvCUMAkc+Z0Nehlk$`00p%qMM&WZAs_ zA8ZQn^mzi!71tM&77a_}%V5?6cT8Uo9SaIe>H6PagBSHLaJoIAw>JoNN40~@P?xRn_Yaa4T zZjDT#d;2KN$nogNRkH3T4a^-;s+y7g6*H84wIh!X-KOTUUr*It?dk?%dfDl=YbXB5 zH^W&iT22mz;02TLy{;OBZ`_Kw9zrvT=yn(bfg^R+9JH)D)8_&#Q|10fv3S#R5!aGX zxtHL!BXPQLIK=A& zTDGQs$8d~HYTIls8IfwZv>J4e?)paUo#8m@FZXnTtq4-+T2>gpCVd#2IRkR8{Iv_j zp_Q`38FJJBxXZ>p83&B4hSIwo2lkv-)gPo~r2^ByK=-=kD?sPWo})tY8?#C|QH)8!d37Mrpt#1ubx!-%k_OMircP?#__JVed7{)>mo(VaoQqkCYY;q=+H+G>sCa z(~>D^+&I(R|6HHT@`YT9<;`RnU2TXRD)W~Igx3P(z(=*vK+Dc4lvs_@B=u;AQ|2C(2SPSBaXXfJxO9?Ux>^ckVN=i0>dpyu)I z8qTQ4pa>mwDB>a;2w8S^=Dk$23xlRL)JaU`Mw4ZEtkB!>T-pfH*bU_@6$o zq9**g-xjbIBG9#%RNW_5YNEs;A1+>Wp@p*BKjDa@5~dcCXx8Y71-M3)cMcH# zEG6@+o8PB+f~LLK+H3X+9ZF-LN^b<1C0oO3uVi(b$(5(-uoGmr$5eu(>543{dT4tZ z_%IDU!A|wopxgOIo^QE=0q5JIEx$~7V|`lmDv_)mukXGXni_n28f_fL>O`5?O5XO> zx7rS8{s}Su8m_4vZkK_5H61X0(uKSa)-XR1_7Sm-z>Np7$d5|;daBGk3m6|cvhO%I zA%H(Sa5^`nR~5%L^9orA>sIz46>;Pqg*&oS3o78^mCpa$InYz7T=NK+})8`nj8F&v3f2xbu>9)r?c${rPf9)*O){%HvoEQ*@7f)P&w=44{L^hK$W>26GRgvufa<`#6xE z^=(P6#as=zImK>#@PJ!M%AEOtP$*;Tg`3e7S#OC!e&O*7DwBGj$bd*gdz7NLIhm42 zF@H-Yk;T${vm5#dmdzX!vn52IPK{nf7P=n~fz4}A-)#D>lZ|(*(^lKFF#9{1+$S~# zrw>uB$|&&T&(@yhwD54VHR&Ca`1hZe`aUL*;Z4JhLsl-kVAr5mVbYZC;#xj8RV*XK zbFUzB5*ci3orLu{P+*KYzGrv+)25xY1&Tc;^@wRHu^hBYMrkaU>Y-KI@1XX->_kdF z0c2WvciL-Sx*!=OBlo8jO8hO!w9-Fg!N`lSh|vz3#Pb~eiK|Zx>YY=rVc6ft;ccH1 z1kcusV_kv^o|==6j%3r)%+rs#T+54Gs-bsU%J*8hV~*;R^Bf7q%9D=vWYfaTviEXX zCd7ZLsA26HwLO&-H#B%LY+mzXM!`bfa?rQZZth1>{&WtK8>W6Fw z)oE{0MqTt;b<)qv16kK8gV{Qi;LEsys{qn%Kf0v+ER4H(WZayY2L9sf<+)=vDD?jL zyZDE#I*Bc_1$OctD+-armzu3V#3tqtg2(D?(#rT;*m&_MM*LhHY}6pa&_2dXnV|mD zpbL)!#&eS+1(&KXrHu5#z29$H3`5P!z%lEWHq`dCs<~a|KKTTtK}A^Yh#IAG13dWv601wgr(Gr9ouEtd#0+iQr#? zp3-#c-TZ$~(akVJusCD~6!@r541{xjh*gs0|G)S(`_vpXnQY;>=(C ztT==&4cj)xh05!3Me=uB8alY8REv$T8J3-$J<1YVRn`Db81aM*>VsgiWe*EHMQkz&h`&gh#;Sb5h~t~ zP=$~+fe|q~%oF@(F`$PP%4gO@_35EgpEs-_Nsblrs2DFqiWCd3tuVcFiDw%MI<1Z< z=SLtP*gZFI)A3#kRgq3eh&v^U9RkOvT8$70AD=MFZCfsKug61Xe&j` z5FjnUFKJM#0!Mk{*0JoUv^=vvF;4baZa00!j&V%x{>fYyrH? z-s59LoJ0^V3@(jgCbr_*>I|7hj#v;NN;8P80w0vvu_f>WI-wk?q82w@|8YW=zPvfenhLlO=m(%aihq|e%z zATiqw9l?i*64{`)aRPJVyX7*#Z^C%wKG-uwG`U-v1#k1iK^>_x(c6ETYHO&-hx3#B zv%q(@YI6bqKyCp0xN>^th?4%^HQp=m`J|uYy1ul)CirpO(us2t9oMj|*luiIt(sKU zrZTHc*@mNCEyjsr-dwZ}dWzr)3jvqtc8mGxIUuaq&(+=8j=QPpiq`G=XcHe;iK^h6 zEu%@UwD0}>mQ!jGoGie+HMn{5ZpGD>#>6hW+W}N2|D7U3gBTbt{!5Dx3~ zl4I~^?^;>Y<>_Y|qiLasoYGAZsT(7sq$V-8O@+vt2EISzM~;Fdm-_O2#jy}`=Y1b= z6{}pUJ^qUME@`aXpLiumNSD!R^hamwawkp!_QXx4bq!xppq%#QNm^spMVe|sX|^AF zx5ogYtg!|Nc6OAkNq@CD#@;wF1{+)v)gg=yyGZzIaUtJ0Z`utrmdvQvb6?vHjkHzs z2(w`&;*z{{3C#f4LKZ*n*q$JLAj9M+2jcDAJ)s zdq@3J6U81T$faaG_$UY$BUI;9@e(;c-(2F5@mRTJkMW3MyZa%Qajzp@dabMo8~TPJ zO-n(XCd@Ho%D!T#a5@9JziR89ai-xxZZS%M`wU&(YC=L?;v{kA^x68VWeE~|$7m9= zC{lDI$`1~8R@;#)QxeqL%;wzsr7aqgiNga}h_5yiqJ)tikqv7vnd&`ZyX;cx!wZ46 zkQs+W zn;t6`jkBU=l90i&He_|Rk|FKK?2lY**=J3>1{VrX8Kvi$LMfw1qwp7h$$)M}Q4TEB zVM+rqm<$y3gVZxJ!bs7o?a;%L9ILVSUYQ7#RbS~9$=K`Edfbn3Rmsv@8=o?{Ff@ci zSnu_5YF;>X1jDHT&T1|=Ns=V9!YJFa7N~A2NiT*5{~k1(>)WYs>zfzH6hLKCbS{Jx z&`$@ZwMyea+ztiE##5XiR*Ps>nxntzqj+hwFqH_zbd~}&UBB&Ohj#c_mkh&e&NQh} zU4p1+?7HUy8^YmJKh3@Sl`sk&;<=U9Pvh@jH0&WnY>+~8%CIHWEz-l^An|7a38zpb zhiq?hL^q(KNT3NT=Tu(uXhg<-)Tr>{ADp>Fu1NAO0O7R|rOUNoa`5Eh8mDfG?4H?Y zVLQcDVfNR-wn(kSd{W?SZAa!{zB{Ge5rzHmS>Fecn`a|RA6ggGvc{PR$ZoOAdoLGY zUdwMkmOhsrdtU@|b>w6{{vf8mgdY_w%`OzHrRWbyXSH0=updZ{#bX$(K|*8V+&+L% zA6Y5KMzGtj7msJvS)ez8i)&@Gt$=wW1Qk}?@_m;0;YjSVedesjX`k>v+W>u3l`;FV za7L;Jo^knn;&J9=+45X`9C6vYq%_^oDDU|VyYEZ2D!QE9*xWoI#rM28P&%5TAuD^f zlr-P46yXeezUQ_(l)G(yr!#D6e|Y_NSG-L6Mb!#y^jZetmyAOcgDcFtWlW-7I7nHG zetUsEA$3=2nzXciABIz}W~}x$YLbMd2r7pBiaQD}zubtBF5#zw0oLoBTUYlNV!JXv*kSjn z3oAY)dr+r=hO?KlfmzhB?{ZN*Rsxe|Ouax@NF!D-Aja>1o-0 zTuRAIh}A}3{c=1b_Hv4&Lc;;JevwgE>iFZ>s#T*v_QU>xP`mFF2j+K#AN)^qYe4*s zNtes*%A>(6rINjn;mXuS(O3DrM}eyv9%@fxEbK;=NE!`O*$mXzPfw9!-so!QLRAp> zW2m&b4joU{|2lM5nHmN1?vkbqO25lE)UCW z7=}sReAoCmwyGPeK-U|(X&V@8Ud9a013pBbav$uDU|El@e-Gu7noh*X7jYBIm!vZO zrC#y(oy+6MYFIQo)(_ef@Z7tu>>E0@h8*(LHwk`di+g%jJZ5URY~{-xy;Q=3X^x<2 z<2ziy+O3fIZS@QEpFPD))oGXd$5Tv!008L!cb?*CXlU>c4iYSh7-iq^E)e8but07t03w z1WGH~ZaXK9i{J18I*A5yO^Vevxs1n*Lkgh?b{!=<1Rhg$7Xec0dlMc0!izIlPOrcN{$Q9S z<1j)CTTU=Sy|=vvywFNlh!SpkRpmp^$^RDrJxptWb7+itUm%6%0zL3vaJU{nbeKAu z&u-Ya{NmS_*vYS$hOhHC`3(W$k^rVpk9pUBHUdWRs(99q0q8(~*8k23POj$vV1q`* zUYk8S_|9X>tTpILGSD&!N_z=}2C)PJL?wFxl2j&@=*cx$DvitTYc^`*00AMSDQb~e5)3k zS-QeI@TM9{#SrgjF{$U$RjNpsWN;;6n2W+!aA&l}IaEA3Z_z=#-q@I->$sUa!4bXk z*x8P9@bEV;ve$GHvh)(O2En~iPmmDl)SPf9=sAZ@(BiP6rU{;5HZoyVl^JnWIg+-b zPzoHDlqkg`u%S-pKkS-k5oA%_U{9Oe5@|oMEhUH8noHV>Idsa$`L~i$Hf-11fVp8$ zyuoE4Y)22}J0T3fiJlt85~1@W_h1@`xjS95*Yu|@T8 zObE#6h#j7dp>1dlzbVBiScRu&{5|Lm*lAt8_%00`J4tTSl3{;a4W!D6wly#%mW7pl zDBfZit8JH#a$2(3YwG70=%%>h>pK-xr+bjYFidY@?7+l+LkGUJDgGf%-)P|M;R??m zKR?kz<%$4B)M+us$h>b}aC-T*yzA0B!BXb18-4-~7d)i?4a1x7IL9?7-@wcOC)tsp z@g`E3={0*HtpWv|97!S*!{9v2NmAPXoP|8Mp0m)- zN+mH822rsVak&cZ@ApTeJBDzGw(EA+1c8xrk`dkM$=cV!tQg}0eR5AEsC{oL5`Ee$-Xq^nvBe)X;o3?>ykY1(ogb8BC+ z$~s%y)oRAiE~(UQTx2$Ll5)gzD7Q=TT|dbf^)ft}wl^jl_Pd|DZE(1FWuVOD6Z6sm z84!dGz{Rq&kR=Fk^Z)7!-@`v?L#>4lmN*104~ZuDrfPm87qTyWV#aaMBJ0?}7Z48V zuVT5tag`|uj*3erqCR^Be$8)SW7Cc9AS03m-V*0&|oT{E>;|Z<0B0FI~X_yz`Qh_G6KralAX2iThpFK)jl4-*DFN-}jE zPMvnT2pAX@{?P~RxOCap;fpL8)|N(Q$;_ahAEoA9MGf^?bs}lw?YCoMR=8uQB6<($ zpGy&GPiA-_%cXYF96>l{$^^)O86K4+J*B`8{vM^5Nde@8FayF&LiyVt5-yKDQ92Iq zu1hvR2g9u`XB$Kul3KjLrSeNti86oWd;ojWK6q;5P}5Ozt_H3yD?m?%7V>AWPu=z+fQMIl53 z@BU=c0d}=FoILmjzgGU`FVp%Ugf7xEKfXGfvJ1BI%tU@~4KU9;#ehA_2)hgeAR*Eb zCjzlmc)N8F^_C~$5vz@tzYeAV6f9!^0!7_j>4>*OBIe5K?)h^t3Ydc1kX!Cc8Zfbd zL_|Iops>UOKl_n{eHt-3%c*!RF(M+37hkmHyIpO4d~aqn(Gn#-Pe_(f4jevNi5MiP z6RaS9f#RsC0>mF9MOL6FnE@qZuD{VDjZMkTE{s8twZS<1GG!)mcDaw7oho6#ddf~Q z(iiVwozSi;?Hk+GpCM9)5vBe_E?al&jh(6yT2soDipgvrK~Q&-+o9}&W6OC&DwnMT zdKmt*VG$YEh$>4>Yo}1d{q-q&pwJcie(9!<-%y75Mk?h8Cwq~I0~QVd26|g83OZqS zH+7hqdfPPpwm1p2jG0@g1WP=Y|?la|J{r3uApVKFZ%by+HIk=!f^!tI&7DOak4uV%tqwOZYxEVMAp zShMihMSf}J3LOao)oLuNj%MVF3B?R&zVY1m6xejUaQjsvdJsvrd{C*k@Sicr4;)+| zNxw|^30dQ#kDl6}V|(Gkv)@xhtjyW{{Ujh4_rxi-BXj>0_y;!~mTImyb6IxSV_k5c zZ8g~TcWGpWCf3V3U;mw|yz;)h6@AG213dl6FXUw~)L{x;KPhw!{ySNyliHT}jhB_a zXK0K3npeKIP32Z}FYLRx?z*o>HA+Ay9JwxwZ%JBJw?01k)_#K+^6EO9W!D(uO%?m* zP2Rr0&8M{1=JNK&uFmi&xVSU-cX$@4@6*ilcj~S5bd;_O>LkN%QDYE{x3jbo~iY7q%o6p?_AR6_W0dtqy27kYU{aQ=yDFt&wDJvByf{oQ>to-l^%Jw_l7 zM~QZJNCe9z5~-IS=hu$WD03i74)DJ%=xvAl) zh^hW>S_k$+3q@%{!C*{Nd8q^t64Crnb{h*9$cs+82P$h_Qm*;-q*tcfjxybNIVN?Q z?Us{fj??>JSMJ*+jjSjDvMdwcNKblss9&ACQ0Po;dgHB@EpRn*G!G;{O4_*r9omf^ zc88O5;FA>s`Biv5t(4rly{Qbz*^8SzPF7ecgTSh8eVs^4JQ6a=bAiDPpgY@%yKgzD zJG8v=T0CPAGvNXwdmoVeui=XNw~T;3 z$LXE)7BFPNH_IcZ&_z`DXAcXS;ph+u>gZPMCWdN*$w6$$QK@#Z=AK>-vS(Ux_v)$D zFkOK%t)RmFLBWP5&vu1z8xkG+hlT$RWLd{T+a+{EKUWQm^oG&Apf(b1Z3g=rUS0S3 z$4G_TuZqA)$#u}8m9*JX9$xi<2bcO+;Bf*xhEy#-KbUBPx1>2y+!U#}6XVS5XadD> zZ^xmZj6bh7wko)o*bjFnzmI1w1QbuKO-hudQrfV{(Mc^8=@Jo~x_lOrnYEf}H6Ohl zNtMBb+fxWAm3Gt(2ymc}CNIZ+mim?sFWmR8x@VQCQZ%lun%O~qmg~E6v|!5^eD&G| z^`_1j4I4#mVB?HNr*O5kwX|R!j#O6Msi#4Xf@7S{kDu#{yp;%X7zo_`fozbsW#hzg z1?v~ms6LLJR?rfYiIo-UyvlL>%fBUCKqxr*fZTxSK#0#OF)|snL4n_lc&88f`WqiF zF%}NVp*U**9Q9?j#kB|{=}=`ix@eQU!g>L+X6k)%Xd;w#(NCjNN$SvzM=>V0CT?ot znC!Dr`TLj54xGT&8Zn4yyBCztQ6g$JcG^e!GMxKo>@wWT>u)!cERt#__rlQ+X&u|! zYhWFBv=5Uh*NNAdQqkV72PwE;CT4Vkgj!MG?|JoNgl;A>ne68uT5wsZqSd@jlql1p z?sNJ@1H|HqFZ%bI`k(MYNdXd7eO4cF;PQ}SpdiUQQvd= z7`IXoR@|N!lAhorrhNI`s+Q zb7TDqd0Z`Vj6e+3_1Nd4gY``KWuXUu7hdL4n9}!WwaI1_>lEvWS(|0dlaoTa(dF$S zfFTs{$%ufILnGa(<9FeGl`lzOrSeNFM7HXmQP9N3{ZTCN`57%tnjK>clT^3jdwC6B z3sD*h2lu2}EUuK3t(D}tTJr(pWEmMw#tvxnzsnYlI0f z&Q9XVuN-J#WwgkkxdAVzvf4@E9hsF^<@$PlsCkrX9)i+vtE|QsO&++H2@KbU6jk?4 zIqAA%=*4rC8iZu=;q*5hl0L&fP!Xp$EKo`Jdav8mNn6hP*FDDJdzehmjq?l(@iQ9{ zEGLH@=&5ZO`df}Zs%H<#32GhD1mCf9goS`*%Ibr@-gCBx_WbgP#Z2BldG43i zk`BZeVzddkPYeZfgAqvSYVi82LF4D+RU?io>GoryKXGBs(0_bJr2IWxXarGxl06Dg zRka$(JJ&ulrPM97qYY0{og&BFa$Pdq#=Lu%hWP_IgY@f?<}-{^ z(L4UT!pe}~6s7AdO)3M$dZH@ja!V`oPQ|+LIy!62o!F|yB{l1;oX#w_v+1?RWd*nX z1~Msgt3V#IV_@v;S2 zF3HC1J($GzD~*QcOmZ|{^=W~P?S1^KLbf}WHr+W&cj{E*6{?#zX0wZ-eFMc}Rl*)| zfUG~q49Rn?fWJsxb>*?tJQt5wsG|*lElUqqjFQq;Bl;*FR9e}O)kfSP@pR1OBT(Wb z21Rk522k}zb(P0s9#nkaz%m_;_2Y+02^hEbyZh&52RkWYB0nFZ<-e7XJFP#CClP|N zfn>)f9VtP^tjXc;!9DB~PJtSPLL~c8(O&3uHB!+gAbbwINM`8 zGJg~<=7H?9Kw+f>XhZ$^<{3ICX9DsKS#v@SF-LFk*OBVjG5LHZb!&d+2autPaOSzv zwz|1)^X@-4SH;ao&?1E-&F=9S{)*Y)y0q~w-+VuGR^Pag#s2D6?gHhKLF7-w1T#wXT1A@CrqjQXZ`C7H!R-Hdm zKS}9PshsBXjZ!GI-sAr;c22>Wg;AG|ZQHhO+jhscla6iMw$rg~+sPN(PEY^;)VQW* z>YU5B-ivc})>?Z%s}|#LGCB;Ek{T5JSXb)TZGi|E^>QUQjGbmii3~!h#9#FvjhUlu z_ps~J@jP<3JoC@UBdst8K$k8cnuYEdOC*s@zg_9{^p9)qDyynqounA99aU$dSZ}=p z@`PI4N7!~oX?N`drCC6Q-eow$!0l^A zbL(wPp>NyM*#H}EhqBY25@EGsD&A!u+u3=*8&22PK@z~u6d!4`=o&uri7ae3r1 z+CJ~EP*TV73E3?4yIQHq%ci|&7enS)ky@Rr!`}H|LIomQGs)yM!>}n;kt!B_!wPlzohlo@cZvY!2c(&7VrNe|Ng6zcVYa$gv)rd zv>i+cKtTW2P=Uz)A6GcIvv9C6{ZA^gRnN|2vmNE%$W`HW-cqn`7S{MyTEBrvfFmb# zhtQI#i?c5r)ZOa9G6(U7-AkKc&t?-{AaU0>l5Ez(S!N;|PS1P~BJV!vnPM(b3_d2v zQ|^`@R5vqJIg;y%qq`UAN$)EpURYN~+s%xjuLTRdu)SqKkv7$Jq2Ol$|J`-vBb0S8 zvTNT4=~*zU#Tr43ZPw=;V8MNRr|?xPHcQX@hjY8+67GO5fV3gwpPD&P=2OiBslwcAQ0fmEuz1~Jce3fT<$fZ1M9_-1@&)t~Uk0IiM9!z9~^(bF4 z&AfyIuu>s~0 zdVir5#U|dESU^a2=)7LZepZ>RbUwROFLW`W!{7b<7OV59%eqbr?U=Q}%oylAYk?Ey zgF7ibEU&!wbMAq*!tMl?W6_F13voY#8<@`qC&p5+BYZZk1*g#)@Gw*J;cy=1&3T@1 zS-^b8ZSL>S``s)OKqr<|abb0R!Fk!_u6v@cL|Djx2+faKK)nB6F$$SADI0V*w*GDd zS>WGZga}HuM7I>gJf5iGS0zS|6Touh4+8T=4GTtBs#%|}WB8jg1a7q4dS5{M#WKVQ zX}xDuJWr&+&CtX0+VNnnA>m>6GOl731(tM3v=FQH0^}aQ_;4F?+I$<6Q0zG;DpT>I zJ;(U+gz2&8(a}L50>&707)_~CtUd9$bOuy`$0Ls0LSTM!X1-|`$;4fI`rTm#zm004uAy5nn6FOqgsVIdGQO% z)RMQ*I>!lIfubF_ zYo4I;dJG^Tr4)--|3&bXJM6|QHry0t z!x$IqW*lnStf<14-3Vr*-4B2vwv8S!1RZ52#T?+5Qi0Af{?LaE@75krk^&GN{OnaA zEO|f_Hl@)tD4?YhOy^TG=iOE%_(v|?`mS~BhO@@Lc%_*~|LNNQ+yMF1uAwnahpz&Y zebTr0i#(=wtwm3`1_(ZOMh%n(ooO#C%{x}W>a=C&wi5T~w|p!yThDC3c@z%a1a#;{ z$%pP`yIPp>)UkT~(oC@T=|jX%tM`sUzWiCaz@RIH{wKL6(*Qw+kKek(ntyX0?vDHE zbmtXzlJ5eSijgIJ?!As=U@W`GKbp*!W%=_|a6}kPaf^nXz#6H7Skz*S*?Gti_4vqO zh7G|c6+*lH9tZEv6wbxh>iNLIfCc>d+uNH9AdCfjC?76?k5~wcNV0#BP5NgiJ^R~O zdBe3@x9f7n1~V33*I%2DI5AV_@2y*LYvGgNiKB`NtmM*!LHmKlf<>{yyPA@7--Sr@ zH0Yxn&=^|>e4jnbs~6*MH*s*A{^%F7jB0cqJ|=QLw6uBH*OFStDd)@&$B0J|u;K126j zlCsi!gV7kO(+C!IYS51{%$hoZ~#k%k0Rgop&sF#2_S( zY1Hyy+)Ko4tQ!s!Jzu;oBNCAoC3u1W{aP@2Rs?E1=^EBWFU2nUK|!_i@zJ-;G-Zyf@tpSz3Q z0h+pi33=(CSlVFttLdqU-8{rTMm$2Vtz)#=JMIPBARfpule{h{e1}nxcvq}s#c)yy z?x_d1mupEZE`N~`81x0DUnvG1{`7dpkcc!OfX|APp_@hQP}NAEs^dHnUjvzqYa@hk zs%7zY{_e4ULp$+AZR=R?1sJ2CyRyT)5<+)q>4Bdj3;)FR;t_G}VAYjWwi!oj<3xoq zIHGgv-t9luHJ`=w)EBSf4Y--mD-nwk?CuH7W76PkU9D3P23lr>m6m8u!#ARFD4rY9 zY8aVv+H@|Y=~Jr)N?*rH&b8gmNM@lXD;1QV8lip};)O=a|K3`oPA!06YP=Upp-J9I zn3EpXSO^_~X(Le-XnE#~2o(|BZ(b$4P^M_u`~q6}SD5Up5C{PY8>~Nc^r9N<1M!40 z#V~cfdOFXu>V3MG&$ZE`@<&DGTm^^BAJx{=&N<_BVi%OFRK1hqjZ6A1wfx*PN5yi= zAJ%I#$BT~3zU>|a0Fgxk3R}kzE}~aHR;-FyxgYJJJol~I#~*c2dY-LJj?vE9c1T>D zf4w41^?Kd0fuLZy?PD8#_WK6#mFkymyqdZ=T%#X~xLp4ZH0X7w1}=P@uh)T(nY1KW z$H+zE(5jO3Y4&rJktZfzICua0@E|K$l76W?oUGt3iJ39oU5$U(XF`N=t^8;e zQn02L3sMRp_K=}op}yn|8<2aEYC@t6jf-HPT_opd8NYg8>yfMdhqpR_T6<4xaa+5| zYdlO$1bZYqoEo2? zQTsbo0u0qC6!evVk|3qZWD<#oVRNqOI(iU_M3w5KU7hR>1afH3`96#%n&f%*nDMDz z-DXAENs#hSZcJ$^Cj*QVq?cpLyc9nPQJ2CBTqGd%K4Yg=COF7&rc45yTur#pl7@Ew zA$6C$m&zP-0T&y@3)3ZE?=W>;oR3?T9=j-Bvd&Tdbs6WGZ@=^IaqiSy6(bpX{s$O1 zM2U@?!Z*(5!n?d8+7D$d(`pFa2}{J21t*p(!Mo4r*MoS&Y0Fzu!#?3X2&!$$4_oa2WlkELfgJBl&i2fFwL;l|y z^!A;x=9?cANRZscGsotC;v5)jCyMQ2gWfrEC#xK;qhU<(X>0OnsV;U=NUA zbnOux-Z?zOWjuX+qO}_`j&P~DWbi@c?fmoZ(Nx!~X-464yc7ze9a$rP)gQBwjLTAf zV3E9~8BnOi@88Lr`0oAKV*FcmBv6--#AtU%7vGGe=k_%G6@Pr#;B_6nNY}1@-`l;| zqo0qeO*nEne^yGVzw=Xe%jF*c7*aSM%z{^WP`}~11ouQ<)Yk@*!jy0!9Q}vf zEC$tx%ZKFKY!1DK0gq5+0n46wTSJ;3%gCF+B|Sy{c!RLcDK;=-D=|~UIKckA>tg?2 zlKLJ5R%PdrVOfQUH5QGCYTgIkL`?EG92RC6GN-QutHs}#ofbw&^L%;Ym3@97Ggz(w zH9W|Yn@MPoR%(WVjKaZw(C*5j7ZB!Hd7U5ZKo!g%3`34mwP$d9GqptEmG|Dn{^SOH zkqoTJJd96z^$UJRj#HvMn;Zytny=Cxrl7_Q>JF`U^j6gp=d=KvzpMfZJ;{0>#Dcs3 zFlIysCqDHN8lzU%#<=H;nLE{v%PGBEB`EK1u?|o=|FnF7X~4(-$K-k;B|eP3qn?Fj z7|llE8TiB&GCSQb#6)>DWyYu+deg#RJVm5tUUC;2LafofsyK)DY#+iSo{7gGS1vk*DY~3M?t^y^?ei3mP5uBqVxM@b(iA zETz{-o+u$IGEDZ`22NmDxW`7*70te zq9}@9#ZYcp%-%o>_q0w=pXBA9r^X@%EJ;#-rpJ9Ki&;iHa-W}|IuJ1^&s;s9^MQQ; zCBtkSd5Reqh}J-oRvIU+ReoG73hsjU6{!vW<0+gN=e8uW5k;H_$r4DbpXq5jCLjyBS}YAe&@zO0POpkRhF!;O}_b|4Dpiu)vVXIIvMP7 zCCFS6TF9R>xWG$#J@FUue+@5VzxChgFoA$_-2Z>1Co2cX|13SP`ECB~OE>L()ADRI za~qkELf?7DT+-;Wpvxe#m4~!wgLkZLkOFbzpvXqD(x%5QC_BJ!?O`3F6yfO zy2k~Pokh8vb@0CfX6mq z8n+{Q9>4e)KQ<>eKg4WZkI z(zpxaVHCK;WzBT`z5t+b|Dyo7dIM>)9`AtBu^yIvAtoDkf(VhY*P$ zl9>6kRp|Mb0=Kxhj)3~?fF3NL~zXx$E5(3`$`9K#(nSjw zAQ`MrC~Pw^=tF-w+FTSURpI*2+*RsP@zJBb*bh=38#hLt^6<0UqqP#?FUlYdrVB&{ zCcnT#vxa`bnOefAieg=lC;Tbvxk6NSkg1|YPQCcR%>5X5X)5_t?4H8@v$qUBGVFcJ zf|}SCJETJTuVuhjzbpi&y!aO7SXC!AO{F&|j{q-iaWSdudo- z-=HN&CZWsZkF9NT7GDnrC}C+QLbcE`=fBo;F$O>Gvb&26rxf>g2AB5gS&NB1go*m+ z2$Fn$r#4yD_)>Kr{&>HZmwql@vz%uJ1@;`X$7X2Km5XgJoKDLFNe9#mJ;G{%*RJ4M zSV&r|QmG0YZ{-YLwfAJ`{mMdGt5aPZ(J^p5>tSBE1A+0ERNB?E6!Ix78+S5pkZL6O z!~WsRy@J0f4Om<}%J-9IPrO*%^<~JMb*UY(q#{LY;;PJ^5`s`Dod!~3sf!$8wEoTZ zru04}&SR5)?ITMSi?k?ydq#PK4!bj)M-Vw>klk)!b=OnKg@Uc0k)Qc<51;avV>fO|wOKV%6EZ&zhslWo`M2GTL?40h zIv0e5B;1NW@KKXj-qxIylxIL!XRmY{^9U?nj`LWLb(M+Z^HafA7ODoY@;g<-C+|v|wm`bLq|FUD;mtNa?_T_+M_0g1Y)k!Gq-X zKNIcvWw>r^xA%eO$)<@gWi3X59D$MczeRl5g?GHca=IM$|D5e<9k;?%Y0e9L0E;3j z`$gpRiw7Mr;|LU<4UB;{xZL`fuw}k0pjqn>O_QJNhWLS=Ioz**$s`%vSXE**i2Fwa zl}Q03Syh%KWtRm>vPnbBM*X(I&!WLg4h)LoNYe?a&O+rjl(9 zI2mfq>i)P|%COb-8BkU%XCk1mmT-8EIG>0avG>#Ec@(_%sbgh%mI4kp-@whO1KMK6 z0a|!DgOx*MwF@&^LYSrV`XPQm2iDmF%xwpC)F%{4Hc{i_`pK9Tb=2D`{T3ai{iiUq z8BYP4QV=dd))aAR-42Qg8~wL2uzHFH*b7($&4GK9nX1D#AUSwB>(FIW9bFNFYZr9t z@U6=erAm_Q9X>(yi+Nx-^)K{KM4FW~GKC_AJcaCK^y11Q!qW{ZpaM2r2rFxLxO9L` zd&Cb+h|`1Gdru=gocR6li>61duB&O;y%aOjDG?Wyw6WrJD+pt9C62q6BoBW3sf?7r z0i#5B>FvS?8rG%{I+EKl0nh~Gv12@{7qIt%`WrWG5IY9jkBrr;iWWP~1g)`ujID`c z%mSIx5-p)QQ?5C=DjEVn8bj%840;%BD9Ev+sm59@ZhEStWwAn*e_d+6SOGDE=XW?) zX)Lq~MiSh;?Tl4~Te7+20&)Lqo|=Fq8Xu@r}|7D6cU2o;^c&6L(qS1-2u;B4IdA$#+oJr0hn8p_wQq&OK;S2-_5p_ zK$vd6W)pz#Y=TLZfBIo6KHAYMJQk6{sYvyg-ydK4%-Kz8QG`9!9QjKdJW00b(~+-^ z%;TiBI@2|zT^7%|BZ=MZ%+5dlm|pwf@1V^2&w(Lng^V1nBhO*lb2Sa5oTjmNj)D)T zOd;XK4U|D?5>{i-pQ_<1)6=&?7-3?iuz^4Gx^&$mY~r}pR@gU<7i8SQNvzXlb*W-b zblA(QtQ^MVRnKV2`R;S(yZRXMuc#H%hmL~T=&T#lpF6UQP+vsVgS|Opjpwkry=_Gavf7)9A&`?5lrs3cD!nE z0+psD3Z6VvJXfkT8aibroHSrpwbmM7dJWYY@|!jmpkG97eQUC$;5*ZIs$G2M|9s}U zL|-ih<)(>GU0@vGxF#26cPtFl3+2G1S9cq4O7!UzGa$rPNQ#p3xO8XQZzf}*Ka3_o z)n>XI`_Z%Pv4u4zaD#mtd(>}!&zhZD2Q=E4cj!Ei9f@ke4=md{MNE?{_m)+#w*uIJ zYXc%gkGM&)^u!*?#n?h4pq&Lc6AJH7 z#qSwTe1lY##hII}ft7;*z%+FN!WQ4FW~ZH>efXdGhO@et9w}f9*$yKrwHdUBea3e8 zLIQ)KKemzb$Js=V&9D2h#am@Czq6s?t2fT+vk7?)a(M@ve*q1uQh!yGm|LBhD~_6d z%a7j$^O_#T3m7N0K!1(w<9cDxlGPp@*d0N-y2gN#@ItbA5ZIAW@0O86de|=ksd0fQ zuevj9KL9;zk{9^<#;^gPk4@3ZYhtic`mG^Ufmnhc?DL>J4tY};Tw4_sKVP2v{R|^J zKnH@x{p3>ltisx!hj}&2Vca~JH7t)_2UC0qAqfQ#Y!4ZPz6yivr)E5-hj58ip7K8u z<0+U-1w0(}CYA~~E%l+&LVObmxmoF)sFk0@eB2;2v4>`?;d65JJ*4{Z)sXf;b@*@i zqd&kGb}t9YHS_?s9bs4w&*lCF2rcQKpcn?=$zD}RN)Stpt|W0dOPjL0#Z zdLtK06^}bP=Scas*K1sk9)Iq=QId7PS|)*XgVzX+f{YS67@;|tzb%)8yQn7n)|i49 zOi}d{mLjg=<5y_lG7bpOj34%=wB7riT^p=q@wNZyvarg{+`ubd{K`FY@(W&RJZVbX~Zq#%@Gc8LTkt3}gTM;PCv>lZx!xfA-Wf(kklGDm-xBJz`rN^nO zQl>BORhO-YZKXw&cN(XzprJ^x5yN_91I#;`z;3uaXHb}TII(qPLm~sb@+i)Tjte{0 zKHaw`L9xe3!czDI2`h*BIfwMRMn0WWTn-oL56>E^|5v|kr|WC}R?pDGNArnxT@jr~ zO7Ka3H6lxi{0If-i(Rkdd=fZR#19^p$^=7~`|qDk9>yXL@hp%k7f$7! zA$lo3*B`CgQj3|l+p-TQf-vwFkbdqKmD)d}ud~L0IOWU7u9BqDVn`o6d#;e<<;44S zCPZt1Zr45B5wON1EhOlv%4`tb&*{&)+5EW)66qzPhT;tK*{b6=b{VKG#sifNXgbbr z><^p`b6!2dK3g9L(;dY9@tk^CYX=KK6x9)NK1VCoRVc z;t@w*9G8ewab`!f;F2{$ULpFlwO=aInej));TaEpqSe7gGfZ5PP;qnt{7@pfD~q!g zTqdiCcSi7&5(WRX{3g6~(SAbNG4SjJG z%}((SU?K9^M%~&0T|y`wIr*0)9=OMY=}wgf=XmpM%udqNUKM-z3=hy>G)LL=(nS6} z##q5w#+jKf=ak?U@?-*ZH!sO{W~>4$efvzCcr}QQSI!N?dQ}67G)^P#jqXtEv@3+qq)`}YztNUs8XILoY>pC zS1~0iX+U%4+Y-?=u2}P(K+`-!G<<~&D41;Mk+ohc z2@-cnCEyH`iz<(Qv;ZX#D0q`xn)K-!jD3}2Wq|7N(*%thc$h8=g2S~Ka!tPgV&|O` zw@4jEoO?uJQfFu2dY_E^i@1^4OeWF#6C*7g{e|yw16o~=-)S&nB}r06+8UgxrHHLF zwg%_Nbi)$lbaZzPg9h8@`MW736xw>;55@8%&nZ9#ge*%A5FQBHKxmwEc&B(@Wm(^|tg6n!XXSfVBIl2sPD z86+<|@%(ZSe%v9D{dRs-mmE7Uu3CeKXTv-&5R^8QFS)&AaOzQU{9@EEl>H8-S}s?w zR_W zcUr8Lzk2M|E#`_8KHEdD$A(v&zy)Jv{3ZbW`94`&h>$N7d|wd3TRUL{5wXD$57G~% zr>X4)>y0^(mT3S~>|w;_D3&&U=U^yn?GZhgg~>e^Sdxz|MQ`iLlMG;3IVM+n=blxq zHtVK*#tUy4g1JLKe9FylVcwM-$-7uOioB9M8FCR{s+9doD4uqWpH9c zjb7n+=r7pLSk{7?^%HA6oGC42Q)Z~`iO}v$EWH&tA=;EMPn+6^^y#WQ@}jTMkeB&r z);-bRj8FdY=seLR=$lwPYaDEAw>2HXM+J(-d1-$4r zKGBN(IEJYq2Y^NSti(bjVFGP7AJ%fBhiR6)FRNDLC3(S~H6+DTI;B0ntf5#Y-&(fg zhkShd=)>`iHFT+`#U9VV&S>#Y*lzLDGhfqrt~ZB(DY0H@7$**57Ip#GfX+W=9IxxU zsKE>oasBKZO?tKZSJ+8iGgM-8%ooT~E{=Xs=DQ0IbW#U2O3fUZJlat0M7sv_c0T8? z)eSH!Da95vQ5@uwZ+VLBZRm%toPMe+Cr>w4=bp^A3~OJ7s@CQn#J8bd5OT9s?;GUG zRTjj%h~eJhUSP?s$!gR7Z*0z)-W@c}7P8!u3tRT7yMnW?eABU;!Ph}_Les!g&B*L5 zO>k(YeYbqJ;N{vGZ|{brn!`vS=zoN`@x$)3Q)1B{9#gi5m6Nhp@foyfL`JFY|^Qw86i+!-hj7aY-13U2QRY zRPt~T-{HHM-@Gb}39HW~WsI5*)sT^&p{@AYH)HI~=})BPv=E-P5^Gw<1~-~!$o0qU z|MuZj!`@2l!c3BK9vK%OHG9mw@|I(%*P;-1v#&aD)Ub}EiHPT`4}mIe=+em`;(HR z9Z4N-AUxBZREkvZ?jzEa2#$Q*lC$LIwX#2HIKf&08rI&w*uFlg{6%G& zW^@*jb+T)(=)1?u%F;WsbXqg3757u!DW8ER^E4nLS|AL&E~*aHG3D6ZmE~pW@*c|5 z<3F;sN?e4!f0josJ6(z!=*l5+Y`*@MNc&@hv)HWFbmH;$0I^-tuF+_jmY7upncCEq zr|@pD$sRE-$XV#KIthGlrm4I(k;7cQrtkRmt*vnmH?PBTn3TX=DOzCeAbv=cbRV}~ z1E%s<+eDWJAFz_8w3bl4spQ5NQKKH?V33+w6`|?qcTzz?kom=6(NRB z)bD;opylt*ugQipIz!1QBAbt4a_5ue>L#Bb9vZbWw347sO-?i?Gxd5>B8Ks^@RtAm zO-S&v8t3tpXXm_!We6W7acq;dQ9|d`Q43dP@Qy)#OOhyDTBzzL4@xuCppyWhF{;}t z1WFg%J?Vp^h7X2HZgscLUBa1*Af}XT_&l@p_1^CEkO!f@bREBh2~(o%xyUDIIAX9A zaGyZZQ#$Y#K$NGBWJLoZw5Xef@(A@)0ge<`)@|d>i>e2=S5;?cXVr(c{Pzgz6NtWn7jc%4Eat%DCNv3P zPTBRiXrZ-B;4`KU^l$zRn|J81&!6Yt`}B1rYMB~|W*MATDN;Wu!0t5}HXz8u|v33jwj25B<7 zJcQ5S$7tZJPvEQ$WF&ZIlZ>-7m*O3cdAd9M?Ev!C{I4Nh#q{jV=-se|XL@!mZs-^q5R-|{3iC3@M>UWEKFT|EkF1t#<3{4T(Jz?@>0Z3|Pp~aE# zEz}Xo(G6v>S9qMy(9*zyz=zdK3|he7$O5#Ok6=wKf1w8ots}|T3B=N>C0dUU{IwMW zVNz=vI?YtS17EmlnpsQDkojev;!`v%e7uBoGEhADkk;fDc$I%uLvGxHcZmb?nu#w5 zfi(6}GEI5GG4b)$XV<#R=aWDh6!+=o>|v(jS`!%m&3m8x=e-C2r}s{I-xqmOyO!ze z;Oh`(p1s0uhLMv-7=UFs-v)QTda2jXDQ!k!=@TxU$P(jq9JZX6-0yFXwz;<7Qsi}v zZEW48`1EqA*zEAP`NZ3YbR0?;fIOgh8Jz~kcmG#MnCmh$n;PbVdwx^9gZdn(lBh{q zITEjdRW~hn`d0Bkply}^LqxkwT#Ahw*#P=Xg2s7DNEhmiLE6)&KXLqZDm@B-I#c&& zMFU!xFcAfbSl&t17;J1=mtg_xuE2Oye~1b0%QibqSY~<@_I7AopV)$5bO`zBj+Jhc z>Mk9oFl?d88R{3ix|4jE4NflKwiC#TaYdTdNx_SsS3E{so5p=MT%T3T;~nI;_iagi zQ5%-`6xXEi-)%uvU zF}O#rHsrnhstWnCXZhlVap7FZeQ`BYtj8vcyorEdZSKUV9V2m0=Z>GbcqTYy$IMmE z>{veS-vR^#@oJ`J?RV3(Z)eS$5;JR~xF5DB)EgUjM@l1RID1!Y2B-T7i~=OF?R!j4 zeG}-M6iDxDFy7JZZc~=0?CdXWFjX6c6(`Dh$=8VLk*!*^LyugD&sVY_si~W1-E+eF zT-B+?B%IhRQnYjAgz#$17@~G=55B@&ZfVw}tr^a(4?V`WkJA5+Z`~ZX-k$I4wH{%p z)@hW#Q`E1UptsKWC>0CqO5wBYK^Ooq^74m+UvMotoR&Z>S!lIw%E=c)PB8T>oh4LF z*t%@(N2x!jGUc74Vunrj+bR#Z2v(!M>ev7y5#1jv8G}opqsrbW?54>Tj{}U0cu}#W zxBVO&xP*#jm1C``Ty#`~W|rCeR}oGeJp7A}X)|*SG0T^W9d zG-8!3FudoXyI{2cgUw6@HZ?lS3p>=<(OF{4m}=q*}0J^ zefxZ=+kAnwG4-AjriE7QkkrhVRC~Dj#PH{f1)DCy3`mlBA!Au?GqZ58KR%4I$bHBk z+{WOQOSZQ*R^)s*!s=K5MLW+e){RQ-W@HJuWJfet58%w26kzQ9z9RnL<*p` zJ|{a3A^oLgAn_23 zXz{DL-l1kqD}2}Zde+ikM0`wXV~y8lZ@Is$Y8X~lT~+#hw5nnr2Qe=EG$0-wxT_<> z0{`js2MV7l2NN0|*tQad=PJIu-2Ly9#VGwss|UKCvs+(~^cp&LpAbe#%N!cTb@L7l}YHs0C$y*sZMyD%A)4> z8kF7dvAvgjECKnS!?kErV0nmitb*qOB2HOKkYUDrU=&2(`S3j(weh=FVnZVQ`&r8< z2OX^(eE)u^C>$1E?%t;p4N9 z#4tqmnK8&X8LcIi;p5iY>us`D4#HMMp|pF%EV^&m*{n}qfaFxrJ$O|zsNBG1>k}o9 z_OQIcY+_0+Ta6nYnEcA(YuEHdz-EhqETFXJ6w;I0A1LjxKK*sP$7xLq;${$D1R(c? zi+MDOAbSJ#M#8s>?nBVtx1m`e+0B3Kj&@DCDbJ=|RGFfIQafZz#Lgyj4T3cFdnnV> z;vez;_fZ_(&%;eN(!$Lj0E+!#`NmKl`3q%MiM9{10J$cic*h zdP<6^Kpt4OU2(k@XvB1rkZIa|28s<*ti4gJge|0L=TrIK0^glxy9=o514)Ki#V)vF zE$cV>%j@(SC@=@J|3^&~_OiL$ApV*`5aXg7HdYAWSAN0O4v4XLeFQw>22_t9;Fb18jj3^P}X{Moka?_FP};1n*09wfeW?^ zBv$6(M6JkR>nK;}*#UcACYlirxhDsU67xEcEf)dWQ(ZdH2#%62%;i}vF`PEZBj zSPsr}&36Am6D)E}p2dw^ow$>q6)wgB=IQ3__3%1;GM!*H`SRb*u3}yioNr7o&%41r zR?G|*Oj)gPoL%pQCzzbFf?-oZHxbO=cz)ajv2)((FZKBXoCBXyolQ{p3`}MYy-`=6 zGw?#g51C+fk3FAal2@kN!A{1vt_`ufB#t)|A6~>sN6D|X?lyv?)l28Lv@}|BKpjfl zp4C>kmHq}@(Z+DEejAfXF4dGnZ2p2%(}J}_Gwd2(8JH0gBgG#UNf)-b0vPOOBkrwb zQ%+)yo*Q?u=FH4r zlJ?^;2R&oUh66ypzI>NQfK*_Qzqs={l%VBN?Ax}irvqfx{P4g3YQ}MsUs5Cgh2DnuN~T(hP#P_>4W5DL`29oG#bcDqLG<52Ys^FY4 zSYPH2v*I-U^yt1UkpuNF8jY4t( zze#bF_afr(9V!uupT0$YF`^WHK`Ts=kZP$WsRUN}HHM#l-|yQx`bjs;Sohq_nSTC}dTl8YZ(Ob<7~HCymyAi3vb+*xOYIVn5eOxSaumLH z-6WKa2aDb3ahLr|pYl51FopyMD#wd5v^xT*gq<(;RrpzC+236MdN z2ow%1D2m{wbu#wvGeEN0=)6YQsN}nEpHsV|tr9g>?l9iz1uk@uiD76@XDQT&vSG-| z_G#dSY($BwHYG|=LlEYdVCDt6gm4~0oLQn#G`yi@jCg~ofii`?D%H8T8gr{$G80UI{K9;jY-DdqU@DY#|!BDeYskpf&w45vAe2YkM7_Z z$R&P#561hlh1)+2d>?3(F-WHOs177o7y%?_RVrEJJLb+b|7d=_y%`s9VDgU^qV*@; zG|$2&#yZ}yIMl>s1VOH0ToXHgaBEu>m6OjZr5FvcY=Xn6nHFTfryUx}y3FTJswG># zzZX{6?>aN_!X_A7K=F(EyT+ws+IiZy9V?5PhLwl3;A#lAbo!^5nYjb8r+-Ma-Y@I|5AKQ{U!bW^XLgI2;_w9(<{&&q7moI8P zWitgSw=4S0zP?}-^>i*($2%k9?~b~_>~%?q8c=(H+Qswk)u^`O4)(?Og8+5O#?xwE zD6ji3a5J&PqVaneytd}%2cYVa(BME=XbX9YF8~`^s6u{!hK_%eCk{d^9ZMFX624J$ zinum!#xlVBL(?+)-q8uDe55om(BqELzm}T?o>Nu4VvwbFjKTjWMrN-d)Vy1>&cyo9jGT@`d&A>e6Fh$G|H`Qs9 zC$vAOdTJ|fOFAEk@>w_4nQkodJh+S58M37EELQqCK-*;0Tzb=CD`&_YK7f#XdeICy z0kKW?Xs%h-g|jOMgUD{8q})t?7ug&R#!_eag!189d>LL2eQth5+ufTfYk^@or#Z;h z$rd+qJo=hLA%VeXQKr~rya#e@iy&p2(n-uF-+)|vTI0@u$P%;>XiIdqOb;!3(Tp2s zYo<*MNlHA#ILnha;ZHhMaRhfZOQLydn*%jYT7mE&PP>S!Y&|LLIm=$a#j)=4 z9V-a(PfpaOe+cWv#&5VPBq*f{eiTNrJ_Kc2P69OVfAX+!9J=2k6e|BbuYIBRvQ9ZV zhl6t_jVe_{S)nnpNyYHc*?JKMHnKW+%28TfGwM zDy^g$1@CO$np!2jk=O=O)g{JDo~AvSm{wsZJ0uucEJ%*VDMY++YG&ifv?ai+a|k4zbYMz zn`5d!DVSib%mtBfGiurM$B21PFXluJV`gK`Px8*dvU4HvrJJwCJXY>g*eOh7NtG#T z^LQYSDiW_3R3k;t=UBhw0aN3vmGRYl4+#=1*rMS__C_$-qW!l%`C?9bsuU<0OV(UZ zDhg6d?B~R`h zJCf5Ts#e7z$^$b^*n4b+Iql6G-x~!tW=F9nGFIXb(8{$^pzb|0!|;mSy~hU^VMZj_ zX_U6{?g|!jHH@M9r+0aS`swq|b9U$Ufcnuklp~6M8z#PHGK1s;?b18A;Pkb(KSNCN zsVOCbE#EB9lz|@oNHSNO3ApQP_8AD@G%TX340ln<6Y?lF9>C%|39RdHQfed_Nhk_& zWjIdL1V5;*w>XR3j4C1SfW$ysbRmxBgc*==pU_6~%Kf8X;8K`%>RyYXrTSrPxATsC znTbpJ=-9o%^1T}M$3FY_@*+*59J4&^%B{JI=OYwqo=N0|DS76JHk7|a+K-}SLKzF$ z4xdxn3ZTUq)@c^iJj+Hj6|+qFn;U}#0jYF!%7IEcdBpq44PiBQ34d6H(m|`!N$e7aXP&Kf zN2t%V+si02@1woWL6^BcUHG+5C)c4uB<{%WUVo8DmC-y9$xUp zqKuX1lquEEJU=3|Yue%C8)ANu2MApW>vRNN(h1Z02}XVt(nWaoqNA;o2fZ7i+7_54 z(EJN32v#~rn9nJHxb4A4R$TIiA=UO*Zzx}{(w-cYj__JR4%aI zyGhFaLH)#oyOvfa2ED=6W83PYX0)tyPpW7hp&JS=h<2Em-EO@N)#WgC0@FAQH~GU2 zt+d}wKST6P%Hr+Klw5NZ2;`(~_53exx_U_T~^T zGkjFWZ+bXu^xA+-u{5|kb0E9%@~@gXYrzq?mp!#+b>BwC4FjJ_5ICg%a7dGyP(@W% z#^I8|mB`-9JcVhN*ajuCN+f+j!yi{l@}m?_3A)iDAdK7MHt86c6h_D4OHG%_A zv9A70R@BL5*a3S$dh0T&9VHehUf14G0}(~j#0HmRCyrYnz2#rv#N$~j=EBxU^}1CI0h+YRT}e~c~mx?-A-X}6Ns zT4^;~tJ9kwKYc3M8U71r?-(RXux@R(ZQHhOo4ak>*lpX|ZF9G6+qP}n-P7mHMBIDM z+%IPC`B70nDx#tytFm(ClW(qNMtjQlWP{#dmU$*Ogri(lBTnV!&j4zYIkIe<=zI!f z5M6T<>)HmG4Aa`nai?nQr$%^;$|aH`Yk^f6?={XiBkfky{vvcxScuHxXk>I4fY5G$ zo3lvdn`3-Rx@t;#3UMgL>}f?`S88cG)zn1$bO!8Iv?$H%S9kOWkZ5w&$;u`|w{#AP(yy!>JTZ-p|L5xz6_xsz^ zn}RL`$|}z0OvBs7H%Idobwm>+KpPLC<|0T94RC{nnQ?Y;Ur zbO459hUNCYgQ}J-;=vN`hu68?C?M3L5}5kWNF2NPn9fla2ZtL?bdUIO6V!uMNeaD= zIq*$9%+7f|?BPvaTVL_(YqFG>+tqxEUtQH{@a79zwk8UXd(9N?YT+MDqZurmgK1tl7YiRgx;sd1exg{rS#cD%frEm*DQarVtnJe>L2j!8B!n3O61wJ+QbljnX#E&i4z=djB##s17Q#%C@a>TG zxYz~=q`qW;=XxVh3ae*-&pAlMuVPh!jM$)s9RMeme6sY^Mkeyl`#I<(K|sg_)@6&u z#4d&j!>AQi56}i%_olng^(>}hPr}vLk0wL_K<-12pz=2lTn-=qIrU;v=0up!YuNwP z``3*g;!DIp0#E<|<`n;bR-D+`nOOgu$H1iKkH-MlKRpKKc}t*aA=%=BLfFh;G5V5I zDlSPbFGp_~n*i*hCfCR$*E2pZx$h!rFp(vi+Mkbvjn30BCwZ6?hAhjj;byoCX)gdK zt-*<3I+;EY+?i467}6_hxZA|2x8KRx`iY6wZ&kOwJ-Z+jM`;Su36{!l2HfJs%{(Q_d~RR3X>+x*7ho$wRw*a(Y2v`NxK+6T z`{oRe4jd;VMk->=)8c4wz2*Wq#A4*jB_)kU#;;GjPl>%}y?WNm&P!-w_<=KZn>XeT zQ?Z;+Ec{37S6;$XPc3=u{uTjy*ro&Ry1=fk}8KJ|t zI=6vHU-Pp4nd@6m zyFyZ`UnRXK5U~Le`8U#TWw~eEET3IW0RTF1p?^ihraM+EfZT*RtauVai|t*Jc%6gl zkBriODOtO{Xq^7S=$9pA(~gCu=J?xWv`>wX4_3U3!IuV1Gn}YO#10oc>~Rev5(e_? zlN96Y0raahxvuwFf=11XBAqwAOH)*FfxXGf9bTf*cW0#%S|t&sGND!Fy68dYZ{W7^ z(!LR4aMisgNd(H6MHXABpy<`@1{CG>PL7ne_)b=&5d9Mx$8DyIxu>n6zGD&N5ohe3 z0$b(BVZM_hWoB}rR)qrwtjM{QM+S5d(>%7HJ%q(44H+wfUIBUy?W(or^w8 zqPSF{PP|9rK_tDCa-Yr1jgvbJDN^iq3aobcrk?i?dSbzL4M1LK1CrWEw3| z`vUrj;}-Jz$=i_NE-{!Vz~JFTz~Mq6qSbGWZ!R0N!;bvAO$zhr5V=|qltrtxyvRyPZCX!tSnII?ql3jSmL`&i)s;z9 z3M^f)Ru&rNpc5){&eyPNFC{eIezO!4Ts-qqm6sKKSVw_eIi>kKQ#+ z(@BhLiitIWR-{Te6t~Nv?`jK9_!J1C#GTdTv%gL#XUfF`pXs?-L(?6w7sG7BVp5s8 zdTu!U5+edgYsCGG^>93^4+&Ooe)yWlcH*@}Z(_o5dPe+5G3H2m-C$2aUpaIHTow|j zTx5TSuwKWB8tcP!+XvZJYsBZfE0bl0TX^)L&g`}cXa0F0^bh>sFE0T;xaF=$0#tb~ zAb$sM6Mi8PX(Q%%R1rpu!f9)tUdQxZ79uL}iJ4>7WRw1U$QUGaLJI=`AWKAf5l88v z`4%8`n&ksT3yZOO_XLKO(5;)!dlzT}J@;qM)E+4H+bIno;mdac@CD@CoR0e0seKJetMf zn^5T-=OQcTpk=OPHNRyIiYY`o;9~yTFbL+{XG}`ifU^j8Uymp}89dqh=t`fN;16EZ z+c@~pF@>-39-beqXGV$fcqtCdHd~iI+@N8_Fg-vO%>m&JGQGVZE{+{8wDAfTMP0}K zEs-Ej7bTRk! z*_ZcnB<`^Wc#qF4gL+JnlV0}l5KPov=~z+l+D_Q4p9$h8bTk=M-fj&1#(l>gzKCeblq)`V`&zhx zh9f5mD5%^|L#E_p(G)!vw8V}HT!C8$9sX2w?oDrL9Ue_&O0cnXjzJLK?_?4(2hu*M z>|f+Ez8HFLnF(`VOF;TfBxz3TQynZP%G7$9c<;_dev-D&9SXP}s)cSh&RoGg@&xltnLrI2QhTwKUQnN=eUFlJe zsmC!HrhHnQRp2a+y=J_+svE#O(_c{Niv3 zYK7mOE%srSulN;cu1#aJ{rtpDvObHzN~;j`6UT)0Slh;?fYiE4ELBCUD@mqtkpGBrh3fL*7~#ID%Da=K?}=w zX;(c^WB*$qFm2G$fEn2iP>7LjJI3UXUQ~$%tymhGlzA-*lxU+#Wy<)qmgV`D{TP2* zJIVXX%U-N`z$ImOHFRt7H=_)2z&J{C>Wf3 zK&l}C;1TUGvF9Urz{Yzqg=cVYr(Fb7sev9l=sX$)qRh|vR5C|m?papxNrNg`z0OcKQ!mZihS7XTa@(s6# zY8a(h2xC>y@5Bz#im)Bpe4bjZAa%{=u!A$>NR$iV#w;YRiV0R9T^efghZ{tpgXU75enuYO`Lw11qnS^k%|{C^y^{woGrC$3knp8-MS`ZYq) z$$~g3Ny!0_P(dYJA_?!WM`NbjCXMXHh(qLy=LJ~YPMvPI&UBQvF(lIk7G7>EX;6L2 zRKMj*NX_M__0vg~cgfnZ!*7#!`QgwZBhBfWRK0DKJQ_t+Ikt`6Ql$mFBBD^wDU+fD zH%}htg!PW5&Hn1cP?urzRYzG-_uTjn5rw7!h>0@E4`3rD{7X^Yh#h@L58z z$o%!sJy*_9M=!Ex*hmr+Tw~TCjN?6xFcR1x*v~-gv!_CcL_!R$6!yd**_Y^hlVI5Q zQw~JD)s@Msgrq3L==_)Rr+-T?x^k*$_W=h02*d^e;QWsoQqR%E%;~>niFl{kIIVFY z_;ue?v&^Sq&z-U~F$O(2vLCD22Qr!kBEO05gCxF4S%b;MZXJHTO9HJ^k4l=ZfxNCR zTK;a-OdF5IeL$Sdspr)+%Rq_WUN=_DH;f)TMgd)Cc{Xa;MD;C-Jv1BB9DF*a^Yoxo ze7*&;h*)bU`92--Tp^uag=WH2Jw5WQllpx^_MXX8kuYDvpRJo!za5+!0fQ6F~#CI)3%bQqKQ=GX^EkNHLxb2Hbdt< z_GBW3UtP~bT~A~-Vsb3pM8;f!IF!en|4qa*)N=3dU&`V2inuykq4*qkupNK?Rg8lg zOvNo7yrMl7MIp2kO7MyGlIZyhuuT_ui6_$yX-3UbAIE(g2UEd{z!tm#s|8;BHxY0r zmU&+zml_mf%&^}rZNKXrFjb%?JW)p5i z4=RYYk*$Q^B&c*^F$Nq*E;;ZdlSPHG8ayrEhA?6c3f~4(t|J@897^&KU>VGVfa>}! zj5sr14R+6cbbR1vk1Izmr0R)C42Ip|2}}76u^>P223Q!;#VV6MC}3M$GvIb#F}X1= zU=^YMNUc3A9D;HL02zn~Hsy?nQwVLh2`U>vek-FM1n7WEdL~P8Gsm7&2!L&9HGvyb z{}T*?uqZ&EK4T+bE_DYKD0WP6)@cTnoPZIOaxbtmEo>eRD`J=qy|qw2jtZhWouSS= zmH_4$fLUQav;YT_y8traehm1pEkvv)+H8d6yxkF34gpj^!$Je!N(>83asoU+0d#=z zZr|lGJT|lIkn+%L5CE}`wY?SfiNJd~Q>wxO*hD9Uzj?(Jmk96mlUv2wJBM+yUUS1H z2!35tnQN>azw^jaDKixTh-y_)^;@@~q9?v`x04+KutH-Z_suyQ2P%MO2DGPey`~(t zz$=Ihx9j_Qe%|PPFJuJGxJXzi3t~PW5}`^^Sb01IAbuu+T~i#PtRKzvAHtdrr`kco z-Q&5sfCΝ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 Ee~ccmjQ{`u literal 0 HcmV?d00001 diff --git a/esp32-cam/esp_camera.h b/esp32-cam/esp_camera.h new file mode 100644 index 0000000..dadd0c0 --- /dev/null +++ b/esp32-cam/esp_camera.h @@ -0,0 +1,195 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/* + * Example Use + * + static camera_config_t camera_example_config = { + .pin_pwdn = PIN_PWDN, + .pin_reset = PIN_RESET, + .pin_xclk = PIN_XCLK, + .pin_sscb_sda = PIN_SIOD, + .pin_sscb_scl = PIN_SIOC, + .pin_d7 = PIN_D7, + .pin_d6 = PIN_D6, + .pin_d5 = PIN_D5, + .pin_d4 = PIN_D4, + .pin_d3 = PIN_D3, + .pin_d2 = PIN_D2, + .pin_d1 = PIN_D1, + .pin_d0 = PIN_D0, + .pin_vsync = PIN_VSYNC, + .pin_href = PIN_HREF, + .pin_pclk = PIN_PCLK, + + .xclk_freq_hz = 20000000, + .ledc_timer = LEDC_TIMER_0, + .ledc_channel = LEDC_CHANNEL_0, + .pixel_format = PIXFORMAT_JPEG, + .frame_size = FRAMESIZE_SVGA, + .jpeg_quality = 10, + .fb_count = 2 + }; + + esp_err_t camera_example_init(){ + return esp_camera_init(&camera_example_config); + } + + esp_err_t camera_example_capture(){ + //capture a frame + camera_fb_t * fb = esp_camera_fb_get(); + if (!fb) { + ESP_LOGE(TAG, "Frame buffer could not be acquired"); + return ESP_FAIL; + } + + //replace this with your own function + display_image(fb->width, fb->height, fb->pixformat, fb->buf, fb->len); + + //return the frame buffer back to be reused + esp_camera_fb_return(fb); + + return ESP_OK; + } +*/ + +#pragma once + +#include "esp_err.h" +#include "driver/ledc.h" +#include "sensor.h" +#include "sys/time.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Configuration structure for camera initialization + */ +typedef struct { + int pin_pwdn; /*!< GPIO pin for camera power down line */ + int pin_reset; /*!< GPIO pin for camera reset line */ + int pin_xclk; /*!< GPIO pin for camera XCLK line */ + int pin_sscb_sda; /*!< GPIO pin for camera SDA line */ + int pin_sscb_scl; /*!< GPIO pin for camera SCL line */ + int pin_d7; /*!< GPIO pin for camera D7 line */ + int pin_d6; /*!< GPIO pin for camera D6 line */ + int pin_d5; /*!< GPIO pin for camera D5 line */ + int pin_d4; /*!< GPIO pin for camera D4 line */ + int pin_d3; /*!< GPIO pin for camera D3 line */ + int pin_d2; /*!< GPIO pin for camera D2 line */ + int pin_d1; /*!< GPIO pin for camera D1 line */ + int pin_d0; /*!< GPIO pin for camera D0 line */ + int pin_vsync; /*!< GPIO pin for camera VSYNC line */ + int pin_href; /*!< GPIO pin for camera HREF line */ + int pin_pclk; /*!< GPIO pin for camera PCLK line */ + + int xclk_freq_hz; /*!< Frequency of XCLK signal, in Hz. Either 20KHz or 10KHz for OV2640 double FPS (Experimental) */ + + ledc_timer_t ledc_timer; /*!< LEDC timer to be used for generating XCLK */ + ledc_channel_t ledc_channel; /*!< LEDC channel to be used for generating XCLK */ + + pixformat_t pixel_format; /*!< Format of the pixel data: PIXFORMAT_ + YUV422|GRAYSCALE|RGB565|JPEG */ + framesize_t frame_size; /*!< Size of the output image: FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA */ + + int jpeg_quality; /*!< Quality of JPEG output. 0-63 lower means higher quality */ + size_t fb_count; /*!< Number of frame buffers to be allocated. If more than one, then each frame will be acquired (double speed) */ +} camera_config_t; + +/** + * @brief Data structure of camera frame buffer + */ +typedef struct { + uint8_t * buf; /*!< Pointer to the pixel data */ + size_t len; /*!< Length of the buffer in bytes */ + size_t width; /*!< Width of the buffer in pixels */ + size_t height; /*!< Height of the buffer in pixels */ + pixformat_t format; /*!< Format of the pixel data */ + struct timeval timestamp; /*!< Timestamp since boot of the first DMA buffer of the frame */ +} camera_fb_t; + +#define ESP_ERR_CAMERA_BASE 0x20000 +#define ESP_ERR_CAMERA_NOT_DETECTED (ESP_ERR_CAMERA_BASE + 1) +#define ESP_ERR_CAMERA_FAILED_TO_SET_FRAME_SIZE (ESP_ERR_CAMERA_BASE + 2) +#define ESP_ERR_CAMERA_FAILED_TO_SET_OUT_FORMAT (ESP_ERR_CAMERA_BASE + 3) +#define ESP_ERR_CAMERA_NOT_SUPPORTED (ESP_ERR_CAMERA_BASE + 4) + +/** + * @brief Initialize the camera driver + * + * @note call camera_probe before calling this function + * + * This function detects and configures camera over I2C interface, + * allocates framebuffer and DMA buffers, + * initializes parallel I2S input, and sets up DMA descriptors. + * + * Currently this function can only be called once and there is + * no way to de-initialize this module. + * + * @param config Camera configuration parameters + * + * @return ESP_OK on success + */ +esp_err_t esp_camera_init(const camera_config_t* config); + +/** + * @brief Deinitialize the camera driver + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if the driver hasn't been initialized yet + */ +esp_err_t esp_camera_deinit(); + +/** + * @brief Obtain pointer to a frame buffer. + * + * @return pointer to the frame buffer + */ +camera_fb_t* esp_camera_fb_get(); + +/** + * @brief Return the frame buffer to be reused again. + * + * @param fb Pointer to the frame buffer + */ +void esp_camera_fb_return(camera_fb_t * fb); + +/** + * @brief Get a pointer to the image sensor control structure + * + * @return pointer to the sensor + */ +sensor_t * esp_camera_sensor_get(); + +/** + * @brief Save camera settings to non-volatile-storage (NVS) + * + * @param key A unique nvs key name for the camera settings + */ +esp_err_t esp_camera_save_to_nvs(const char *key); + +/** + * @brief Load camera settings from non-volatile-storage (NVS) + * + * @param key A unique nvs key name for the camera settings + */ +esp_err_t esp_camera_load_from_nvs(const char *key); + +#ifdef __cplusplus +} +#endif + +#include "img_converters.h" + diff --git a/esp32-cam/esp_jpg_decode.c b/esp32-cam/esp_jpg_decode.c new file mode 100644 index 0000000..d42794f --- /dev/null +++ b/esp32-cam/esp_jpg_decode.c @@ -0,0 +1,128 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#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/rom/tjpgd.h" +#else +#error Target CONFIG_IDF_TARGET is not supported +#endif +#else // ESP32 Before IDF 4.0 +#include "rom/tjpgd.h" +#endif + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define TAG "" +#else +#include "esp_log.h" +static const char* TAG = "esp_jpg_decode"; +#endif + +typedef struct { + jpg_scale_t scale; + jpg_reader_cb reader; + jpg_writer_cb writer; + void * arg; + size_t len; + size_t index; +} esp_jpg_decoder_t; + +static const char * jd_errors[] = { + "Succeeded", + "Interrupted by output function", + "Device error or wrong termination of input stream", + "Insufficient memory pool for the image", + "Insufficient stream input buffer", + "Parameter error", + "Data format error", + "Right format but not supported", + "Not supported JPEG standard" +}; + +static uint32_t _jpg_write(JDEC *decoder, void *bitmap, JRECT *rect) +{ + uint16_t x = rect->left; + uint16_t y = rect->top; + uint16_t w = rect->right + 1 - x; + uint16_t h = rect->bottom + 1 - y; + uint8_t *data = (uint8_t *)bitmap; + + esp_jpg_decoder_t * jpeg = (esp_jpg_decoder_t *)decoder->device; + + if (jpeg->writer) { + return jpeg->writer(jpeg->arg, x, y, w, h, data); + } + return 0; +} + +static uint32_t _jpg_read(JDEC *decoder, uint8_t *buf, uint32_t len) +{ + esp_jpg_decoder_t * jpeg = (esp_jpg_decoder_t *)decoder->device; + if (jpeg->len && len > (jpeg->len - jpeg->index)) { + len = jpeg->len - jpeg->index; + } + if (len) { + len = jpeg->reader(jpeg->arg, jpeg->index, buf, len); + if (!len) { + ESP_LOGE(TAG, "Read Fail at %u/%u", jpeg->index, jpeg->len); + } + jpeg->index += len; + } + return len; +} + +esp_err_t esp_jpg_decode(size_t len, jpg_scale_t scale, jpg_reader_cb reader, jpg_writer_cb writer, void * arg) +{ + static uint8_t work[3100]; + JDEC decoder; + esp_jpg_decoder_t jpeg; + + jpeg.len = len; + jpeg.reader = reader; + jpeg.writer = writer; + jpeg.arg = arg; + jpeg.scale = scale; + jpeg.index = 0; + + JRESULT jres = jd_prepare(&decoder, _jpg_read, work, 3100, &jpeg); + if(jres != JDR_OK){ + ESP_LOGE(TAG, "JPG Header Parse Failed! %s", jd_errors[jres]); + return ESP_FAIL; + } + + uint16_t output_width = decoder.width / (1 << (uint8_t)(jpeg.scale)); + uint16_t output_height = decoder.height / (1 << (uint8_t)(jpeg.scale)); + + //output start + writer(arg, 0, 0, output_width, output_height, NULL); + //output write + jres = jd_decomp(&decoder, _jpg_write, (uint8_t)jpeg.scale); + //output end + writer(arg, output_width, output_height, output_width, output_height, NULL); + + if (jres != JDR_OK) { + ESP_LOGE(TAG, "JPG Decompression Failed! %s", jd_errors[jres]); + return ESP_FAIL; + } + //check if all data has been consumed. + if (len && jpeg.index < len) { + _jpg_read(&decoder, NULL, len - jpeg.index); + } + + return ESP_OK; +} + diff --git a/esp32-cam/esp_jpg_decode.h b/esp32-cam/esp_jpg_decode.h new file mode 100644 index 0000000..f13536e --- /dev/null +++ b/esp32-cam/esp_jpg_decode.h @@ -0,0 +1,43 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef _ESP_JPG_DECODE_H_ +#define _ESP_JPG_DECODE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include "esp_err.h" + +typedef enum { + JPG_SCALE_NONE, + JPG_SCALE_2X, + JPG_SCALE_4X, + JPG_SCALE_8X, + JPG_SCALE_MAX = JPG_SCALE_8X +} jpg_scale_t; + +typedef size_t (* jpg_reader_cb)(void * arg, size_t index, uint8_t *buf, size_t len); +typedef bool (* jpg_writer_cb)(void * arg, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t *data); + +esp_err_t esp_jpg_decode(size_t len, jpg_scale_t scale, jpg_reader_cb reader, jpg_writer_cb writer, void * arg); + +#ifdef __cplusplus +} +#endif + +#endif /* _ESP_JPG_DECODE_H_ */ diff --git a/esp32-cam/img_converters.h b/esp32-cam/img_converters.h new file mode 100644 index 0000000..2b83c4d --- /dev/null +++ b/esp32-cam/img_converters.h @@ -0,0 +1,126 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef _IMG_CONVERTERS_H_ +#define _IMG_CONVERTERS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include "esp_camera.h" + +typedef size_t (* jpg_out_cb)(void * arg, size_t index, const void* data, size_t len); + +/** + * @brief Convert image buffer to JPEG + * + * @param src Source buffer in RGB565, RGB888, YUYV or GRAYSCALE format + * @param src_len Length in bytes of the source buffer + * @param width Width in pixels of the source image + * @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 cp Callback to be called to write the bytes of the output JPEG + * @param arg Pointer to be passed to the callback + * + * @return true on success + */ +bool fmt2jpg_cb(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, jpg_out_cb cb, void * arg); + +/** + * @brief Convert camera frame buffer to JPEG + * + * @param fb Source camera frame buffer + * @param quality JPEG quality of the resulting image + * @param cp Callback to be called to write the bytes of the output JPEG + * @param arg Pointer to be passed to the callback + * + * @return true on success + */ +bool frame2jpg_cb(camera_fb_t * fb, uint8_t quality, jpg_out_cb cb, void * arg); + +/** + * @brief Convert image buffer to JPEG buffer + * + * @param src Source buffer in RGB565, RGB888, YUYV or GRAYSCALE format + * @param src_len Length in bytes of the source buffer + * @param width Width in pixels of the source image + * @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_len Pointer to be populated with the length of the output buffer + * + * @return true on success + */ +bool fmt2jpg(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, uint8_t ** out, size_t * out_len); + +/** + * @brief Convert camera frame buffer to JPEG buffer + * + * @param fb Source camera frame buffer + * @param quality JPEG quality of the resulting image + * @param out Pointer to be populated with the address of the resulting buffer + * @param out_len Pointer to be populated with the length of the output buffer + * + * @return true on success + */ +bool frame2jpg(camera_fb_t * fb, uint8_t quality, uint8_t ** out, size_t * out_len); + +/** + * @brief Convert image buffer to BMP buffer + * + * @param src Source buffer in JPEG, RGB565, RGB888, YUYV or GRAYSCALE format + * @param src_len Length in bytes of the source buffer + * @param width Width in pixels of the source image + * @param height Height in pixels of the source image + * @param format Format of the source image + * @param out Pointer to be populated with the address of the resulting buffer + * @param out_len Pointer to be populated with the length of the output buffer + * + * @return true on success + */ +bool fmt2bmp(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t ** out, size_t * out_len); + +/** + * @brief Convert camera frame buffer to BMP buffer + * + * @param fb Source camera frame buffer + * @param out Pointer to be populated with the address of the resulting buffer + * @param out_len Pointer to be populated with the length of the output buffer + * + * @return true on success + */ +bool frame2bmp(camera_fb_t * fb, uint8_t ** out, size_t * out_len); + +/** + * @brief Convert image buffer to RGB888 buffer (used for face detection) + * + * @param src Source buffer in JPEG, RGB565, RGB888, YUYV or GRAYSCALE format + * @param src_len Length in bytes of the source buffer + * @param format Format of the source image + * @param rgb_buf Pointer to the output buffer (width * height * 3) + * + * @return true on success + */ +bool fmt2rgb888(const uint8_t *src_buf, size_t src_len, pixformat_t format, uint8_t * rgb_buf); + +#ifdef __cplusplus +} +#endif + +#endif /* _IMG_CONVERTERS_H_ */ diff --git a/esp32-cam/jpge.cpp b/esp32-cam/jpge.cpp new file mode 100644 index 0000000..a8ab93e --- /dev/null +++ b/esp32-cam/jpge.cpp @@ -0,0 +1,723 @@ +// jpge.cpp - C++ class for JPEG compression. +// Public domain, Rich Geldreich +// v1.01, Dec. 18, 2010 - Initial release +// v1.02, Apr. 6, 2011 - Removed 2x2 ordered dither in H2V1 chroma subsampling method load_block_16_8_8(). (The rounding factor was 2, when it should have been 1. Either way, it wasn't helping.) +// v1.03, Apr. 16, 2011 - Added support for optimized Huffman code tables, optimized dynamic memory allocation down to only 1 alloc. +// Also from Alex Evans: Added RGBA support, linear memory allocator (no longer needed in v1.03). +// v1.04, May. 19, 2012: Forgot to set m_pFile ptr to NULL in cfile_stream::close(). Thanks to Owen Kaluza for reporting this bug. +// Code tweaks to fix VS2008 static code analysis warnings (all looked harmless). +// Code review revealed method load_block_16_8_8() (used for the non-default H2V1 sampling mode to downsample chroma) somehow didn't get the rounding factor fix from v1.02. + +#include "jpge.h" + +#include +#include +#include +#include +#include +#include +#include +#include "esp_heap_caps.h" + +#define JPGE_MAX(a,b) (((a)>(b))?(a):(b)) +#define JPGE_MIN(a,b) (((a)<(b))?(a):(b)) + +namespace jpge { + + static inline void *jpge_malloc(size_t nSize) { + void * b = malloc(nSize); + if(b){ + return b; + } + return heap_caps_malloc(nSize, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + } + static inline void jpge_free(void *p) { free(p); } + + // Various JPEG enums and tables. + enum { M_SOF0 = 0xC0, M_DHT = 0xC4, M_SOI = 0xD8, M_EOI = 0xD9, M_SOS = 0xDA, M_DQT = 0xDB, M_APP0 = 0xE0 }; + enum { DC_LUM_CODES = 12, AC_LUM_CODES = 256, DC_CHROMA_CODES = 12, AC_CHROMA_CODES = 256, MAX_HUFF_SYMBOLS = 257, MAX_HUFF_CODESIZE = 32 }; + + static const uint8 s_zag[64] = { 0,1,8,16,9,2,3,10,17,24,32,25,18,11,4,5,12,19,26,33,40,48,41,34,27,20,13,6,7,14,21,28,35,42,49,56,57,50,43,36,29,22,15,23,30,37,44,51,58,59,52,45,38,31,39,46,53,60,61,54,47,55,62,63 }; + static const int16 s_std_lum_quant[64] = { 16,11,12,14,12,10,16,14,13,14,18,17,16,19,24,40,26,24,22,22,24,49,35,37,29,40,58,51,61,60,57,51,56,55,64,72,92,78,64,68,87,69,55,56,80,109,81,87,95,98,103,104,103,62,77,113,121,112,100,120,92,101,103,99 }; + static const int16 s_std_croma_quant[64] = { 17,18,18,24,21,24,47,26,26,47,99,66,56,66,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99 }; + static const uint8 s_dc_lum_bits[17] = { 0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0 }; + static const uint8 s_dc_lum_val[DC_LUM_CODES] = { 0,1,2,3,4,5,6,7,8,9,10,11 }; + static const uint8 s_ac_lum_bits[17] = { 0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d }; + static const uint8 s_ac_lum_val[AC_LUM_CODES] = { + 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0, + 0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49, + 0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5, + 0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, + 0xf9,0xfa + }; + static const uint8 s_dc_chroma_bits[17] = { 0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0 }; + static const uint8 s_dc_chroma_val[DC_CHROMA_CODES] = { 0,1,2,3,4,5,6,7,8,9,10,11 }; + static const uint8 s_ac_chroma_bits[17] = { 0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77 }; + static const uint8 s_ac_chroma_val[AC_CHROMA_CODES] = { + 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0, + 0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48, + 0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3, + 0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, + 0xf9,0xfa + }; + + const int YR = 19595, YG = 38470, YB = 7471, CB_R = -11059, CB_G = -21709, CB_B = 32768, CR_R = 32768, CR_G = -27439, CR_B = -5329; + + static int32 m_last_quality = 0; + static int32 m_quantization_tables[2][64]; + + static bool m_huff_initialized = false; + static uint m_huff_codes[4][256]; + static uint8 m_huff_code_sizes[4][256]; + static uint8 m_huff_bits[4][17]; + static uint8 m_huff_val[4][256]; + + static inline uint8 clamp(int i) { + if (i < 0) { + i = 0; + } else if (i > 255){ + i = 255; + } + return static_cast(i); + } + + static void RGB_to_YCC(uint8* pDst, const uint8 *pSrc, int num_pixels) { + for ( ; num_pixels; pDst += 3, pSrc += 3, num_pixels--) { + const int r = pSrc[0], g = pSrc[1], b = pSrc[2]; + pDst[0] = static_cast((r * YR + g * YG + b * YB + 32768) >> 16); + pDst[1] = clamp(128 + ((r * CB_R + g * CB_G + b * CB_B + 32768) >> 16)); + pDst[2] = clamp(128 + ((r * CR_R + g * CR_G + b * CR_B + 32768) >> 16)); + } + } + + static void RGB_to_Y(uint8* pDst, const uint8 *pSrc, int num_pixels) { + for ( ; num_pixels; pDst++, pSrc += 3, num_pixels--) { + pDst[0] = static_cast((pSrc[0] * YR + pSrc[1] * YG + pSrc[2] * YB + 32768) >> 16); + } + } + + static void Y_to_YCC(uint8* pDst, const uint8* pSrc, int num_pixels) { + for( ; num_pixels; pDst += 3, pSrc++, num_pixels--) { + pDst[0] = pSrc[0]; + pDst[1] = 128; + pDst[2] = 128; + } + } + + // Forward DCT - DCT derived from jfdctint. + enum { CONST_BITS = 13, ROW_BITS = 2 }; +#define DCT_DESCALE(x, n) (((x) + (((int32)1) << ((n) - 1))) >> (n)) +#define DCT_MUL(var, c) (static_cast(var) * static_cast(c)) +#define DCT1D(s0, s1, s2, s3, s4, s5, s6, s7) \ + int32 t0 = s0 + s7, t7 = s0 - s7, t1 = s1 + s6, t6 = s1 - s6, t2 = s2 + s5, t5 = s2 - s5, t3 = s3 + s4, t4 = s3 - s4; \ + int32 t10 = t0 + t3, t13 = t0 - t3, t11 = t1 + t2, t12 = t1 - t2; \ + int32 u1 = DCT_MUL(t12 + t13, 4433); \ + s2 = u1 + DCT_MUL(t13, 6270); \ + s6 = u1 + DCT_MUL(t12, -15137); \ + u1 = t4 + t7; \ + int32 u2 = t5 + t6, u3 = t4 + t6, u4 = t5 + t7; \ + int32 z5 = DCT_MUL(u3 + u4, 9633); \ + t4 = DCT_MUL(t4, 2446); t5 = DCT_MUL(t5, 16819); \ + t6 = DCT_MUL(t6, 25172); t7 = DCT_MUL(t7, 12299); \ + u1 = DCT_MUL(u1, -7373); u2 = DCT_MUL(u2, -20995); \ + u3 = DCT_MUL(u3, -16069); u4 = DCT_MUL(u4, -3196); \ + u3 += z5; u4 += z5; \ + s0 = t10 + t11; s1 = t7 + u1 + u4; s3 = t6 + u2 + u3; s4 = t10 - t11; s5 = t5 + u2 + u4; s7 = t4 + u1 + u3; + + static void DCT2D(int32 *p) { + int32 c, *q = p; + for (c = 7; c >= 0; c--, q += 8) { + int32 s0 = q[0], s1 = q[1], s2 = q[2], s3 = q[3], s4 = q[4], s5 = q[5], s6 = q[6], s7 = q[7]; + DCT1D(s0, s1, s2, s3, s4, s5, s6, s7); + q[0] = s0 << ROW_BITS; q[1] = DCT_DESCALE(s1, CONST_BITS-ROW_BITS); q[2] = DCT_DESCALE(s2, CONST_BITS-ROW_BITS); q[3] = DCT_DESCALE(s3, CONST_BITS-ROW_BITS); + q[4] = s4 << ROW_BITS; q[5] = DCT_DESCALE(s5, CONST_BITS-ROW_BITS); q[6] = DCT_DESCALE(s6, CONST_BITS-ROW_BITS); q[7] = DCT_DESCALE(s7, CONST_BITS-ROW_BITS); + } + for (q = p, c = 7; c >= 0; c--, q++) { + int32 s0 = q[0*8], s1 = q[1*8], s2 = q[2*8], s3 = q[3*8], s4 = q[4*8], s5 = q[5*8], s6 = q[6*8], s7 = q[7*8]; + DCT1D(s0, s1, s2, s3, s4, s5, s6, s7); + q[0*8] = DCT_DESCALE(s0, ROW_BITS+3); q[1*8] = DCT_DESCALE(s1, CONST_BITS+ROW_BITS+3); q[2*8] = DCT_DESCALE(s2, CONST_BITS+ROW_BITS+3); q[3*8] = DCT_DESCALE(s3, CONST_BITS+ROW_BITS+3); + q[4*8] = DCT_DESCALE(s4, ROW_BITS+3); q[5*8] = DCT_DESCALE(s5, CONST_BITS+ROW_BITS+3); q[6*8] = DCT_DESCALE(s6, CONST_BITS+ROW_BITS+3); q[7*8] = DCT_DESCALE(s7, CONST_BITS+ROW_BITS+3); + } + } + + // Compute the actual canonical Huffman codes/code sizes given the JPEG huff bits and val arrays. + static void compute_huffman_table(uint *codes, uint8 *code_sizes, uint8 *bits, uint8 *val) + { + int i, l, last_p, si; + static uint8 huff_size[257]; + static uint huff_code[257]; + uint code; + + int p = 0; + for (l = 1; l <= 16; l++) { + for (i = 1; i <= bits[l]; i++) { + huff_size[p++] = (char)l; + } + } + + huff_size[p] = 0; + last_p = p; // write sentinel + + code = 0; si = huff_size[0]; p = 0; + + while (huff_size[p]) { + while (huff_size[p] == si) { + huff_code[p++] = code++; + } + code <<= 1; + si++; + } + + memset(codes, 0, sizeof(codes[0])*256); + memset(code_sizes, 0, sizeof(code_sizes[0])*256); + for (p = 0; p < last_p; p++) { + codes[val[p]] = huff_code[p]; + code_sizes[val[p]] = huff_size[p]; + } + } + + void jpeg_encoder::flush_output_buffer() + { + if (m_out_buf_left != JPGE_OUT_BUF_SIZE) { + m_all_stream_writes_succeeded = m_all_stream_writes_succeeded && m_pStream->put_buf(m_out_buf, JPGE_OUT_BUF_SIZE - m_out_buf_left); + } + m_pOut_buf = m_out_buf; + m_out_buf_left = JPGE_OUT_BUF_SIZE; + } + + void jpeg_encoder::emit_byte(uint8 i) + { + *m_pOut_buf++ = i; + if (--m_out_buf_left == 0) { + flush_output_buffer(); + } + } + + void jpeg_encoder::put_bits(uint bits, uint len) + { + uint8 c = 0; + m_bit_buffer |= ((uint32)bits << (24 - (m_bits_in += len))); + while (m_bits_in >= 8) { + c = (uint8)((m_bit_buffer >> 16) & 0xFF); + emit_byte(c); + if (c == 0xFF) { + emit_byte(0); + } + m_bit_buffer <<= 8; + m_bits_in -= 8; + } + } + + void jpeg_encoder::emit_word(uint i) + { + emit_byte(uint8(i >> 8)); emit_byte(uint8(i & 0xFF)); + } + + // JPEG marker generation. + void jpeg_encoder::emit_marker(int marker) + { + emit_byte(uint8(0xFF)); emit_byte(uint8(marker)); + } + + // Emit JFIF marker + void jpeg_encoder::emit_jfif_app0() + { + emit_marker(M_APP0); + emit_word(2 + 4 + 1 + 2 + 1 + 2 + 2 + 1 + 1); + emit_byte(0x4A); emit_byte(0x46); emit_byte(0x49); emit_byte(0x46); /* Identifier: ASCII "JFIF" */ + emit_byte(0); + emit_byte(1); /* Major version */ + emit_byte(1); /* Minor version */ + emit_byte(0); /* Density unit */ + emit_word(1); + emit_word(1); + emit_byte(0); /* No thumbnail image */ + emit_byte(0); + } + + // Emit quantization tables + void jpeg_encoder::emit_dqt() + { + for (int i = 0; i < ((m_num_components == 3) ? 2 : 1); i++) + { + emit_marker(M_DQT); + emit_word(64 + 1 + 2); + emit_byte(static_cast(i)); + for (int j = 0; j < 64; j++) + emit_byte(static_cast(m_quantization_tables[i][j])); + } + } + + // Emit start of frame marker + void jpeg_encoder::emit_sof() + { + emit_marker(M_SOF0); /* baseline */ + emit_word(3 * m_num_components + 2 + 5 + 1); + emit_byte(8); /* precision */ + emit_word(m_image_y); + emit_word(m_image_x); + emit_byte(m_num_components); + for (int i = 0; i < m_num_components; i++) + { + emit_byte(static_cast(i + 1)); /* component ID */ + emit_byte((m_comp_h_samp[i] << 4) + m_comp_v_samp[i]); /* h and v sampling */ + emit_byte(i > 0); /* quant. table num */ + } + } + + // Emit Huffman table. + void jpeg_encoder::emit_dht(uint8 *bits, uint8 *val, int index, bool ac_flag) + { + emit_marker(M_DHT); + + int length = 0; + for (int i = 1; i <= 16; i++) + length += bits[i]; + + emit_word(length + 2 + 1 + 16); + emit_byte(static_cast(index + (ac_flag << 4))); + + for (int i = 1; i <= 16; i++) + emit_byte(bits[i]); + + for (int i = 0; i < length; i++) + emit_byte(val[i]); + } + + // Emit all Huffman tables. + void jpeg_encoder::emit_dhts() + { + emit_dht(m_huff_bits[0+0], m_huff_val[0+0], 0, false); + emit_dht(m_huff_bits[2+0], m_huff_val[2+0], 0, true); + if (m_num_components == 3) { + emit_dht(m_huff_bits[0+1], m_huff_val[0+1], 1, false); + emit_dht(m_huff_bits[2+1], m_huff_val[2+1], 1, true); + } + } + + // emit start of scan + void jpeg_encoder::emit_sos() + { + emit_marker(M_SOS); + emit_word(2 * m_num_components + 2 + 1 + 3); + emit_byte(m_num_components); + for (int i = 0; i < m_num_components; i++) + { + emit_byte(static_cast(i + 1)); + if (i == 0) + emit_byte((0 << 4) + 0); + else + emit_byte((1 << 4) + 1); + } + emit_byte(0); /* spectral selection */ + emit_byte(63); + emit_byte(0); + } + + void jpeg_encoder::load_block_8_8_grey(int x) + { + uint8 *pSrc; + sample_array_t *pDst = m_sample_array; + x <<= 3; + for (int i = 0; i < 8; i++, pDst += 8) + { + pSrc = m_mcu_lines[i] + x; + pDst[0] = pSrc[0] - 128; pDst[1] = pSrc[1] - 128; pDst[2] = pSrc[2] - 128; pDst[3] = pSrc[3] - 128; + pDst[4] = pSrc[4] - 128; pDst[5] = pSrc[5] - 128; pDst[6] = pSrc[6] - 128; pDst[7] = pSrc[7] - 128; + } + } + + void jpeg_encoder::load_block_8_8(int x, int y, int c) + { + uint8 *pSrc; + sample_array_t *pDst = m_sample_array; + x = (x * (8 * 3)) + c; + y <<= 3; + for (int i = 0; i < 8; i++, pDst += 8) + { + pSrc = m_mcu_lines[y + i] + x; + pDst[0] = pSrc[0 * 3] - 128; pDst[1] = pSrc[1 * 3] - 128; pDst[2] = pSrc[2 * 3] - 128; pDst[3] = pSrc[3 * 3] - 128; + pDst[4] = pSrc[4 * 3] - 128; pDst[5] = pSrc[5 * 3] - 128; pDst[6] = pSrc[6 * 3] - 128; pDst[7] = pSrc[7 * 3] - 128; + } + } + + void jpeg_encoder::load_block_16_8(int x, int c) + { + uint8 *pSrc1, *pSrc2; + sample_array_t *pDst = m_sample_array; + x = (x * (16 * 3)) + c; + int a = 0, b = 2; + for (int i = 0; i < 16; i += 2, pDst += 8) + { + pSrc1 = m_mcu_lines[i + 0] + x; + pSrc2 = m_mcu_lines[i + 1] + x; + pDst[0] = ((pSrc1[ 0 * 3] + pSrc1[ 1 * 3] + pSrc2[ 0 * 3] + pSrc2[ 1 * 3] + a) >> 2) - 128; pDst[1] = ((pSrc1[ 2 * 3] + pSrc1[ 3 * 3] + pSrc2[ 2 * 3] + pSrc2[ 3 * 3] + b) >> 2) - 128; + pDst[2] = ((pSrc1[ 4 * 3] + pSrc1[ 5 * 3] + pSrc2[ 4 * 3] + pSrc2[ 5 * 3] + a) >> 2) - 128; pDst[3] = ((pSrc1[ 6 * 3] + pSrc1[ 7 * 3] + pSrc2[ 6 * 3] + pSrc2[ 7 * 3] + b) >> 2) - 128; + pDst[4] = ((pSrc1[ 8 * 3] + pSrc1[ 9 * 3] + pSrc2[ 8 * 3] + pSrc2[ 9 * 3] + a) >> 2) - 128; pDst[5] = ((pSrc1[10 * 3] + pSrc1[11 * 3] + pSrc2[10 * 3] + pSrc2[11 * 3] + b) >> 2) - 128; + pDst[6] = ((pSrc1[12 * 3] + pSrc1[13 * 3] + pSrc2[12 * 3] + pSrc2[13 * 3] + a) >> 2) - 128; pDst[7] = ((pSrc1[14 * 3] + pSrc1[15 * 3] + pSrc2[14 * 3] + pSrc2[15 * 3] + b) >> 2) - 128; + int temp = a; a = b; b = temp; + } + } + + void jpeg_encoder::load_block_16_8_8(int x, int c) + { + uint8 *pSrc1; + sample_array_t *pDst = m_sample_array; + x = (x * (16 * 3)) + c; + for (int i = 0; i < 8; i++, pDst += 8) + { + pSrc1 = m_mcu_lines[i + 0] + x; + pDst[0] = ((pSrc1[ 0 * 3] + pSrc1[ 1 * 3]) >> 1) - 128; pDst[1] = ((pSrc1[ 2 * 3] + pSrc1[ 3 * 3]) >> 1) - 128; + pDst[2] = ((pSrc1[ 4 * 3] + pSrc1[ 5 * 3]) >> 1) - 128; pDst[3] = ((pSrc1[ 6 * 3] + pSrc1[ 7 * 3]) >> 1) - 128; + pDst[4] = ((pSrc1[ 8 * 3] + pSrc1[ 9 * 3]) >> 1) - 128; pDst[5] = ((pSrc1[10 * 3] + pSrc1[11 * 3]) >> 1) - 128; + pDst[6] = ((pSrc1[12 * 3] + pSrc1[13 * 3]) >> 1) - 128; pDst[7] = ((pSrc1[14 * 3] + pSrc1[15 * 3]) >> 1) - 128; + } + } + + void jpeg_encoder::load_quantized_coefficients(int component_num) + { + int32 *q = m_quantization_tables[component_num > 0]; + int16 *pDst = m_coefficient_array; + for (int i = 0; i < 64; i++) + { + sample_array_t j = m_sample_array[s_zag[i]]; + if (j < 0) + { + if ((j = -j + (*q >> 1)) < *q) + *pDst++ = 0; + else + *pDst++ = static_cast(-(j / *q)); + } + else + { + if ((j = j + (*q >> 1)) < *q) + *pDst++ = 0; + else + *pDst++ = static_cast((j / *q)); + } + q++; + } + } + + void jpeg_encoder::code_coefficients_pass_two(int component_num) + { + int i, j, run_len, nbits, temp1, temp2; + int16 *pSrc = m_coefficient_array; + uint *codes[2]; + uint8 *code_sizes[2]; + + if (component_num == 0) + { + codes[0] = m_huff_codes[0 + 0]; codes[1] = m_huff_codes[2 + 0]; + code_sizes[0] = m_huff_code_sizes[0 + 0]; code_sizes[1] = m_huff_code_sizes[2 + 0]; + } + else + { + codes[0] = m_huff_codes[0 + 1]; codes[1] = m_huff_codes[2 + 1]; + code_sizes[0] = m_huff_code_sizes[0 + 1]; code_sizes[1] = m_huff_code_sizes[2 + 1]; + } + + temp1 = temp2 = pSrc[0] - m_last_dc_val[component_num]; + m_last_dc_val[component_num] = pSrc[0]; + + if (temp1 < 0) + { + temp1 = -temp1; temp2--; + } + + nbits = 0; + while (temp1) + { + nbits++; temp1 >>= 1; + } + + put_bits(codes[0][nbits], code_sizes[0][nbits]); + if (nbits) put_bits(temp2 & ((1 << nbits) - 1), nbits); + + for (run_len = 0, i = 1; i < 64; i++) + { + if ((temp1 = m_coefficient_array[i]) == 0) + run_len++; + else + { + while (run_len >= 16) + { + put_bits(codes[1][0xF0], code_sizes[1][0xF0]); + run_len -= 16; + } + if ((temp2 = temp1) < 0) + { + temp1 = -temp1; + temp2--; + } + nbits = 1; + while (temp1 >>= 1) + nbits++; + j = (run_len << 4) + nbits; + put_bits(codes[1][j], code_sizes[1][j]); + put_bits(temp2 & ((1 << nbits) - 1), nbits); + run_len = 0; + } + } + if (run_len) + put_bits(codes[1][0], code_sizes[1][0]); + } + + void jpeg_encoder::code_block(int component_num) + { + DCT2D(m_sample_array); + load_quantized_coefficients(component_num); + code_coefficients_pass_two(component_num); + } + + void jpeg_encoder::process_mcu_row() + { + if (m_num_components == 1) + { + for (int i = 0; i < m_mcus_per_row; i++) + { + load_block_8_8_grey(i); code_block(0); + } + } + else if ((m_comp_h_samp[0] == 1) && (m_comp_v_samp[0] == 1)) + { + for (int i = 0; i < m_mcus_per_row; i++) + { + load_block_8_8(i, 0, 0); code_block(0); load_block_8_8(i, 0, 1); code_block(1); load_block_8_8(i, 0, 2); code_block(2); + } + } + else if ((m_comp_h_samp[0] == 2) && (m_comp_v_samp[0] == 1)) + { + for (int i = 0; i < m_mcus_per_row; i++) + { + load_block_8_8(i * 2 + 0, 0, 0); code_block(0); load_block_8_8(i * 2 + 1, 0, 0); code_block(0); + load_block_16_8_8(i, 1); code_block(1); load_block_16_8_8(i, 2); code_block(2); + } + } + else if ((m_comp_h_samp[0] == 2) && (m_comp_v_samp[0] == 2)) + { + for (int i = 0; i < m_mcus_per_row; i++) + { + load_block_8_8(i * 2 + 0, 0, 0); code_block(0); load_block_8_8(i * 2 + 1, 0, 0); code_block(0); + load_block_8_8(i * 2 + 0, 1, 0); code_block(0); load_block_8_8(i * 2 + 1, 1, 0); code_block(0); + load_block_16_8(i, 1); code_block(1); load_block_16_8(i, 2); code_block(2); + } + } + } + + void jpeg_encoder::load_mcu(const void *pSrc) + { + const uint8* Psrc = reinterpret_cast(pSrc); + + uint8* pDst = m_mcu_lines[m_mcu_y_ofs]; // OK to write up to m_image_bpl_xlt bytes to pDst + + if (m_num_components == 1) { + if (m_image_bpp == 3) + RGB_to_Y(pDst, Psrc, m_image_x); + else + memcpy(pDst, Psrc, m_image_x); + } else { + if (m_image_bpp == 3) + RGB_to_YCC(pDst, Psrc, m_image_x); + else + Y_to_YCC(pDst, Psrc, m_image_x); + } + + // Possibly duplicate pixels at end of scanline if not a multiple of 8 or 16 + if (m_num_components == 1) + memset(m_mcu_lines[m_mcu_y_ofs] + m_image_bpl_xlt, pDst[m_image_bpl_xlt - 1], m_image_x_mcu - m_image_x); + else + { + const uint8 y = pDst[m_image_bpl_xlt - 3 + 0], cb = pDst[m_image_bpl_xlt - 3 + 1], cr = pDst[m_image_bpl_xlt - 3 + 2]; + uint8 *q = m_mcu_lines[m_mcu_y_ofs] + m_image_bpl_xlt; + for (int i = m_image_x; i < m_image_x_mcu; i++) + { + *q++ = y; *q++ = cb; *q++ = cr; + } + } + + if (++m_mcu_y_ofs == m_mcu_y) + { + process_mcu_row(); + m_mcu_y_ofs = 0; + } + } + + // Quantization table generation. + void jpeg_encoder::compute_quant_table(int32 *pDst, const int16 *pSrc) + { + int32 q; + if (m_params.m_quality < 50) + q = 5000 / m_params.m_quality; + else + q = 200 - m_params.m_quality * 2; + for (int i = 0; i < 64; i++) + { + int32 j = *pSrc++; j = (j * q + 50L) / 100L; + *pDst++ = JPGE_MIN(JPGE_MAX(j, 1), 255); + } + } + + // Higher-level methods. + bool jpeg_encoder::jpg_open(int p_x_res, int p_y_res, int src_channels) + { + m_num_components = 3; + switch (m_params.m_subsampling) + { + case Y_ONLY: + { + m_num_components = 1; + m_comp_h_samp[0] = 1; m_comp_v_samp[0] = 1; + m_mcu_x = 8; m_mcu_y = 8; + break; + } + case H1V1: + { + m_comp_h_samp[0] = 1; m_comp_v_samp[0] = 1; + m_comp_h_samp[1] = 1; m_comp_v_samp[1] = 1; + m_comp_h_samp[2] = 1; m_comp_v_samp[2] = 1; + m_mcu_x = 8; m_mcu_y = 8; + break; + } + case H2V1: + { + m_comp_h_samp[0] = 2; m_comp_v_samp[0] = 1; + m_comp_h_samp[1] = 1; m_comp_v_samp[1] = 1; + m_comp_h_samp[2] = 1; m_comp_v_samp[2] = 1; + m_mcu_x = 16; m_mcu_y = 8; + break; + } + case H2V2: + { + m_comp_h_samp[0] = 2; m_comp_v_samp[0] = 2; + m_comp_h_samp[1] = 1; m_comp_v_samp[1] = 1; + m_comp_h_samp[2] = 1; m_comp_v_samp[2] = 1; + m_mcu_x = 16; m_mcu_y = 16; + } + } + + m_image_x = p_x_res; m_image_y = p_y_res; + m_image_bpp = src_channels; + m_image_bpl = m_image_x * src_channels; + m_image_x_mcu = (m_image_x + m_mcu_x - 1) & (~(m_mcu_x - 1)); + m_image_y_mcu = (m_image_y + m_mcu_y - 1) & (~(m_mcu_y - 1)); + m_image_bpl_xlt = m_image_x * m_num_components; + m_image_bpl_mcu = m_image_x_mcu * m_num_components; + m_mcus_per_row = m_image_x_mcu / m_mcu_x; + + if ((m_mcu_lines[0] = static_cast(jpge_malloc(m_image_bpl_mcu * m_mcu_y))) == NULL) { + return false; + } + for (int i = 1; i < m_mcu_y; i++) + m_mcu_lines[i] = m_mcu_lines[i-1] + m_image_bpl_mcu; + + if(m_last_quality != m_params.m_quality){ + m_last_quality = m_params.m_quality; + compute_quant_table(m_quantization_tables[0], s_std_lum_quant); + compute_quant_table(m_quantization_tables[1], s_std_croma_quant); + } + + if(!m_huff_initialized){ + m_huff_initialized = true; + + memcpy(m_huff_bits[0+0], s_dc_lum_bits, 17); memcpy(m_huff_val[0+0], s_dc_lum_val, DC_LUM_CODES); + memcpy(m_huff_bits[2+0], s_ac_lum_bits, 17); memcpy(m_huff_val[2+0], s_ac_lum_val, AC_LUM_CODES); + memcpy(m_huff_bits[0+1], s_dc_chroma_bits, 17); memcpy(m_huff_val[0+1], s_dc_chroma_val, DC_CHROMA_CODES); + memcpy(m_huff_bits[2+1], s_ac_chroma_bits, 17); memcpy(m_huff_val[2+1], s_ac_chroma_val, AC_CHROMA_CODES); + + compute_huffman_table(&m_huff_codes[0+0][0], &m_huff_code_sizes[0+0][0], m_huff_bits[0+0], m_huff_val[0+0]); + compute_huffman_table(&m_huff_codes[2+0][0], &m_huff_code_sizes[2+0][0], m_huff_bits[2+0], m_huff_val[2+0]); + compute_huffman_table(&m_huff_codes[0+1][0], &m_huff_code_sizes[0+1][0], m_huff_bits[0+1], m_huff_val[0+1]); + compute_huffman_table(&m_huff_codes[2+1][0], &m_huff_code_sizes[2+1][0], m_huff_bits[2+1], m_huff_val[2+1]); + } + + m_out_buf_left = JPGE_OUT_BUF_SIZE; + m_pOut_buf = m_out_buf; + m_bit_buffer = 0; + m_bits_in = 0; + m_mcu_y_ofs = 0; + m_pass_num = 2; + memset(m_last_dc_val, 0, 3 * sizeof(m_last_dc_val[0])); + + // Emit all markers at beginning of image file. + emit_marker(M_SOI); + emit_jfif_app0(); + emit_dqt(); + emit_sof(); + emit_dhts(); + emit_sos(); + + return m_all_stream_writes_succeeded; + } + + bool jpeg_encoder::process_end_of_image() + { + if (m_mcu_y_ofs) { + if (m_mcu_y_ofs < 16) { // check here just to shut up static analysis + for (int i = m_mcu_y_ofs; i < m_mcu_y; i++) { + memcpy(m_mcu_lines[i], m_mcu_lines[m_mcu_y_ofs - 1], m_image_bpl_mcu); + } + } + process_mcu_row(); + } + + put_bits(0x7F, 7); + emit_marker(M_EOI); + flush_output_buffer(); + m_all_stream_writes_succeeded = m_all_stream_writes_succeeded && m_pStream->put_buf(NULL, 0); + m_pass_num++; // purposely bump up m_pass_num, for debugging + return true; + } + + void jpeg_encoder::clear() + { + m_mcu_lines[0] = NULL; + m_pass_num = 0; + m_all_stream_writes_succeeded = true; + } + + jpeg_encoder::jpeg_encoder() + { + clear(); + } + + jpeg_encoder::~jpeg_encoder() + { + deinit(); + } + + bool jpeg_encoder::init(output_stream *pStream, int width, int height, int src_channels, const params &comp_params) + { + deinit(); + if (((!pStream) || (width < 1) || (height < 1)) || ((src_channels != 1) && (src_channels != 3) && (src_channels != 4)) || (!comp_params.check())) return false; + m_pStream = pStream; + m_params = comp_params; + return jpg_open(width, height, src_channels); + } + + void jpeg_encoder::deinit() + { + jpge_free(m_mcu_lines[0]); + clear(); + } + + bool jpeg_encoder::process_scanline(const void* pScanline) + { + if ((m_pass_num < 1) || (m_pass_num > 2)) { + return false; + } + if (m_all_stream_writes_succeeded) { + if (!pScanline) { + if (!process_end_of_image()) { + return false; + } + } else { + load_mcu(pScanline); + } + } + return m_all_stream_writes_succeeded; + } + +} // namespace jpge diff --git a/esp32-cam/jpge.h b/esp32-cam/jpge.h new file mode 100644 index 0000000..aa295c8 --- /dev/null +++ b/esp32-cam/jpge.h @@ -0,0 +1,142 @@ +// jpge.h - C++ class for JPEG compression. +// Public domain, Rich Geldreich +// Alex Evans: Added RGBA support, linear memory allocator. +#ifndef JPEG_ENCODER_H +#define JPEG_ENCODER_H + +namespace jpge +{ + typedef unsigned char uint8; + typedef signed short int16; + typedef signed int int32; + typedef unsigned short uint16; + typedef unsigned int uint32; + typedef unsigned int uint; + + // JPEG chroma subsampling factors. Y_ONLY (grayscale images) and H2V2 (color images) are the most common. + enum subsampling_t { Y_ONLY = 0, H1V1 = 1, H2V1 = 2, H2V2 = 3 }; + + // JPEG compression parameters structure. + struct params { + inline params() : m_quality(85), m_subsampling(H2V2) { } + + inline bool check() const { + if ((m_quality < 1) || (m_quality > 100)) { + return false; + } + if ((uint)m_subsampling > (uint)H2V2) { + return false; + } + return true; + } + + // Quality: 1-100, higher is better. Typical values are around 50-95. + int m_quality; + + // m_subsampling: + // 0 = Y (grayscale) only + // 1 = H1V1 subsampling (YCbCr 1x1x1, 3 blocks per MCU) + // 2 = H2V1 subsampling (YCbCr 2x1x1, 4 blocks per MCU) + // 3 = H2V2 subsampling (YCbCr 4x1x1, 6 blocks per MCU-- very common) + subsampling_t m_subsampling; + }; + + // Output stream abstract class - used by the jpeg_encoder class to write to the output stream. + // put_buf() is generally called with len==JPGE_OUT_BUF_SIZE bytes, but for headers it'll be called with smaller amounts. + class output_stream { + public: + virtual ~output_stream() { }; + virtual bool put_buf(const void* Pbuf, int len) = 0; + virtual uint get_size() const = 0; + }; + + // Lower level jpeg_encoder class - useful if more control is needed than the above helper functions. + class jpeg_encoder { + public: + jpeg_encoder(); + ~jpeg_encoder(); + + // Initializes the compressor. + // pStream: The stream object to use for writing compressed data. + // params - Compression parameters structure, defined above. + // width, height - Image dimensions. + // channels - May be 1, or 3. 1 indicates grayscale, 3 indicates RGB source data. + // Returns false on out of memory or if a stream write fails. + bool init(output_stream *pStream, int width, int height, int src_channels, const params &comp_params = params()); + + // Call this method with each source scanline. + // width * src_channels bytes per scanline is expected (RGB or Y format). + // You must call with NULL after all scanlines are processed to finish compression. + // Returns false on out of memory or if a stream write fails. + bool process_scanline(const void* pScanline); + + // Deinitializes the compressor, freeing any allocated memory. May be called at any time. + void deinit(); + + private: + jpeg_encoder(const jpeg_encoder &); + jpeg_encoder &operator =(const jpeg_encoder &); + + typedef int32 sample_array_t; + enum { JPGE_OUT_BUF_SIZE = 512 }; + + output_stream *m_pStream; + params m_params; + uint8 m_num_components; + uint8 m_comp_h_samp[3], m_comp_v_samp[3]; + int m_image_x, m_image_y, m_image_bpp, m_image_bpl; + int m_image_x_mcu, m_image_y_mcu; + int m_image_bpl_xlt, m_image_bpl_mcu; + int m_mcus_per_row; + int m_mcu_x, m_mcu_y; + uint8 *m_mcu_lines[16]; + uint8 m_mcu_y_ofs; + sample_array_t m_sample_array[64]; + int16 m_coefficient_array[64]; + + int m_last_dc_val[3]; + uint8 m_out_buf[JPGE_OUT_BUF_SIZE]; + uint8 *m_pOut_buf; + uint m_out_buf_left; + uint32 m_bit_buffer; + uint m_bits_in; + uint8 m_pass_num; + bool m_all_stream_writes_succeeded; + + bool jpg_open(int p_x_res, int p_y_res, int src_channels); + + void flush_output_buffer(); + void put_bits(uint bits, uint len); + + void emit_byte(uint8 i); + void emit_word(uint i); + void emit_marker(int marker); + + void emit_jfif_app0(); + void emit_dqt(); + void emit_sof(); + void emit_dht(uint8 *bits, uint8 *val, int index, bool ac_flag); + void emit_dhts(); + void emit_sos(); + + void compute_quant_table(int32 *dst, const int16 *src); + void load_quantized_coefficients(int component_num); + + void load_block_8_8_grey(int x); + void load_block_8_8(int x, int y, int c); + void load_block_16_8(int x, int c); + void load_block_16_8_8(int x, int c); + + void code_coefficients_pass_two(int component_num); + void code_block(int component_num); + + void process_mcu_row(); + bool process_end_of_image(); + void load_mcu(const void* src); + void clear(); + void init(); + }; + +} // namespace jpge + +#endif // JPEG_ENCODER diff --git a/esp32-cam/ov2640.c b/esp32-cam/ov2640.c new file mode 100644 index 0000000..811023c --- /dev/null +++ b/esp32-cam/ov2640.c @@ -0,0 +1,580 @@ +/* + * 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. + * + * OV2640 driver. + * + */ +#include +#include +#include +#include "sccb.h" +#include "ov2640.h" +#include "ov2640_regs.h" +#include "ov2640_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 = "ov2640"; +#endif + +static volatile ov2640_bank_t reg_bank = BANK_MAX; +static int set_bank(sensor_t *sensor, ov2640_bank_t bank) +{ + int res = 0; + if (bank != reg_bank) { + reg_bank = bank; + res = SCCB_Write(sensor->slv_addr, BANK_SEL, bank); + } + return res; +} + +static int write_regs(sensor_t *sensor, const uint8_t (*regs)[2]) +{ + int i=0, res = 0; + while (regs[i][0]) { + if (regs[i][0] == BANK_SEL) { + res = set_bank(sensor, regs[i][1]); + } else { + res = SCCB_Write(sensor->slv_addr, regs[i][0], regs[i][1]); + } + if (res) { + return res; + } + i++; + } + return res; +} + +static int write_reg(sensor_t *sensor, ov2640_bank_t bank, uint8_t reg, uint8_t value) +{ + int ret = set_bank(sensor, bank); + if(!ret) { + ret = SCCB_Write(sensor->slv_addr, reg, value); + } + return ret; +} + +static int set_reg_bits(sensor_t *sensor, uint8_t bank, uint8_t reg, uint8_t offset, uint8_t mask, uint8_t value) +{ + int ret = 0; + uint8_t c_value, new_value; + + ret = set_bank(sensor, bank); + if(ret) { + return ret; + } + c_value = SCCB_Read(sensor->slv_addr, reg); + new_value = (c_value & ~(mask << offset)) | ((value & mask) << offset); + ret = SCCB_Write(sensor->slv_addr, reg, new_value); + return ret; +} + +static int read_reg(sensor_t *sensor, ov2640_bank_t bank, uint8_t reg) +{ + if(set_bank(sensor, bank)){ + return 0; + } + return SCCB_Read(sensor->slv_addr, reg); +} + +static uint8_t get_reg_bits(sensor_t *sensor, uint8_t bank, uint8_t reg, uint8_t offset, uint8_t mask) +{ + return (read_reg(sensor, bank, reg) >> offset) & mask; +} + +static int write_reg_bits(sensor_t *sensor, uint8_t bank, uint8_t reg, uint8_t mask, int enable) +{ + return set_reg_bits(sensor, bank, reg, 0, mask, enable?mask:0); +} + +#define WRITE_REGS_OR_RETURN(regs) ret = write_regs(sensor, regs); if(ret){return ret;} +#define WRITE_REG_OR_RETURN(bank, reg, val) ret = write_reg(sensor, bank, reg, val); if(ret){return ret;} +#define SET_REG_BITS_OR_RETURN(bank, reg, offset, mask, val) ret = set_reg_bits(sensor, bank, reg, offset, mask, val); if(ret){return ret;} + +static int reset(sensor_t *sensor) +{ + int ret = 0; + WRITE_REG_OR_RETURN(BANK_SENSOR, COM7, COM7_SRST); + vTaskDelay(10 / portTICK_PERIOD_MS); + WRITE_REGS_OR_RETURN(ov2640_settings_cif); + return ret; +} + +static int set_pixformat(sensor_t *sensor, pixformat_t pixformat) +{ + int ret = 0; + sensor->pixformat = pixformat; + switch (pixformat) { + case PIXFORMAT_RGB565: + case PIXFORMAT_RGB888: + WRITE_REGS_OR_RETURN(ov2640_settings_rgb565); + break; + case PIXFORMAT_YUV422: + case PIXFORMAT_GRAYSCALE: + WRITE_REGS_OR_RETURN(ov2640_settings_yuv422); + break; + case PIXFORMAT_JPEG: + WRITE_REGS_OR_RETURN(ov2640_settings_jpeg3); + break; + default: + ret = -1; + break; + } + if(!ret) { + vTaskDelay(10 / portTICK_PERIOD_MS); + } + + return ret; +} + +static int set_window(sensor_t *sensor, ov2640_sensor_mode_t mode, int offset_x, int offset_y, int max_x, int max_y, int w, int h){ + int ret = 0; + const uint8_t (*regs)[2]; + ov2640_clk_t c; + c.reserved = 0; + + max_x /= 4; + max_y /= 4; + w /= 4; + h /= 4; + uint8_t win_regs[][2] = { + {BANK_SEL, BANK_DSP}, + {HSIZE, max_x & 0xFF}, + {VSIZE, max_y & 0xFF}, + {XOFFL, offset_x & 0xFF}, + {YOFFL, offset_y & 0xFF}, + {VHYX, ((max_y >> 1) & 0X80) | ((offset_y >> 4) & 0X70) | ((max_x >> 5) & 0X08) | ((offset_y >> 8) & 0X07)}, + {TEST, (max_x >> 2) & 0X80}, + {ZMOW, (w)&0xFF}, + {ZMOH, (h)&0xFF}, + {ZMHH, ((h>>6)&0x04)|((w>>8)&0x03)}, + {0, 0} + }; + + c.pclk_auto = 0; + c.pclk_div = 8; + c.clk_2x = 0; + c.clk_div = 0; + + if(sensor->pixformat != PIXFORMAT_JPEG){ + c.pclk_auto = 1; + c.clk_div = 7; + } + + if (mode == OV2640_MODE_CIF) { + regs = ov2640_settings_to_cif; + if(sensor->pixformat != PIXFORMAT_JPEG){ + c.clk_div = 3; + } + } else if (mode == OV2640_MODE_SVGA) { + regs = ov2640_settings_to_svga; + } else { + regs = ov2640_settings_to_uxga; + c.pclk_div = 12; + } + + WRITE_REG_OR_RETURN(BANK_DSP, R_BYPASS, R_BYPASS_DSP_BYPAS); + WRITE_REGS_OR_RETURN(regs); + WRITE_REGS_OR_RETURN(win_regs); + WRITE_REG_OR_RETURN(BANK_SENSOR, CLKRC, c.clk); + WRITE_REG_OR_RETURN(BANK_DSP, R_DVP_SP, c.pclk); + WRITE_REG_OR_RETURN(BANK_DSP, R_BYPASS, R_BYPASS_DSP_EN); + + vTaskDelay(10 / portTICK_PERIOD_MS); + //required when changing resolution + set_pixformat(sensor, sensor->pixformat); + + return ret; +} + +static int set_framesize(sensor_t *sensor, framesize_t framesize) +{ + int ret = 0; + uint16_t w = resolution[framesize].width; + uint16_t h = resolution[framesize].height; + aspect_ratio_t ratio = resolution[framesize].aspect_ratio; + uint16_t max_x = ratio_table[ratio].max_x; + uint16_t max_y = ratio_table[ratio].max_y; + uint16_t offset_x = ratio_table[ratio].offset_x; + uint16_t offset_y = ratio_table[ratio].offset_y; + ov2640_sensor_mode_t mode = OV2640_MODE_UXGA; + + sensor->status.framesize = framesize; + + + + if (framesize <= FRAMESIZE_CIF) { + mode = OV2640_MODE_CIF; + max_x /= 4; + max_y /= 4; + offset_x /= 4; + offset_y /= 4; + if(max_y > 296){ + max_y = 296; + } + } else if (framesize <= FRAMESIZE_SVGA) { + mode = OV2640_MODE_SVGA; + max_x /= 2; + max_y /= 2; + offset_x /= 2; + offset_y /= 2; + } + + ret = set_window(sensor, mode, offset_x, offset_y, max_x, max_y, w, h); + return ret; +} + +static int set_contrast(sensor_t *sensor, int level) +{ + int ret=0; + level += 3; + if (level <= 0 || level > NUM_CONTRAST_LEVELS) { + return -1; + } + sensor->status.contrast = level-3; + for (int i=0; i<7; i++) { + WRITE_REG_OR_RETURN(BANK_DSP, contrast_regs[0][i], contrast_regs[level][i]); + } + return ret; +} + +static int set_brightness(sensor_t *sensor, int level) +{ + int ret=0; + level += 3; + if (level <= 0 || level > NUM_BRIGHTNESS_LEVELS) { + return -1; + } + sensor->status.brightness = level-3; + for (int i=0; i<5; i++) { + WRITE_REG_OR_RETURN(BANK_DSP, brightness_regs[0][i], brightness_regs[level][i]); + } + return ret; +} + +static int set_saturation(sensor_t *sensor, int level) +{ + int ret=0; + level += 3; + if (level <= 0 || level > NUM_SATURATION_LEVELS) { + return -1; + } + sensor->status.saturation = level-3; + for (int i=0; i<5; i++) { + WRITE_REG_OR_RETURN(BANK_DSP, saturation_regs[0][i], saturation_regs[level][i]); + } + return ret; +} + +static int set_special_effect(sensor_t *sensor, int effect) +{ + int ret=0; + effect++; + if (effect <= 0 || effect > NUM_SPECIAL_EFFECTS) { + return -1; + } + sensor->status.special_effect = effect-1; + for (int i=0; i<5; i++) { + WRITE_REG_OR_RETURN(BANK_DSP, special_effects_regs[0][i], special_effects_regs[effect][i]); + } + return ret; +} + +static int set_wb_mode(sensor_t *sensor, int mode) +{ + int ret=0; + if (mode < 0 || mode > NUM_WB_MODES) { + return -1; + } + sensor->status.wb_mode = mode; + SET_REG_BITS_OR_RETURN(BANK_DSP, 0XC7, 6, 1, mode?1:0); + if(mode) { + for (int i=0; i<3; i++) { + WRITE_REG_OR_RETURN(BANK_DSP, wb_modes_regs[0][i], wb_modes_regs[mode][i]); + } + } + return ret; +} + +static int set_ae_level(sensor_t *sensor, int level) +{ + int ret=0; + level += 3; + if (level <= 0 || level > NUM_AE_LEVELS) { + return -1; + } + sensor->status.ae_level = level-3; + for (int i=0; i<3; i++) { + WRITE_REG_OR_RETURN(BANK_SENSOR, ae_levels_regs[0][i], ae_levels_regs[level][i]); + } + return ret; +} + +static int set_quality(sensor_t *sensor, int quality) +{ + if(quality < 0) { + quality = 0; + } else if(quality > 63) { + quality = 63; + } + sensor->status.quality = quality; + return write_reg(sensor, BANK_DSP, QS, quality); +} + +static int set_agc_gain(sensor_t *sensor, int gain) +{ + if(gain < 0) { + gain = 0; + } else if(gain > 30) { + gain = 30; + } + sensor->status.agc_gain = gain; + return write_reg(sensor, BANK_SENSOR, GAIN, agc_gain_tbl[gain]); +} + +static int set_gainceiling_sensor(sensor_t *sensor, gainceiling_t gainceiling) +{ + sensor->status.gainceiling = gainceiling; + //return write_reg(sensor, BANK_SENSOR, COM9, COM9_AGC_SET(gainceiling)); + return set_reg_bits(sensor, BANK_SENSOR, COM9, 5, 7, gainceiling); +} + +static int set_aec_value(sensor_t *sensor, int value) +{ + if(value < 0) { + value = 0; + } else if(value > 1200) { + value = 1200; + } + sensor->status.aec_value = value; + return set_reg_bits(sensor, BANK_SENSOR, REG04, 0, 3, value & 0x3) + || write_reg(sensor, BANK_SENSOR, AEC, (value >> 2) & 0xFF) + || set_reg_bits(sensor, BANK_SENSOR, REG45, 0, 0x3F, value >> 10); +} + +static int set_aec2(sensor_t *sensor, int enable) +{ + sensor->status.aec2 = enable; + return set_reg_bits(sensor, BANK_DSP, CTRL0, 6, 1, enable?0:1); +} + +static int set_colorbar(sensor_t *sensor, int enable) +{ + sensor->status.colorbar = enable; + return write_reg_bits(sensor, BANK_SENSOR, COM7, COM7_COLOR_BAR, enable?1:0); +} + +static int set_agc_sensor(sensor_t *sensor, int enable) +{ + sensor->status.agc = enable; + return write_reg_bits(sensor, BANK_SENSOR, COM8, COM8_AGC_EN, enable?1:0); +} + +static int set_aec_sensor(sensor_t *sensor, int enable) +{ + sensor->status.aec = enable; + return write_reg_bits(sensor, BANK_SENSOR, COM8, COM8_AEC_EN, enable?1:0); +} + +static int set_hmirror_sensor(sensor_t *sensor, int enable) +{ + sensor->status.hmirror = enable; + return write_reg_bits(sensor, BANK_SENSOR, REG04, REG04_HFLIP_IMG, enable?1:0); +} + +static int set_vflip_sensor(sensor_t *sensor, int enable) +{ + int ret = 0; + sensor->status.vflip = enable; + ret = write_reg_bits(sensor, BANK_SENSOR, REG04, REG04_VREF_EN, enable?1:0); + return ret & write_reg_bits(sensor, BANK_SENSOR, REG04, REG04_VFLIP_IMG, enable?1:0); +} + +static int set_raw_gma_dsp(sensor_t *sensor, int enable) +{ + sensor->status.raw_gma = enable; + return set_reg_bits(sensor, BANK_DSP, CTRL1, 5, 1, enable?1:0); +} + +static int set_awb_dsp(sensor_t *sensor, int enable) +{ + sensor->status.awb = enable; + return set_reg_bits(sensor, BANK_DSP, CTRL1, 3, 1, enable?1:0); +} + +static int set_awb_gain_dsp(sensor_t *sensor, int enable) +{ + sensor->status.awb_gain = enable; + return set_reg_bits(sensor, BANK_DSP, CTRL1, 2, 1, enable?1:0); +} + +static int set_lenc_dsp(sensor_t *sensor, int enable) +{ + sensor->status.lenc = enable; + return set_reg_bits(sensor, BANK_DSP, CTRL1, 1, 1, enable?1:0); +} + +static int set_dcw_dsp(sensor_t *sensor, int enable) +{ + sensor->status.dcw = enable; + return set_reg_bits(sensor, BANK_DSP, CTRL2, 5, 1, enable?1:0); +} + +static int set_bpc_dsp(sensor_t *sensor, int enable) +{ + sensor->status.bpc = enable; + return set_reg_bits(sensor, BANK_DSP, CTRL3, 7, 1, enable?1:0); +} + +static int set_wpc_dsp(sensor_t *sensor, int enable) +{ + sensor->status.wpc = enable; + return set_reg_bits(sensor, BANK_DSP, CTRL3, 6, 1, enable?1:0); +} + +//unsupported +static int set_sharpness(sensor_t *sensor, int level) +{ + return -1; +} + +static int set_denoise(sensor_t *sensor, int level) +{ + return -1; +} + +static int get_reg(sensor_t *sensor, int reg, int mask) +{ + int ret = read_reg(sensor, (reg >> 8) & 0x01, 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 = read_reg(sensor, (reg >> 8) & 0x01, reg & 0xFF); + if(ret < 0){ + return ret; + } + value = (ret & ~mask) | (value & mask); + ret = write_reg(sensor, (reg >> 8) & 0x01, reg & 0xFF, 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) +{ + return set_window(sensor, (ov2640_sensor_mode_t)startX, offsetX, offsetY, totalX, totalY, outputX, outputY); +} + +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; +} + +static int init_status(sensor_t *sensor){ + sensor->status.brightness = 0; + sensor->status.contrast = 0; + sensor->status.saturation = 0; + sensor->status.ae_level = 0; + sensor->status.special_effect = 0; + sensor->status.wb_mode = 0; + + sensor->status.agc_gain = 30; + int agc_gain = read_reg(sensor, BANK_SENSOR, GAIN); + for (int i=0; i<30; i++){ + if(agc_gain >= agc_gain_tbl[i] && agc_gain < agc_gain_tbl[i+1]){ + sensor->status.agc_gain = i; + break; + } + } + + sensor->status.aec_value = ((uint16_t)get_reg_bits(sensor, BANK_SENSOR, REG45, 0, 0x3F) << 10) + | ((uint16_t)read_reg(sensor, BANK_SENSOR, AEC) << 2) + | get_reg_bits(sensor, BANK_SENSOR, REG04, 0, 3);//0 - 1200 + sensor->status.quality = read_reg(sensor, BANK_DSP, QS); + sensor->status.gainceiling = get_reg_bits(sensor, BANK_SENSOR, COM9, 5, 7); + + sensor->status.awb = get_reg_bits(sensor, BANK_DSP, CTRL1, 3, 1); + sensor->status.awb_gain = get_reg_bits(sensor, BANK_DSP, CTRL1, 2, 1); + sensor->status.aec = get_reg_bits(sensor, BANK_SENSOR, COM8, 0, 1); + sensor->status.aec2 = get_reg_bits(sensor, BANK_DSP, CTRL0, 6, 1); + sensor->status.agc = get_reg_bits(sensor, BANK_SENSOR, COM8, 2, 1); + sensor->status.bpc = get_reg_bits(sensor, BANK_DSP, CTRL3, 7, 1); + sensor->status.wpc = get_reg_bits(sensor, BANK_DSP, CTRL3, 6, 1); + sensor->status.raw_gma = get_reg_bits(sensor, BANK_DSP, CTRL1, 5, 1); + sensor->status.lenc = get_reg_bits(sensor, BANK_DSP, CTRL1, 1, 1); + sensor->status.hmirror = get_reg_bits(sensor, BANK_SENSOR, REG04, 7, 1); + sensor->status.vflip = get_reg_bits(sensor, BANK_SENSOR, REG04, 6, 1); + sensor->status.dcw = get_reg_bits(sensor, BANK_DSP, CTRL2, 5, 1); + sensor->status.colorbar = get_reg_bits(sensor, BANK_SENSOR, COM7, 1, 1); + + sensor->status.sharpness = 0;//not supported + sensor->status.denoise = 0; + return 0; +} + +int ov2640_init(sensor_t *sensor) +{ + sensor->reset = reset; + sensor->init_status = init_status; + 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_quality = set_quality; + sensor->set_colorbar = set_colorbar; + + sensor->set_gainceiling = set_gainceiling_sensor; + sensor->set_gain_ctrl = set_agc_sensor; + sensor->set_exposure_ctrl = set_aec_sensor; + sensor->set_hmirror = set_hmirror_sensor; + sensor->set_vflip = set_vflip_sensor; + + sensor->set_whitebal = set_awb_dsp; + 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; + + //not supported + sensor->set_sharpness = set_sharpness; + 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; + ESP_LOGD(TAG, "OV2640 Attached"); + return 0; +} diff --git a/esp32-cam/ov2640.h b/esp32-cam/ov2640.h new file mode 100644 index 0000000..a890499 --- /dev/null +++ b/esp32-cam/ov2640.h @@ -0,0 +1,13 @@ +/* + * 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. + * + * OV2640 driver. + * + */ +#ifndef __OV2640_H__ +#define __OV2640_H__ +#include "sensor.h" +int ov2640_init(sensor_t *sensor); +#endif // __OV2640_H__ diff --git a/esp32-cam/ov2640_regs.h b/esp32-cam/ov2640_regs.h new file mode 100644 index 0000000..eb096b4 --- /dev/null +++ b/esp32-cam/ov2640_regs.h @@ -0,0 +1,216 @@ +/* + * 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. + * + * OV2640 register definitions. + */ +#ifndef __REG_REGS_H__ +#define __REG_REGS_H__ +/* DSP register bank FF=0x00*/ +#define R_BYPASS 0x05 +#define QS 0x44 +#define CTRLI 0x50 +#define HSIZE 0x51 +#define VSIZE 0x52 +#define XOFFL 0x53 +#define YOFFL 0x54 +#define VHYX 0x55 +#define DPRP 0x56 +#define TEST 0x57 +#define ZMOW 0x5A +#define ZMOH 0x5B +#define ZMHH 0x5C +#define BPADDR 0x7C +#define BPDATA 0x7D +#define CTRL2 0x86 +#define CTRL3 0x87 +#define SIZEL 0x8C +#define HSIZE8 0xC0 +#define VSIZE8 0xC1 +#define CTRL0 0xC2 +#define CTRL1 0xC3 +#define R_DVP_SP 0xD3 +#define IMAGE_MODE 0xDA +#define RESET 0xE0 +#define MS_SP 0xF0 +#define SS_ID 0xF7 +#define SS_CTRL 0xF7 +#define MC_BIST 0xF9 +#define MC_AL 0xFA +#define MC_AH 0xFB +#define MC_D 0xFC +#define P_CMD 0xFD +#define P_STATUS 0xFE +#define BANK_SEL 0xFF + +#define CTRLI_LP_DP 0x80 +#define CTRLI_ROUND 0x40 + +#define CTRL0_AEC_EN 0x80 +#define CTRL0_AEC_SEL 0x40 +#define CTRL0_STAT_SEL 0x20 +#define CTRL0_VFIRST 0x10 +#define CTRL0_YUV422 0x08 +#define CTRL0_YUV_EN 0x04 +#define CTRL0_RGB_EN 0x02 +#define CTRL0_RAW_EN 0x01 + +#define CTRL2_DCW_EN 0x20 +#define CTRL2_SDE_EN 0x10 +#define CTRL2_UV_ADJ_EN 0x08 +#define CTRL2_UV_AVG_EN 0x04 +#define CTRL2_CMX_EN 0x01 + +#define CTRL3_BPC_EN 0x80 +#define CTRL3_WPC_EN 0x40 + +#define R_DVP_SP_AUTO_MODE 0x80 + +#define R_BYPASS_DSP_EN 0x00 +#define R_BYPASS_DSP_BYPAS 0x01 + +#define IMAGE_MODE_Y8_DVP_EN 0x40 +#define IMAGE_MODE_JPEG_EN 0x10 +#define IMAGE_MODE_YUV422 0x00 +#define IMAGE_MODE_RAW10 0x04 +#define IMAGE_MODE_RGB565 0x08 +#define IMAGE_MODE_HREF_VSYNC 0x02 +#define IMAGE_MODE_LBYTE_FIRST 0x01 + +#define RESET_MICROC 0x40 +#define RESET_SCCB 0x20 +#define RESET_JPEG 0x10 +#define RESET_DVP 0x04 +#define RESET_IPU 0x02 +#define RESET_CIF 0x01 + +#define MC_BIST_RESET 0x80 +#define MC_BIST_BOOT_ROM_SEL 0x40 +#define MC_BIST_12KB_SEL 0x20 +#define MC_BIST_12KB_MASK 0x30 +#define MC_BIST_512KB_SEL 0x08 +#define MC_BIST_512KB_MASK 0x0C +#define MC_BIST_BUSY_BIT_R 0x02 +#define MC_BIST_MC_RES_ONE_SH_W 0x02 +#define MC_BIST_LAUNCH 0x01 + + +typedef enum { + BANK_DSP, BANK_SENSOR, BANK_MAX +} ov2640_bank_t; + +/* Sensor register bank FF=0x01*/ +#define GAIN 0x00 +#define COM1 0x03 +#define REG04 0x04 +#define REG08 0x08 +#define COM2 0x09 +#define REG_PID 0x0A +#define REG_VER 0x0B +#define COM3 0x0C +#define COM4 0x0D +#define AEC 0x10 +#define CLKRC 0x11 +#define COM7 0x12 +#define COM8 0x13 +#define COM9 0x14 /* AGC gain ceiling */ +#define COM10 0x15 +#define HSTART 0x17 +#define HSTOP 0x18 +#define VSTART 0x19 +#define VSTOP 0x1A +#define MIDH 0x1C +#define MIDL 0x1D +#define AEW 0x24 +#define AEB 0x25 +#define VV 0x26 +#define REG2A 0x2A +#define FRARL 0x2B +#define ADDVSL 0x2D +#define ADDVSH 0x2E +#define YAVG 0x2F +#define HSDY 0x30 +#define HEDY 0x31 +#define REG32 0x32 +#define ARCOM2 0x34 +#define REG45 0x45 +#define FLL 0x46 +#define FLH 0x47 +#define COM19 0x48 +#define ZOOMS 0x49 +#define COM22 0x4B +#define COM25 0x4E +#define BD50 0x4F +#define BD60 0x50 +#define REG5D 0x5D +#define REG5E 0x5E +#define REG5F 0x5F +#define REG60 0x60 +#define HISTO_LOW 0x61 +#define HISTO_HIGH 0x62 + +#define REG04_DEFAULT 0x28 +#define REG04_HFLIP_IMG 0x80 +#define REG04_VFLIP_IMG 0x40 +#define REG04_VREF_EN 0x10 +#define REG04_HREF_EN 0x08 +#define REG04_SET(x) (REG04_DEFAULT|x) + +#define COM2_STDBY 0x10 +#define COM2_OUT_DRIVE_1x 0x00 +#define COM2_OUT_DRIVE_2x 0x01 +#define COM2_OUT_DRIVE_3x 0x02 +#define COM2_OUT_DRIVE_4x 0x03 + +#define COM3_DEFAULT 0x38 +#define COM3_BAND_50Hz 0x04 +#define COM3_BAND_60Hz 0x00 +#define COM3_BAND_AUTO 0x02 +#define COM3_BAND_SET(x) (COM3_DEFAULT|x) + +#define COM7_SRST 0x80 +#define COM7_RES_UXGA 0x00 /* UXGA */ +#define COM7_RES_SVGA 0x40 /* SVGA */ +#define COM7_RES_CIF 0x20 /* CIF */ +#define COM7_ZOOM_EN 0x04 /* Enable Zoom */ +#define COM7_COLOR_BAR 0x02 /* Enable Color Bar Test */ + +#define COM8_DEFAULT 0xC0 +#define COM8_BNDF_EN 0x20 /* Enable Banding filter */ +#define COM8_AGC_EN 0x04 /* AGC Auto/Manual control selection */ +#define COM8_AEC_EN 0x01 /* Auto/Manual Exposure control */ +#define COM8_SET(x) (COM8_DEFAULT|x) + +#define COM9_DEFAULT 0x08 +#define COM9_AGC_GAIN_2x 0x00 /* AGC: 2x */ +#define COM9_AGC_GAIN_4x 0x01 /* AGC: 4x */ +#define COM9_AGC_GAIN_8x 0x02 /* AGC: 8x */ +#define COM9_AGC_GAIN_16x 0x03 /* AGC: 16x */ +#define COM9_AGC_GAIN_32x 0x04 /* AGC: 32x */ +#define COM9_AGC_GAIN_64x 0x05 /* AGC: 64x */ +#define COM9_AGC_GAIN_128x 0x06 /* AGC: 128x */ +#define COM9_AGC_SET(x) (COM9_DEFAULT|(x<<5)) + +#define COM10_HREF_EN 0x80 /* HSYNC changes to HREF */ +#define COM10_HSYNC_EN 0x40 /* HREF changes to HSYNC */ +#define COM10_PCLK_FREE 0x20 /* PCLK output option: free running PCLK */ +#define COM10_PCLK_EDGE 0x10 /* Data is updated at the rising edge of PCLK */ +#define COM10_HREF_NEG 0x08 /* HREF negative */ +#define COM10_VSYNC_NEG 0x02 /* VSYNC negative */ +#define COM10_HSYNC_NEG 0x01 /* HSYNC negative */ + +#define CTRL1_AWB 0x08 /* Enable AWB */ + +#define VV_AGC_TH_SET(h,l) ((h<<4)|(l&0x0F)) + +#define REG32_UXGA 0x36 +#define REG32_SVGA 0x09 +#define REG32_CIF 0x89 + +#define CLKRC_2X 0x80 +#define CLKRC_2X_UXGA (0x01 | CLKRC_2X) +#define CLKRC_2X_SVGA CLKRC_2X +#define CLKRC_2X_CIF CLKRC_2X + +#endif //__REG_REGS_H__ diff --git a/esp32-cam/ov2640_settings.h b/esp32-cam/ov2640_settings.h new file mode 100644 index 0000000..f151f0a --- /dev/null +++ b/esp32-cam/ov2640_settings.h @@ -0,0 +1,485 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef _OV2640_SETTINGS_H_ +#define _OV2640_SETTINGS_H_ + +#include +#include +#include "esp_attr.h" +#include "ov2640_regs.h" + +typedef enum { + OV2640_MODE_UXGA, OV2640_MODE_SVGA, OV2640_MODE_CIF, OV2640_MODE_MAX +} ov2640_sensor_mode_t; + +typedef struct { + union { + struct { + uint8_t pclk_div:7; + uint8_t pclk_auto:1; + }; + uint8_t pclk; + }; + union { + struct { + uint8_t clk_div:6; + uint8_t reserved:1; + uint8_t clk_2x:1; + }; + uint8_t clk; + }; +} ov2640_clk_t; + +typedef struct { + uint16_t offset_x; + uint16_t offset_y; + uint16_t max_x; + uint16_t max_y; +} ov2640_ratio_settings_t; + +static const DRAM_ATTR ov2640_ratio_settings_t ratio_table[] = { + // ox, oy, mx, my + { 0, 0, 1600, 1200 }, //4x3 + { 8, 72, 1584, 1056 }, //3x2 + { 0, 100, 1600, 1000 }, //16x10 + { 0, 120, 1600, 960 }, //5x3 + { 0, 150, 1600, 900 }, //16x9 + { 2, 258, 1596, 684 }, //21x9 + { 50, 0, 1500, 1200 }, //5x4 + { 200, 0, 1200, 1200 }, //1x1 + { 462, 0, 676, 1200 } //9x16 +}; + +// 30fps@24MHz +const DRAM_ATTR uint8_t ov2640_settings_cif[][2] = { + {BANK_SEL, BANK_DSP}, + {0x2c, 0xff}, + {0x2e, 0xdf}, + {BANK_SEL, BANK_SENSOR}, + {0x3c, 0x32}, + {CLKRC, 0x01}, + {COM2, COM2_OUT_DRIVE_3x}, + {REG04, REG04_DEFAULT}, + {COM8, COM8_DEFAULT | COM8_BNDF_EN | COM8_AGC_EN | COM8_AEC_EN}, + {COM9, COM9_AGC_SET(COM9_AGC_GAIN_8x)}, + {0x2c, 0x0c}, + {0x33, 0x78}, + {0x3a, 0x33}, + {0x3b, 0xfB}, + {0x3e, 0x00}, + {0x43, 0x11}, + {0x16, 0x10}, + {0x39, 0x92}, + {0x35, 0xda}, + {0x22, 0x1a}, + {0x37, 0xc3}, + {0x23, 0x00}, + {ARCOM2, 0xc0}, + {0x06, 0x88}, + {0x07, 0xc0}, + {COM4, 0x87}, + {0x0e, 0x41}, + {0x4c, 0x00}, + {0x4a, 0x81}, + {0x21, 0x99}, + {AEW, 0x40}, + {AEB, 0x38}, + {VV, VV_AGC_TH_SET(8,2)}, + {0x5c, 0x00}, + {0x63, 0x00}, + {HISTO_LOW, 0x70}, + {HISTO_HIGH, 0x80}, + {0x7c, 0x05}, + {0x20, 0x80}, + {0x28, 0x30}, + {0x6c, 0x00}, + {0x6d, 0x80}, + {0x6e, 0x00}, + {0x70, 0x02}, + {0x71, 0x94}, + {0x73, 0xc1}, + {0x3d, 0x34}, + {0x5a, 0x57}, + {BD50, 0xbb}, + {BD60, 0x9c}, + {COM7, COM7_RES_CIF}, + {HSTART, 0x11}, + {HSTOP, 0x43}, + {VSTART, 0x00}, + {VSTOP, 0x25}, + {REG32, 0x89}, + {0x37, 0xc0}, + {BD50, 0xca}, + {BD60, 0xa8}, + {0x6d, 0x00}, + {0x3d, 0x38}, + {BANK_SEL, BANK_DSP}, + {0xe5, 0x7f}, + {MC_BIST, MC_BIST_RESET | MC_BIST_BOOT_ROM_SEL}, + {0x41, 0x24}, + {RESET, RESET_JPEG | RESET_DVP}, + {0x76, 0xff}, + {0x33, 0xa0}, + {0x42, 0x20}, + {0x43, 0x18}, + {0x4c, 0x00}, + {CTRL3, CTRL3_WPC_EN | 0x10 }, + {0x88, 0x3f}, + {0xd7, 0x03}, + {0xd9, 0x10}, + {R_DVP_SP, R_DVP_SP_AUTO_MODE | 0x02}, + {0xc8, 0x08}, + {0xc9, 0x80}, + {BPADDR, 0x00}, + {BPDATA, 0x00}, + {BPADDR, 0x03}, + {BPDATA, 0x48}, + {BPDATA, 0x48}, + {BPADDR, 0x08}, + {BPDATA, 0x20}, + {BPDATA, 0x10}, + {BPDATA, 0x0e}, + {0x90, 0x00}, + {0x91, 0x0e}, + {0x91, 0x1a}, + {0x91, 0x31}, + {0x91, 0x5a}, + {0x91, 0x69}, + {0x91, 0x75}, + {0x91, 0x7e}, + {0x91, 0x88}, + {0x91, 0x8f}, + {0x91, 0x96}, + {0x91, 0xa3}, + {0x91, 0xaf}, + {0x91, 0xc4}, + {0x91, 0xd7}, + {0x91, 0xe8}, + {0x91, 0x20}, + {0x92, 0x00}, + {0x93, 0x06}, + {0x93, 0xe3}, + {0x93, 0x05}, + {0x93, 0x05}, + {0x93, 0x00}, + {0x93, 0x04}, + {0x93, 0x00}, + {0x93, 0x00}, + {0x93, 0x00}, + {0x93, 0x00}, + {0x93, 0x00}, + {0x93, 0x00}, + {0x93, 0x00}, + {0x96, 0x00}, + {0x97, 0x08}, + {0x97, 0x19}, + {0x97, 0x02}, + {0x97, 0x0c}, + {0x97, 0x24}, + {0x97, 0x30}, + {0x97, 0x28}, + {0x97, 0x26}, + {0x97, 0x02}, + {0x97, 0x98}, + {0x97, 0x80}, + {0x97, 0x00}, + {0x97, 0x00}, + {0xa4, 0x00}, + {0xa8, 0x00}, + {0xc5, 0x11}, + {0xc6, 0x51}, + {0xbf, 0x80}, + {0xc7, 0x10}, + {0xb6, 0x66}, + {0xb8, 0xA5}, + {0xb7, 0x64}, + {0xb9, 0x7C}, + {0xb3, 0xaf}, + {0xb4, 0x97}, + {0xb5, 0xFF}, + {0xb0, 0xC5}, + {0xb1, 0x94}, + {0xb2, 0x0f}, + {0xc4, 0x5c}, + {CTRL1, 0xfd}, + {0x7f, 0x00}, + {0xe5, 0x1f}, + {0xe1, 0x67}, + {0xdd, 0x7f}, + {IMAGE_MODE, 0x00}, + {RESET, 0x00}, + {R_BYPASS, R_BYPASS_DSP_EN}, + {0, 0} +}; + +const DRAM_ATTR uint8_t ov2640_settings_to_cif[][2] = { + {BANK_SEL, BANK_SENSOR}, + {COM7, COM7_RES_CIF}, + + //Set the sensor output window + {COM1, 0x0A}, + {REG32, REG32_CIF}, + {HSTART, 0x11}, + {HSTOP, 0x43}, + {VSTART, 0x00}, + {VSTOP, 0x25}, + + //{CLKRC, 0x00}, + {BD50, 0xca}, + {BD60, 0xa8}, + {0x5a, 0x23}, + {0x6d, 0x00}, + {0x3d, 0x38}, + {0x39, 0x92}, + {0x35, 0xda}, + {0x22, 0x1a}, + {0x37, 0xc3}, + {0x23, 0x00}, + {ARCOM2, 0xc0}, + {0x06, 0x88}, + {0x07, 0xc0}, + {COM4, 0x87}, + {0x0e, 0x41}, + {0x4c, 0x00}, + {BANK_SEL, BANK_DSP}, + {RESET, RESET_DVP}, + + //Set the sensor resolution (UXGA, SVGA, CIF) + {HSIZE8, 0x32}, + {VSIZE8, 0x25}, + {SIZEL, 0x00}, + + //Set the image window size >= output size + {HSIZE, 0x64}, + {VSIZE, 0x4a}, + {XOFFL, 0x00}, + {YOFFL, 0x00}, + {VHYX, 0x00}, + {TEST, 0x00}, + + {CTRL2, CTRL2_DCW_EN | 0x1D}, + {CTRLI, CTRLI_LP_DP | 0x00}, + //{R_DVP_SP, 0x08}, + {0, 0} +}; + +const DRAM_ATTR uint8_t ov2640_settings_to_svga[][2] = { + {BANK_SEL, BANK_SENSOR}, + {COM7, COM7_RES_SVGA}, + + //Set the sensor output window + {COM1, 0x0A}, + {REG32, REG32_SVGA}, + {HSTART, 0x11}, + {HSTOP, 0x43}, + {VSTART, 0x00}, + {VSTOP, 0x4b}, + + //{CLKRC, 0x00}, + {0x37, 0xc0}, + {BD50, 0xca}, + {BD60, 0xa8}, + {0x5a, 0x23}, + {0x6d, 0x00}, + {0x3d, 0x38}, + {0x39, 0x92}, + {0x35, 0xda}, + {0x22, 0x1a}, + {0x37, 0xc3}, + {0x23, 0x00}, + {ARCOM2, 0xc0}, + {0x06, 0x88}, + {0x07, 0xc0}, + {COM4, 0x87}, + {0x0e, 0x41}, + {0x42, 0x03}, + {0x4c, 0x00}, + {BANK_SEL, BANK_DSP}, + {RESET, RESET_DVP}, + + //Set the sensor resolution (UXGA, SVGA, CIF) + {HSIZE8, 0x64}, + {VSIZE8, 0x4B}, + {SIZEL, 0x00}, + + //Set the image window size >= output size + {HSIZE, 0xC8}, + {VSIZE, 0x96}, + {XOFFL, 0x00}, + {YOFFL, 0x00}, + {VHYX, 0x00}, + {TEST, 0x00}, + + {CTRL2, CTRL2_DCW_EN | 0x1D}, + {CTRLI, CTRLI_LP_DP | 0x00}, + //{R_DVP_SP, 0x08}, + {0, 0} +}; + +const DRAM_ATTR uint8_t ov2640_settings_to_uxga[][2] = { + {BANK_SEL, BANK_SENSOR}, + {COM7, COM7_RES_UXGA}, + + //Set the sensor output window + {COM1, 0x0F}, + {REG32, REG32_UXGA}, + {HSTART, 0x11}, + {HSTOP, 0x75}, + {VSTART, 0x01}, + {VSTOP, 0x97}, + + //{CLKRC, 0x00}, + {0x3d, 0x34}, + {BD50, 0xbb}, + {BD60, 0x9c}, + {0x5a, 0x57}, + {0x6d, 0x80}, + {0x39, 0x82}, + {0x23, 0x00}, + {0x07, 0xc0}, + {0x4c, 0x00}, + {0x35, 0x88}, + {0x22, 0x0a}, + {0x37, 0x40}, + {ARCOM2, 0xa0}, + {0x06, 0x02}, + {COM4, 0xb7}, + {0x0e, 0x01}, + {0x42, 0x83}, + {BANK_SEL, BANK_DSP}, + {RESET, RESET_DVP}, + + //Set the sensor resolution (UXGA, SVGA, CIF) + {HSIZE8, 0xc8}, + {VSIZE8, 0x96}, + {SIZEL, 0x00}, + + //Set the image window size >= output size + {HSIZE, 0x90}, + {VSIZE, 0x2c}, + {XOFFL, 0x00}, + {YOFFL, 0x00}, + {VHYX, 0x88}, + {TEST, 0x00}, + + {CTRL2, CTRL2_DCW_EN | 0x1d}, + {CTRLI, 0x00}, + //{R_DVP_SP, 0x06}, + {0, 0} +}; + +const DRAM_ATTR uint8_t ov2640_settings_jpeg3[][2] = { + {BANK_SEL, BANK_DSP}, + {RESET, RESET_JPEG | RESET_DVP}, + {IMAGE_MODE, IMAGE_MODE_JPEG_EN | IMAGE_MODE_HREF_VSYNC}, + {0xD7, 0x03}, + {0xE1, 0x77}, + {0xE5, 0x1F}, + {0xD9, 0x10}, + {0xDF, 0x80}, + {0x33, 0x80}, + {0x3C, 0x10}, + {0xEB, 0x30}, + {0xDD, 0x7F}, + {RESET, 0x00}, + {0, 0} +}; + +static const uint8_t ov2640_settings_yuv422[][2] = { + {BANK_SEL, BANK_DSP}, + {RESET, RESET_DVP}, + {IMAGE_MODE, IMAGE_MODE_YUV422}, + {0xD7, 0x01}, + {0xE1, 0x67}, + {RESET, 0x00}, + {0, 0}, +}; + +static const uint8_t ov2640_settings_rgb565[][2] = { + {BANK_SEL, BANK_DSP}, + {RESET, RESET_DVP}, + {IMAGE_MODE, IMAGE_MODE_RGB565}, + {0xD7, 0x03}, + {0xE1, 0x77}, + {RESET, 0x00}, + {0, 0}, +}; + +#define NUM_BRIGHTNESS_LEVELS (5) +static const uint8_t brightness_regs[NUM_BRIGHTNESS_LEVELS + 1][5] = { + {BPADDR, BPDATA, BPADDR, BPDATA, BPDATA }, + {0x00, 0x04, 0x09, 0x00, 0x00 }, /* -2 */ + {0x00, 0x04, 0x09, 0x10, 0x00 }, /* -1 */ + {0x00, 0x04, 0x09, 0x20, 0x00 }, /* 0 */ + {0x00, 0x04, 0x09, 0x30, 0x00 }, /* +1 */ + {0x00, 0x04, 0x09, 0x40, 0x00 }, /* +2 */ +}; + +#define NUM_CONTRAST_LEVELS (5) +static const uint8_t contrast_regs[NUM_CONTRAST_LEVELS + 1][7] = { + {BPADDR, BPDATA, BPADDR, BPDATA, BPDATA, BPDATA, BPDATA }, + {0x00, 0x04, 0x07, 0x20, 0x18, 0x34, 0x06 }, /* -2 */ + {0x00, 0x04, 0x07, 0x20, 0x1c, 0x2a, 0x06 }, /* -1 */ + {0x00, 0x04, 0x07, 0x20, 0x20, 0x20, 0x06 }, /* 0 */ + {0x00, 0x04, 0x07, 0x20, 0x24, 0x16, 0x06 }, /* +1 */ + {0x00, 0x04, 0x07, 0x20, 0x28, 0x0c, 0x06 }, /* +2 */ +}; + +#define NUM_SATURATION_LEVELS (5) +static const uint8_t saturation_regs[NUM_SATURATION_LEVELS + 1][5] = { + {BPADDR, BPDATA, BPADDR, BPDATA, BPDATA }, + {0x00, 0x02, 0x03, 0x28, 0x28 }, /* -2 */ + {0x00, 0x02, 0x03, 0x38, 0x38 }, /* -1 */ + {0x00, 0x02, 0x03, 0x48, 0x48 }, /* 0 */ + {0x00, 0x02, 0x03, 0x58, 0x58 }, /* +1 */ + {0x00, 0x02, 0x03, 0x68, 0x68 }, /* +2 */ +}; + +#define NUM_SPECIAL_EFFECTS (7) +static const uint8_t special_effects_regs[NUM_SPECIAL_EFFECTS + 1][5] = { + {BPADDR, BPDATA, BPADDR, BPDATA, BPDATA }, + {0x00, 0X00, 0x05, 0X80, 0X80 }, /* no effect */ + {0x00, 0X40, 0x05, 0X80, 0X80 }, /* negative */ + {0x00, 0X18, 0x05, 0X80, 0X80 }, /* black and white */ + {0x00, 0X18, 0x05, 0X40, 0XC0 }, /* reddish */ + {0x00, 0X18, 0x05, 0X40, 0X40 }, /* greenish */ + {0x00, 0X18, 0x05, 0XA0, 0X40 }, /* blue */ + {0x00, 0X18, 0x05, 0X40, 0XA6 }, /* retro */ +}; + +#define NUM_WB_MODES (4) +static const uint8_t wb_modes_regs[NUM_WB_MODES + 1][3] = { + {0XCC, 0XCD, 0XCE }, + {0x5E, 0X41, 0x54 }, /* sunny */ + {0x65, 0X41, 0x4F }, /* cloudy */ + {0x52, 0X41, 0x66 }, /* office */ + {0x42, 0X3F, 0x71 }, /* home */ +}; + +#define NUM_AE_LEVELS (5) +static const uint8_t ae_levels_regs[NUM_AE_LEVELS + 1][3] = { + { AEW, AEB, VV }, + {0x20, 0X18, 0x60 }, + {0x34, 0X1C, 0x00 }, + {0x3E, 0X38, 0x81 }, + {0x48, 0X40, 0x81 }, + {0x58, 0X50, 0x92 }, +}; + +const uint8_t agc_gain_tbl[31] = { + 0x00, 0x10, 0x18, 0x30, 0x34, 0x38, 0x3C, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7A, 0x7C, 0x7E, 0xF0, + 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF +}; + +#endif /* _OV2640_SETTINGS_H_ */ diff --git a/esp32-cam/ov3660.c b/esp32-cam/ov3660.c new file mode 100644 index 0000000..723ec5c --- /dev/null +++ b/esp32-cam/ov3660.c @@ -0,0 +1,1033 @@ +/* + * 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. + * + * OV3660 driver. + * + */ +#include +#include +#include +#include "sccb.h" +#include "ov3660.h" +#include "ov3660_regs.h" +#include "ov3660_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 = "ov3660"; +#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_LOGI(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){ + int ret = 0; + if(multiplier > 31 || sys_div > 15 || pre_div > 3 || pclk_div > 31 || seld5 > 3){ + ESP_LOGE(TAG, "Invalid arguments"); + return -1; + } + + calc_sysclk(sensor->xclk_freq_hz, bypass, multiplier, sys_div, pre_div, root_2x, seld5, pclk_manual, pclk_div); + + ret = write_reg(sensor->slv_addr, SC_PLLS_CTRL0, bypass?0x80:0x00); + if (ret == 0) { + ret = write_reg(sensor->slv_addr, SC_PLLS_CTRL1, multiplier & 0x1f); + } + if (ret == 0) { + ret = write_reg(sensor->slv_addr, SC_PLLS_CTRL2, 0x10 | (sys_div & 0x0f)); + } + if (ret == 0) { + ret = write_reg(sensor->slv_addr, SC_PLLS_CTRL3, (pre_div & 0x3) << 4 | seld5 | (root_2x?0x40:0x00)); + } + if (ret == 0) { + ret = write_reg(sensor->slv_addr, PCLK_RATIO, pclk_div & 0x1f); + } + if (ret == 0) { + ret = write_reg(sensor->slv_addr, VFIFO_CTRL0C, pclk_manual?0x22:0x20); + } + if(ret){ + ESP_LOGE(TAG, "set_sensor_pll FAILED!"); + } + return ret; +} + +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, 0x82); + if(ret){ + ESP_LOGE(TAG, "Software Reset FAILED!"); + return ret; + } + vTaskDelay(100 / portTICK_PERIOD_MS); + ret = write_regs(sensor->slv_addr, sensor_default_regs); + 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; + + // compression + if (sensor->pixformat == PIXFORMAT_JPEG) { + reg21 |= 0x20; + } + + // binning + if (sensor->status.binning) { + reg20 |= 0x01; + reg21 |= 0x01; + reg4514_test |= 4; + } else { + reg20 |= 0x40; + } + + // V-Flip + if (sensor->status.vflip) { + reg20 |= 0x06; + reg4514_test |= 1; + } + + // H-Mirror + if (sensor->status.hmirror) { + reg21 |= 0x06; + reg4514_test |= 2; + } + + switch (reg4514_test) { + //no binning + case 0: reg4514 = 0x88; break;//normal + case 1: reg4514 = 0x88; break;//v-flip + case 2: reg4514 = 0xbb; break;//h-mirror + case 3: reg4514 = 0xbb; break;//v-flip+h-mirror + //binning + case 4: reg4514 = 0xaa; break;//normal + case 5: reg4514 = 0xbb; break;//v-flip + case 6: reg4514 = 0xbb; break;//h-mirror + case 7: reg4514 = 0xaa; break;//v-flip+h-mirror + } + + if(write_reg(sensor->slv_addr, TIMING_TC_REG20, reg20) + || write_reg(sensor->slv_addr, TIMING_TC_REG21, reg21) + || write_reg(sensor->slv_addr, 0x4514, reg4514)){ + ESP_LOGE(TAG, "Setting Image Options Failed"); + ret = -1; + } + + if (sensor->status.binning) { + ret = write_reg(sensor->slv_addr, 0x4520, 0x0b) + || write_reg(sensor->slv_addr, X_INCREMENT, 0x31)//odd:3, even: 1 + || write_reg(sensor->slv_addr, Y_INCREMENT, 0x31);//odd:3, even: 1 + } else { + ret = write_reg(sensor->slv_addr, 0x4520, 0xb0) + || write_reg(sensor->slv_addr, X_INCREMENT, 0x11)//odd:1, even: 1 + || write_reg(sensor->slv_addr, Y_INCREMENT, 0x11);//odd:1, even: 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; + framesize_t old_framesize = sensor->status.framesize; + sensor->status.framesize = framesize; + + if(framesize > FRAMESIZE_QXGA){ + ESP_LOGE(TAG, "Invalid framesize: %u", framesize); + return -1; + } + uint16_t w = resolution[framesize].width; + uint16_t h = resolution[framesize].height; + aspect_ratio_t ratio = resolution[sensor->status.framesize].aspect_ratio; + ratio_settings_t settings = ratio_table[ratio]; + + sensor->status.binning = (w <= (settings.max_width / 2) && h <= (settings.max_height / 2)); + sensor->status.scale = !((w == settings.max_width && h == settings.max_height) + || (w == (settings.max_width / 2) && h == (settings.max_height / 2))); + + ret = write_addr_reg(sensor->slv_addr, X_ADDR_ST_H, settings.start_x, settings.start_y) + || write_addr_reg(sensor->slv_addr, X_ADDR_END_H, settings.end_x, settings.end_y) + || write_addr_reg(sensor->slv_addr, X_OUTPUT_SIZE_H, w, h); + + if (ret) { + goto fail; + } + + if (sensor->status.binning) { + ret = write_addr_reg(sensor->slv_addr, X_TOTAL_SIZE_H, settings.total_x, (settings.total_y / 2) + 1) + || write_addr_reg(sensor->slv_addr, X_OFFSET_H, 8, 2); + } else { + ret = write_addr_reg(sensor->slv_addr, X_TOTAL_SIZE_H, settings.total_x, settings.total_y) + || write_addr_reg(sensor->slv_addr, X_OFFSET_H, 16, 6); + } + + if (ret == 0) { + ret = write_reg_bits(sensor->slv_addr, ISP_CONTROL_01, 0x20, sensor->status.scale); + } + + if (ret == 0) { + ret = set_image_options(sensor); + } + + if (ret) { + goto fail; + } + + if (sensor->pixformat == PIXFORMAT_JPEG) { + if (framesize == FRAMESIZE_QXGA) { + //40MHz SYSCLK and 10MHz PCLK + ret = set_pll(sensor, false, 24, 1, 3, false, 0, true, 8); + } else { + //50MHz SYSCLK and 10MHz PCLK + ret = set_pll(sensor, false, 30, 1, 3, false, 0, true, 10); + } + } else { + if (framesize > FRAMESIZE_CIF) { + //10MHz SYSCLK and 10MHz PCLK (6.19 FPS) + ret = set_pll(sensor, false, 2, 1, 0, false, 0, true, 2); + } else { + //25MHz SYSCLK and 10MHz PCLK (15.45 FPS) + ret = set_pll(sensor, false, 5, 1, 0, false, 0, true, 5); + } + } + + if (ret == 0) { + ESP_LOGD(TAG, "Set framesize to: %ux%u", w, h); + } + return ret; + +fail: + sensor->status.framesize = old_framesize; + ESP_LOGE(TAG, "Setting framesize to: %ux%u failed", w, h); + return ret; +} + +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, AEC_PK_MANUAL, AEC_PK_MANUAL_AGC_MANUALEN, !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; + ret = write_reg_bits(sensor->slv_addr, AEC_PK_MANUAL, AEC_PK_MANUAL_AEC_MANUALEN, !enable); + 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; + ret = write_reg_bits(sensor->slv_addr, ISP_CONTROL_01, 0x01, enable); + 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; + ret = write_reg_bits(sensor->slv_addr, 0x5183, 0x80, !enable); + 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; + ret = write_reg_bits(sensor->slv_addr, 0x3a00, 0x04, enable); + 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; + ret = write_reg_bits(sensor->slv_addr, 0x5000, 0x04, enable); + 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; + ret = write_reg_bits(sensor->slv_addr, 0x5000, 0x02, enable); + 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; + ret = write_reg_bits(sensor->slv_addr, 0x5000, 0x20, enable); + 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; + ret = write_reg_bits(sensor->slv_addr, 0x5000, 0x80, enable); + 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) +{ + int ra = read_reg(sensor->slv_addr, 0x350a); + if (ra < 0) { + return 0; + } + int rb = read_reg(sensor->slv_addr, 0x350b); + if (rb < 0) { + return 0; + } + int res = (rb & 0xF0) >> 4 | (ra & 0x03) << 4; + if (rb & 0x0F) { + res += 1; + } + return res; +} + +//real gain +static int set_agc_gain(sensor_t *sensor, int gain) +{ + int ret = 0; + if(gain < 0) { + gain = 0; + } else if(gain > 64) { + gain = 64; + } + + //gain value is 6.4 bits float + //in order to use the max range, we deduct 1/16 + int gainv = gain << 4; + if(gainv){ + gainv -= 1; + } + + ret = write_reg(sensor->slv_addr, 0x350a, gainv >> 8) || write_reg(sensor->slv_addr, 0x350b, gainv & 0xff); + if (ret == 0) { + ESP_LOGD(TAG, "Set agc_gain to: %d", gain); + sensor->status.agc_gain = gain; + } + return ret; +} + +static int get_aec_value(sensor_t *sensor) +{ + int ra = read_reg(sensor->slv_addr, 0x3500); + if (ra < 0) { + return 0; + } + int rb = read_reg(sensor->slv_addr, 0x3501); + if (rb < 0) { + return 0; + } + int rc = read_reg(sensor->slv_addr, 0x3502); + if (rc < 0) { + return 0; + } + int res = (ra & 0x0F) << 12 | (rb & 0xFF) << 4 | (rc & 0xF0) >> 4; + return res; +} + +static int set_aec_value(sensor_t *sensor, int value) +{ + int ret = 0, max_val = 0; + max_val = read_reg16(sensor->slv_addr, 0x380e); + if (max_val < 0) { + ESP_LOGE(TAG, "Could not read max aec_value"); + return -1; + } + if (value > max_val) { + value =max_val; + } + + ret = write_reg(sensor->slv_addr, 0x3500, (value >> 12) & 0x0F) + || write_reg(sensor->slv_addr, 0x3501, (value >> 4) & 0xFF) + || write_reg(sensor->slv_addr, 0x3502, (value << 4) & 0xF0); + + if (ret == 0) { + ESP_LOGD(TAG, "Set aec_value to: %d / %d", value, max_val); + sensor->status.aec_value = value; + } + return ret; +} + +static int set_ae_level(sensor_t *sensor, int level) +{ + int ret = 0; + if (level < -5 || level > 5) { + return -1; + } + //good targets are between 5 and 115 + int target_level = ((level + 5) * 10) + 5; + + int level_high, level_low; + int fast_high, fast_low; + + level_low = target_level * 23 / 25; //0.92 (0.46) + level_high = target_level * 27 / 25; //1.08 (2.08) + + fast_low = level_low >> 1; + fast_high = level_high << 1; + + if(fast_high>255) { + fast_high = 255; + } + + ret = write_reg(sensor->slv_addr, 0x3a0f, level_high) + || write_reg(sensor->slv_addr, 0x3a10, level_low) + || write_reg(sensor->slv_addr, 0x3a1b, level_high) + || write_reg(sensor->slv_addr, 0x3a1e, level_low) + || write_reg(sensor->slv_addr, 0x3a11, fast_high) + || write_reg(sensor->slv_addr, 0x3a1f, fast_low); + + if (ret == 0) { + ESP_LOGD(TAG, "Set ae_level to: %d", level); + sensor->status.ae_level = level; + } + return ret; +} + +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, 0x3406, (mode != 0)); + if (ret) { + return ret; + } + switch (mode) { + case 1://Sunny + ret = write_reg16(sensor->slv_addr, 0x3400, 0x5e0) //AWB R GAIN + || write_reg16(sensor->slv_addr, 0x3402, 0x410) //AWB G GAIN + || write_reg16(sensor->slv_addr, 0x3404, 0x540);//AWB B GAIN + break; + case 2://Cloudy + ret = write_reg16(sensor->slv_addr, 0x3400, 0x650) //AWB R GAIN + || write_reg16(sensor->slv_addr, 0x3402, 0x410) //AWB G GAIN + || write_reg16(sensor->slv_addr, 0x3404, 0x4f0);//AWB B GAIN + break; + case 3://Office + ret = write_reg16(sensor->slv_addr, 0x3400, 0x520) //AWB R GAIN + || write_reg16(sensor->slv_addr, 0x3402, 0x410) //AWB G GAIN + || write_reg16(sensor->slv_addr, 0x3404, 0x660);//AWB B GAIN + break; + case 4://HOME + ret = write_reg16(sensor->slv_addr, 0x3400, 0x420) //AWB R GAIN + || write_reg16(sensor->slv_addr, 0x3402, 0x3f0) //AWB G GAIN + || write_reg16(sensor->slv_addr, 0x3404, 0x710);//AWB B GAIN + 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, 0x5580, regs[0]) + || write_reg(sensor->slv_addr, 0x5583, regs[1]) + || write_reg(sensor->slv_addr, 0x5584, regs[2]) + || write_reg(sensor->slv_addr, 0x5003, 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 = 0x30; + break; + case 2: + value = 0x20; + break; + case 1: + value = 0x10; + break; + case -1: + value = 0x10; + negative = true; + break; + case -2: + value = 0x20; + negative = true; + break; + case -3: + value = 0x30; + negative = true; + break; + default: // 0 + break; + } + + ret = write_reg(sensor->slv_addr, 0x5587, value); + if (ret == 0) { + ret = write_reg_bits(sensor->slv_addr, 0x5588, 0x08, negative); + } + + 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; + if(level > 3 || level < -3) { + return -1; + } + ret = write_reg(sensor->slv_addr, 0x5586, (level + 4) << 3); + + 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]; + for(int i=0; i<11; i++) { + ret = write_reg(sensor->slv_addr, 0x5381 + i, regs[i]); + if (ret) { + break; + } + } + + 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) +{ + int ret = 0, l = (int)level; + + ret = write_reg(sensor->slv_addr, 0x3A18, (l >> 8) & 3) + || write_reg(sensor->slv_addr, 0x3A19, l & 0xFF); + + if (ret == 0) { + ESP_LOGD(TAG, "Set gainceiling to: %d", l); + sensor->status.gainceiling = l; + } + return ret; +} + +static int get_denoise(sensor_t *sensor) +{ + if (!check_reg_mask(sensor->slv_addr, 0x5308, 0x10)) { + return 0; + } + return (read_reg(sensor->slv_addr, 0x5306) / 4) + 1; +} + +static int set_denoise(sensor_t *sensor, int level) +{ + int ret = 0; + if (level < 0 || level > 8) { + return -1; + } + + ret = write_reg_bits(sensor->slv_addr, 0x5308, 0x10, level > 0); + if (ret == 0 && level > 0) { + ret = write_reg(sensor->slv_addr, 0x5306, (level - 1) * 4); + } + + if (ret == 0) { + ESP_LOGD(TAG, "Set denoise to: %d", level); + sensor->status.denoise = level; + } + return ret; +} + +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) + || write_reg_bits(sensor->slv_addr, ISP_CONTROL_01, 0x20, scale); + 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; + 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, 0x5303) / 8) - 3; + sensor->status.denoise = get_denoise(sensor); + sensor->status.ae_level = 0; + sensor->status.gainceiling = read_reg16(sensor->slv_addr, 0x3A18) & 0x3FF; + sensor->status.awb = check_reg_mask(sensor->slv_addr, ISP_CONTROL_01, 0x01); + 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, 0x3406, 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, 0x3a00, 0x04); + return 0; +} + +int ov3660_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/ov3660.h b/esp32-cam/ov3660.h new file mode 100644 index 0000000..8e5ae3c --- /dev/null +++ b/esp32-cam/ov3660.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. + * + * OV3660 driver. + * + */ +#ifndef __OV3660_H__ +#define __OV3660_H__ + +#include "sensor.h" + +int ov3660_init(sensor_t *sensor); + +#endif // __OV3660_H__ diff --git a/esp32-cam/ov3660_regs.h b/esp32-cam/ov3660_regs.h new file mode 100644 index 0000000..b5cf30a --- /dev/null +++ b/esp32-cam/ov3660_regs.h @@ -0,0 +1,211 @@ +/* + * OV3660 register definitions. + */ +#ifndef __OV3660_REG_REGS_H__ +#define __OV3660_REG_REGS_H__ + +/* system control registers */ +#define SYSTEM_CTROL0 0x3008 // Bit[7]: Software reset + // Bit[6]: Software power down + // Bit[5]: Reserved + // Bit[4]: SRB clock SYNC enable + // Bit[3]: Isolation suspend select + // Bit[2:0]: Not used + +/* 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 0x503D // Bit[7]: Test enable + // 0: Test disable + // 1: Color bar enable + // Bit[6]: Rolling + // Bit[5]: Transparent + // Bit[4]: Square black and white + // Bit[3:2]: Color bar style + // 00: Standard 8 color bar + // 01: Gradual change at vertical mode 1 + // 10: Gradual change at horizontal + // 11: Gradual change at vertical mode 2 + // Bit[1:0]: Test select + // 00: Color bar + // 01: Random data + // 10: Square data + // 11: Black image + +//exposure = {0x3500[3:0], 0x3501[7:0], 0x3502[7:0]} / 16 × tROW + +/* AEC/AGC control functions */ +#define AEC_PK_MANUAL 0x3503 // AEC Manual Mode Control + // Bit[7:6]: Reserved + // Bit[5]: Gain delay option + // Valid when 0x3503[4]=1’b0 + // 0: Delay one frame latch + // 1: One frame latch + // Bit[4:2]: Reserved + // Bit[1]: AGC manual + // 0: Auto enable + // 1: Manual enable + // Bit[0]: AEC manual + // 0: Auto enable + // 1: Manual enable + +//gain = {0x350A[1:0], 0x350B[7:0]} / 16 + +/* mirror and flip registers */ +#define TIMING_TC_REG20 0x3820 // Timing Control Register + // Bit[2:1]: Vertical flip enable + // 00: Normal + // 11: Vertical flip + // Bit[0]: Vertical binning enable +#define TIMING_TC_REG21 0x3821 // 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 0x4740// Bit[5]: PCLK polarity 0: active low + // 1: active high + // Bit[3]: Gate PCLK under VSYNC + // Bit[2]: Gate PCLK under HREF + // Bit[1]: HREF polarity + // 0: active low + // 1: active high + // Bit[0] VSYNC polarity + // 0: active low + // 1: active high +#define DRIVE_CAPABILITY 0x302c // 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 0x5001 // 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 0x4407 // 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 0xC0 /* 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 0x06 /* Vertical flip enable */ +#define TIMING_TC_REG21_HMIRROR 0x06 /* Horizontal mirror enable */ + +#endif // __OV3660_REG_REGS_H__ diff --git a/esp32-cam/ov3660_settings.h b/esp32-cam/ov3660_settings.h new file mode 100644 index 0000000..97c4e03 --- /dev/null +++ b/esp32-cam/ov3660_settings.h @@ -0,0 +1,318 @@ +#ifndef _OV3660_SETTINGS_H_ +#define _OV3660_SETTINGS_H_ + +#include +#include +#include "esp_attr.h" +#include "ov3660_regs.h" + +static const ratio_settings_t ratio_table[] = { + // mw, mh, sx, sy, ex, ey, ox, oy, tx, ty + { 2048, 1536, 0, 0, 2079, 1547, 16, 6, 2300, 1564 }, //4x3 + { 1920, 1280, 64, 128, 2015, 1419, 16, 6, 2172, 1436 }, //3x2 + { 2048, 1280, 0, 128, 2079, 1419, 16, 6, 2300, 1436 }, //16x10 + { 1920, 1152, 64, 192, 2015, 1355, 16, 6, 2172, 1372 }, //5x3 + { 1920, 1080, 64, 242, 2015, 1333, 16, 6, 2172, 1322 }, //16x9 + { 2048, 880, 0, 328, 2079, 1219, 16, 6, 2300, 1236 }, //21x9 + { 1920, 1536, 64, 0, 2015, 1547, 16, 6, 2172, 1564 }, //5x4 + { 1536, 1536, 256, 0, 1823, 1547, 16, 6, 2044, 1564 }, //1x1 + { 864, 1536, 592, 0, 1487, 1547, 16, 6, 2044, 1564 } //9x16 +}; + +#define REG_DLY 0xffff +#define REGLIST_TAIL 0x0000 + +static const DRAM_ATTR uint16_t sensor_default_regs[][2] = { + {SYSTEM_CTROL0, 0x82}, // software reset + {REG_DLY, 10}, // delay 10ms + + {0x3103, 0x13}, + {SYSTEM_CTROL0, 0x42}, + {0x3017, 0xff}, + {0x3018, 0xff}, + {DRIVE_CAPABILITY, 0xc3}, + {CLOCK_POL_CONTROL, 0x21}, + + {0x3611, 0x01}, + {0x3612, 0x2d}, + + {0x3032, 0x00}, + {0x3614, 0x80}, + {0x3618, 0x00}, + {0x3619, 0x75}, + {0x3622, 0x80}, + {0x3623, 0x00}, + {0x3624, 0x03}, + {0x3630, 0x52}, + {0x3632, 0x07}, + {0x3633, 0xd2}, + {0x3704, 0x80}, + {0x3708, 0x66}, + {0x3709, 0x12}, + {0x370b, 0x12}, + {0x3717, 0x00}, + {0x371b, 0x60}, + {0x371c, 0x00}, + {0x3901, 0x13}, + + {0x3600, 0x08}, + {0x3620, 0x43}, + {0x3702, 0x20}, + {0x3739, 0x48}, + {0x3730, 0x20}, + {0x370c, 0x0c}, + + {0x3a18, 0x00}, + {0x3a19, 0xf8}, + + {0x3000, 0x10}, + {0x3004, 0xef}, + + {0x6700, 0x05}, + {0x6701, 0x19}, + {0x6702, 0xfd}, + {0x6703, 0xd1}, + {0x6704, 0xff}, + {0x6705, 0xff}, + + {0x3c01, 0x80}, + {0x3c00, 0x04}, + {0x3a08, 0x00}, {0x3a09, 0x62}, //50Hz Band Width Step (10bit) + {0x3a0e, 0x08}, //50Hz Max Bands in One Frame (6 bit) + {0x3a0a, 0x00}, {0x3a0b, 0x52}, //60Hz Band Width Step (10bit) + {0x3a0d, 0x09}, //60Hz Max Bands in One Frame (6 bit) + + {0x3a00, 0x3a},//night mode off + {0x3a14, 0x09}, + {0x3a15, 0x30}, + {0x3a02, 0x09}, + {0x3a03, 0x30}, + + {COMPRESSION_CTRL0E, 0x08}, + {0x4520, 0x0b}, + {0x460b, 0x37}, + {0x4713, 0x02}, + {0x471c, 0xd0}, + {0x5086, 0x00}, + + {0x5002, 0x00}, + {0x501f, 0x00}, + + {SYSTEM_CTROL0, 0x02}, + + {0x5180, 0xff}, + {0x5181, 0xf2}, + {0x5182, 0x00}, + {0x5183, 0x14}, + {0x5184, 0x25}, + {0x5185, 0x24}, + {0x5186, 0x16}, + {0x5187, 0x16}, + {0x5188, 0x16}, + {0x5189, 0x68}, + {0x518a, 0x60}, + {0x518b, 0xe0}, + {0x518c, 0xb2}, + {0x518d, 0x42}, + {0x518e, 0x35}, + {0x518f, 0x56}, + {0x5190, 0x56}, + {0x5191, 0xf8}, + {0x5192, 0x04}, + {0x5193, 0x70}, + {0x5194, 0xf0}, + {0x5195, 0xf0}, + {0x5196, 0x03}, + {0x5197, 0x01}, + {0x5198, 0x04}, + {0x5199, 0x12}, + {0x519a, 0x04}, + {0x519b, 0x00}, + {0x519c, 0x06}, + {0x519d, 0x82}, + {0x519e, 0x38}, + + {0x5381, 0x1d}, + {0x5382, 0x60}, + {0x5383, 0x03}, + {0x5384, 0x0c}, + {0x5385, 0x78}, + {0x5386, 0x84}, + {0x5387, 0x7d}, + {0x5388, 0x6b}, + {0x5389, 0x12}, + {0x538a, 0x01}, + {0x538b, 0x98}, + + {0x5480, 0x01}, +// {0x5481, 0x05}, +// {0x5482, 0x09}, +// {0x5483, 0x10}, +// {0x5484, 0x3a}, +// {0x5485, 0x4c}, +// {0x5486, 0x5a}, +// {0x5487, 0x68}, +// {0x5488, 0x74}, +// {0x5489, 0x80}, +// {0x548a, 0x8e}, +// {0x548b, 0xa4}, +// {0x548c, 0xb4}, +// {0x548d, 0xc8}, +// {0x548e, 0xde}, +// {0x548f, 0xf0}, +// {0x5490, 0x15}, + + {0x5000, 0xa7}, + {0x5800, 0x0C}, + {0x5801, 0x09}, + {0x5802, 0x0C}, + {0x5803, 0x0C}, + {0x5804, 0x0D}, + {0x5805, 0x17}, + {0x5806, 0x06}, + {0x5807, 0x05}, + {0x5808, 0x04}, + {0x5809, 0x06}, + {0x580a, 0x09}, + {0x580b, 0x0E}, + {0x580c, 0x05}, + {0x580d, 0x01}, + {0x580e, 0x01}, + {0x580f, 0x01}, + {0x5810, 0x05}, + {0x5811, 0x0D}, + {0x5812, 0x05}, + {0x5813, 0x01}, + {0x5814, 0x01}, + {0x5815, 0x01}, + {0x5816, 0x05}, + {0x5817, 0x0D}, + {0x5818, 0x08}, + {0x5819, 0x06}, + {0x581a, 0x05}, + {0x581b, 0x07}, + {0x581c, 0x0B}, + {0x581d, 0x0D}, + {0x581e, 0x12}, + {0x581f, 0x0D}, + {0x5820, 0x0E}, + {0x5821, 0x10}, + {0x5822, 0x10}, + {0x5823, 0x1E}, + {0x5824, 0x53}, + {0x5825, 0x15}, + {0x5826, 0x05}, + {0x5827, 0x14}, + {0x5828, 0x54}, + {0x5829, 0x25}, + {0x582a, 0x33}, + {0x582b, 0x33}, + {0x582c, 0x34}, + {0x582d, 0x16}, + {0x582e, 0x24}, + {0x582f, 0x41}, + {0x5830, 0x50}, + {0x5831, 0x42}, + {0x5832, 0x15}, + {0x5833, 0x25}, + {0x5834, 0x34}, + {0x5835, 0x33}, + {0x5836, 0x24}, + {0x5837, 0x26}, + {0x5838, 0x54}, + {0x5839, 0x25}, + {0x583a, 0x15}, + {0x583b, 0x25}, + {0x583c, 0x53}, + {0x583d, 0xCF}, + + {0x3a0f, 0x30}, + {0x3a10, 0x28}, + {0x3a1b, 0x30}, + {0x3a1e, 0x28}, + {0x3a11, 0x60}, + {0x3a1f, 0x14}, + + {0x5302, 0x28}, + {0x5303, 0x20}, + + {0x5306, 0x1c}, //de-noise offset 1 + {0x5307, 0x28}, //de-noise offset 2 + + {0x4002, 0xc5}, + {0x4003, 0x81}, + {0x4005, 0x12}, + + {0x5688, 0x11}, + {0x5689, 0x11}, + {0x568a, 0x11}, + {0x568b, 0x11}, + {0x568c, 0x11}, + {0x568d, 0x11}, + {0x568e, 0x11}, + {0x568f, 0x11}, + + {0x5580, 0x06}, + {0x5588, 0x00}, + {0x5583, 0x40}, + {0x5584, 0x2c}, + + {ISP_CONTROL_01, 0x83}, // turn color matrix, awb and SDE + {REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_fmt_jpeg[][2] = { + {FORMAT_CTRL, 0x00}, // YUV422 + {FORMAT_CTRL00, 0x30}, // YUYV + {0x3002, 0x00},//0x1c to 0x00 !!! + {0x3006, 0xff},//0xc3 to 0xff !!! + {0x471c, 0x50},//0xd0 to 0x50 !!! + {REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_fmt_raw[][2] = { + {FORMAT_CTRL00, 0x00}, // RAW + {REGLIST_TAIL, 0x00} +}; + +static const DRAM_ATTR uint16_t sensor_fmt_grayscale[][2] = { + {FORMAT_CTRL, 0x00}, // YUV422 + {FORMAT_CTRL00, 0x10}, // Y8 + {REGLIST_TAIL, 0x00} +}; + +static const DRAM_ATTR uint16_t sensor_fmt_yuv422[][2] = { + {FORMAT_CTRL, 0x00}, // YUV422 + {FORMAT_CTRL00, 0x30}, // YUYV + {REGLIST_TAIL, 0x00} +}; + +static const DRAM_ATTR uint16_t sensor_fmt_rgb565[][2] = { + {FORMAT_CTRL, 0x01}, // RGB + {FORMAT_CTRL00, 0x61}, // RGB565 (BGR) + {REGLIST_TAIL, 0x00} +}; + +static const DRAM_ATTR uint8_t sensor_saturation_levels[9][11] = { + {0x1d, 0x60, 0x03, 0x07, 0x48, 0x4f, 0x4b, 0x40, 0x0b, 0x01, 0x98},//-4 + {0x1d, 0x60, 0x03, 0x08, 0x54, 0x5c, 0x58, 0x4b, 0x0d, 0x01, 0x98},//-3 + {0x1d, 0x60, 0x03, 0x0a, 0x60, 0x6a, 0x64, 0x56, 0x0e, 0x01, 0x98},//-2 + {0x1d, 0x60, 0x03, 0x0b, 0x6c, 0x77, 0x70, 0x60, 0x10, 0x01, 0x98},//-1 + {0x1d, 0x60, 0x03, 0x0c, 0x78, 0x84, 0x7d, 0x6b, 0x12, 0x01, 0x98},//0 + {0x1d, 0x60, 0x03, 0x0d, 0x84, 0x91, 0x8a, 0x76, 0x14, 0x01, 0x98},//+1 + {0x1d, 0x60, 0x03, 0x0e, 0x90, 0x9e, 0x96, 0x80, 0x16, 0x01, 0x98},//+2 + {0x1d, 0x60, 0x03, 0x10, 0x9c, 0xac, 0xa2, 0x8b, 0x17, 0x01, 0x98},//+3 + {0x1d, 0x60, 0x03, 0x11, 0xa8, 0xb9, 0xaf, 0x96, 0x19, 0x01, 0x98},//+4 +}; + +static const DRAM_ATTR uint8_t sensor_special_effects[7][4] = { + {0x06, 0x40, 0x2c, 0x08},//Normal + {0x46, 0x40, 0x28, 0x08},//Negative + {0x1e, 0x80, 0x80, 0x08},//Grayscale + {0x1e, 0x80, 0xc0, 0x08},//Red Tint + {0x1e, 0x60, 0x60, 0x08},//Green Tint + {0x1e, 0xa0, 0x40, 0x08},//Blue Tint + {0x1e, 0x40, 0xa0, 0x08},//Sepia +}; + +#endif diff --git a/esp32-cam/ov5640.c b/esp32-cam/ov5640.c new file mode 100644 index 0000000..e7adcf4 --- /dev/null +++ b/esp32-cam/ov5640.c @@ -0,0 +1,1105 @@ +/* + * 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. + * + * OV3660 driver. + * + */ +#include +#include +#include +#include "sccb.h" +#include "ov5640.h" +#include "ov5640_regs.h" +#include "ov5640_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 = "ov5640"; +#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 void dump_reg(sensor_t *sensor, const uint16_t reg){ +// int v = SCCB_Read16(sensor->slv_addr, reg); +// if(v < 0){ +// ets_printf(" 0x%04x: FAIL[%d]\n", reg, v); +// } else { +// ets_printf(" 0x%04x: 0x%02X\n", reg, v); +// } +//} +// +//static void dump_range(sensor_t *sensor, const char * name, const uint16_t start_reg, const uint16_t end_reg){ +// ets_printf("%s: 0x%04x - 0x%04X\n", name, start_reg, end_reg); +// for(uint16_t reg = start_reg; reg <= end_reg; reg++){ +// dump_reg(sensor, reg); +// } +//} +// +//static void dump_regs(sensor_t *sensor){ +//// dump_range(sensor, "All Regs", 0x3000, 0x6100); +//// dump_range(sensor, "system and IO pad control", 0x3000, 0x3052); +//// dump_range(sensor, "SCCB control", 0x3100, 0x3108); +//// dump_range(sensor, "SRB control", 0x3200, 0x3211); +//// dump_range(sensor, "AWB gain control", 0x3400, 0x3406); +//// dump_range(sensor, "AEC/AGC control", 0x3500, 0x350D); +//// dump_range(sensor, "VCM control", 0x3600, 0x3606); +//// dump_range(sensor, "timing control", 0x3800, 0x3821); +//// dump_range(sensor, "AEC/AGC power down domain control", 0x3A00, 0x3A25); +//// dump_range(sensor, "strobe control", 0x3B00, 0x3B0C); +//// dump_range(sensor, "50/60Hz detector control", 0x3C00, 0x3C1E); +//// dump_range(sensor, "OTP control", 0x3D00, 0x3D21); +//// dump_range(sensor, "MC control", 0x3F00, 0x3F0D); +//// dump_range(sensor, "BLC control", 0x4000, 0x4033); +//// dump_range(sensor, "frame control", 0x4201, 0x4202); +//// dump_range(sensor, "format control", 0x4300, 0x430D); +//// dump_range(sensor, "JPEG control", 0x4400, 0x4431); +//// dump_range(sensor, "VFIFO control", 0x4600, 0x460D); +//// dump_range(sensor, "DVP control", 0x4709, 0x4745); +//// dump_range(sensor, "MIPI control", 0x4800, 0x4837); +//// dump_range(sensor, "ISP frame control", 0x4901, 0x4902); +//// dump_range(sensor, "ISP top control", 0x5000, 0x5063); +//// dump_range(sensor, "AWB control", 0x5180, 0x51D0); +//// dump_range(sensor, "CIP control", 0x5300, 0x530F); +//// dump_range(sensor, "CMX control", 0x5380, 0x538B); +//// dump_range(sensor, "gamma control", 0x5480, 0x5490); +//// dump_range(sensor, "SDE control", 0x5580, 0x558C); +//// dump_range(sensor, "scale control", 0x5600, 0x5606); +//// dump_range(sensor, "AVG control", 0x5680, 0x56A2); +//// dump_range(sensor, "LENC control", 0x5800, 0x5849); +//// dump_range(sensor, "AFC control", 0x6000, 0x603F); +//} + +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_LOGI(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 pre_div, bool root_2x, int pclk_root_div, bool pclk_manual, int pclk_div) +{ + const float pll_pre_div2x_map[] = { 1, 1, 2, 3, 4, 1.5, 6, 2.5, 8}; + const int pll_pclk_root_div_map[] = { 1, 2, 4, 8 }; + + if(!pll_sys_div) { + pll_sys_div = 1; + } + + float pll_pre_div = pll_pre_div2x_map[pre_div]; + unsigned int root_2x_div = root_2x?2:1; + unsigned int pll_pclk_root_div = pll_pclk_root_div_map[pclk_root_div]; + + unsigned int REFIN = xclk / pll_pre_div; + + unsigned int VCO = REFIN * pll_multiplier / root_2x_div; + + unsigned int PLL_CLK = pll_bypass?(xclk):(VCO / pll_sys_div * 2 / 5);//5 here is 10bit mode / 2, for 8bit it should be 4 (reg 0x3034) + + unsigned int PCLK = PLL_CLK / pll_pclk_root_div / ((pclk_manual && pclk_div)?pclk_div:2); + + unsigned int SYSCLK = PLL_CLK / 4; + + ESP_LOGD(TAG, "Calculated XVCLK: %d Hz, REFIN: %u Hz, VCO: %u Hz, PLL_CLK: %u Hz, SYSCLK: %u Hz, PCLK: %u Hz", xclk, REFIN, VCO, PLL_CLK, 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 pclk_root_div, bool pclk_manual, uint8_t pclk_div){ + int ret = 0; + if(multiplier > 252 || multiplier < 4 || sys_div > 15 || pre_div > 8 || pclk_div > 31 || pclk_root_div > 3){ + ESP_LOGE(TAG, "Invalid arguments"); + return -1; + } + if(multiplier > 127){ + multiplier &= 0xFE;//only even integers above 127 + } + + calc_sysclk(sensor->xclk_freq_hz, bypass, multiplier, sys_div, pre_div, root_2x, pclk_root_div, pclk_manual, pclk_div); + + ret = write_reg(sensor->slv_addr, 0x3039, bypass?0x80:0x00); + if (ret == 0) { + ret = write_reg(sensor->slv_addr, 0x3034, 0x1A);//10bit mode + } + if (ret == 0) { + ret = write_reg(sensor->slv_addr, 0x3035, 0x01 | ((sys_div & 0x0f) << 4)); + } + if (ret == 0) { + ret = write_reg(sensor->slv_addr, 0x3036, multiplier & 0xff); + } + if (ret == 0) { + ret = write_reg(sensor->slv_addr, 0x3037, (pre_div & 0xf) | (root_2x?0x10:0x00)); + } + if (ret == 0) { + ret = write_reg(sensor->slv_addr, 0x3108, (pclk_root_div & 0x3) << 4 | 0x06); + } + if (ret == 0) { + ret = write_reg(sensor->slv_addr, 0x3824, pclk_div & 0x1f); + } + if (ret == 0) { + ret = write_reg(sensor->slv_addr, 0x460C, pclk_manual?0x22:0x20); + } + if (ret == 0) { + ret = write_reg(sensor->slv_addr, 0x3103, 0x13);// system clock from pll, bit[1] + } + if(ret){ + ESP_LOGE(TAG, "set_sensor_pll FAILED!"); + } + return ret; +} + +static int set_ae_level(sensor_t *sensor, int level); + +static int reset(sensor_t *sensor) +{ + //dump_regs(sensor); + vTaskDelay(100 / portTICK_PERIOD_MS); + int ret = 0; + // Software Reset: clear all registers and reset them to their default values + ret = write_reg(sensor->slv_addr, SYSTEM_CTROL0, 0x82); + if(ret){ + ESP_LOGE(TAG, "Software Reset FAILED!"); + return ret; + } + vTaskDelay(100 / portTICK_PERIOD_MS); + ret = write_regs(sensor->slv_addr, sensor_default_regs); + if (ret == 0) { + ESP_LOGD(TAG, "Camera defaults loaded"); + vTaskDelay(100 / portTICK_PERIOD_MS); + //write_regs(sensor->slv_addr, sensor_regs_awb0); + //write_regs(sensor->slv_addr, sensor_regs_gamma1); + } + 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; + + // compression + if (sensor->pixformat == PIXFORMAT_JPEG) { + reg21 |= 0x20; + } + + // binning + if (!sensor->status.binning) { + reg20 |= 0x40; + } else { + reg20 |= 0x01; + reg21 |= 0x01; + reg4514_test |= 4; + } + + // V-Flip + if (sensor->status.vflip) { + reg20 |= 0x06; + reg4514_test |= 1; + } + + // H-Mirror + if (sensor->status.hmirror) { + reg21 |= 0x06; + reg4514_test |= 2; + } + + switch (reg4514_test) { + //no binning + case 0: reg4514 = 0x88; break;//normal + case 1: reg4514 = 0x00; break;//v-flip + case 2: reg4514 = 0xbb; break;//h-mirror + case 3: reg4514 = 0x00; break;//v-flip+h-mirror + //binning + case 4: reg4514 = 0xaa; break;//normal + case 5: reg4514 = 0xbb; break;//v-flip + case 6: reg4514 = 0xbb; break;//h-mirror + case 7: reg4514 = 0xaa; break;//v-flip+h-mirror + } + + if(write_reg(sensor->slv_addr, TIMING_TC_REG20, reg20) + || write_reg(sensor->slv_addr, TIMING_TC_REG21, reg21) + || write_reg(sensor->slv_addr, 0x4514, reg4514)){ + ESP_LOGE(TAG, "Setting Image Options Failed"); + return -1; + } + + if (!sensor->status.binning) { + ret = write_reg(sensor->slv_addr, 0x4520, 0x10) + || write_reg(sensor->slv_addr, X_INCREMENT, 0x11)//odd:1, even: 1 + || write_reg(sensor->slv_addr, Y_INCREMENT, 0x11);//odd:1, even: 1 + } else { + ret = write_reg(sensor->slv_addr, 0x4520, 0x0b) + || write_reg(sensor->slv_addr, X_INCREMENT, 0x31)//odd:3, even: 1 + || write_reg(sensor->slv_addr, Y_INCREMENT, 0x31);//odd:3, even: 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; + framesize_t old_framesize = sensor->status.framesize; + sensor->status.framesize = framesize; + + if(framesize > FRAMESIZE_QSXGA){ + ESP_LOGE(TAG, "Invalid framesize: %u", framesize); + return -1; + } + uint16_t w = resolution[framesize].width; + uint16_t h = resolution[framesize].height; + aspect_ratio_t ratio = resolution[framesize].aspect_ratio; + ratio_settings_t settings = ratio_table[ratio]; + + sensor->status.binning = (w <= (settings.max_width / 2) && h <= (settings.max_height / 2)); + sensor->status.scale = !((w == settings.max_width && h == settings.max_height) + || (w == (settings.max_width / 2) && h == (settings.max_height / 2))); + + ret = write_addr_reg(sensor->slv_addr, X_ADDR_ST_H, settings.start_x, settings.start_y) + || write_addr_reg(sensor->slv_addr, X_ADDR_END_H, settings.end_x, settings.end_y) + || write_addr_reg(sensor->slv_addr, X_OUTPUT_SIZE_H, w, h); + + if (ret) { + goto fail; + } + + if (!sensor->status.binning) { + ret = write_addr_reg(sensor->slv_addr, X_TOTAL_SIZE_H, settings.total_x, settings.total_y) + || write_addr_reg(sensor->slv_addr, X_OFFSET_H, settings.offset_x, settings.offset_y); + } else { + if (w > 920) { + ret = write_addr_reg(sensor->slv_addr, X_TOTAL_SIZE_H, settings.total_x - 200, settings.total_y / 2); + } else { + ret = write_addr_reg(sensor->slv_addr, X_TOTAL_SIZE_H, 2060, settings.total_y / 2); + } + if (ret == 0) { + ret = write_addr_reg(sensor->slv_addr, X_OFFSET_H, settings.offset_x / 2, settings.offset_y / 2); + } + } + + if (ret == 0) { + ret = write_reg_bits(sensor->slv_addr, ISP_CONTROL_01, 0x20, sensor->status.scale); + } + + if (ret == 0) { + ret = set_image_options(sensor); + } + + if (ret) { + goto fail; + } + + if (sensor->pixformat == PIXFORMAT_JPEG) { + //10MHz PCLK + uint8_t sys_mul = 200; + if(framesize < FRAMESIZE_QVGA){ + sys_mul = 160; + } else if(framesize < FRAMESIZE_XGA){ + sys_mul = 180; + } + ret = set_pll(sensor, false, sys_mul, 4, 2, false, 2, true, 4); + } else { + ret = set_pll(sensor, false, 10, 1, 1, false, 1, true, 4); + } + + if (ret == 0) { + ESP_LOGD(TAG, "Set framesize to: %ux%u", w, h); + } + return ret; + +fail: + sensor->status.framesize = old_framesize; + ESP_LOGE(TAG, "Setting framesize to: %ux%u failed", w, h); + return ret; +} + +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, AEC_PK_MANUAL, AEC_PK_MANUAL_AGC_MANUALEN, !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; + ret = write_reg_bits(sensor->slv_addr, AEC_PK_MANUAL, AEC_PK_MANUAL_AEC_MANUALEN, !enable); + 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; + ret = write_reg_bits(sensor->slv_addr, ISP_CONTROL_01, 0x01, enable); + 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; + ret = write_reg_bits(sensor->slv_addr, 0x5183, 0x80, !enable); + 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; + ret = write_reg_bits(sensor->slv_addr, 0x3a00, 0x04, enable); + 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; + ret = write_reg_bits(sensor->slv_addr, 0x5000, 0x04, enable); + 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; + ret = write_reg_bits(sensor->slv_addr, 0x5000, 0x02, enable); + 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; + ret = write_reg_bits(sensor->slv_addr, 0x5000, 0x20, enable); + 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; + ret = write_reg_bits(sensor->slv_addr, 0x5000, 0x80, enable); + 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) +{ + int ra = read_reg(sensor->slv_addr, 0x350a); + if (ra < 0) { + return 0; + } + int rb = read_reg(sensor->slv_addr, 0x350b); + if (rb < 0) { + return 0; + } + int res = (rb & 0xF0) >> 4 | (ra & 0x03) << 4; + if (rb & 0x0F) { + res += 1; + } + return res; +} + +//real gain +static int set_agc_gain(sensor_t *sensor, int gain) +{ + int ret = 0; + if(gain < 0) { + gain = 0; + } else if(gain > 64) { + gain = 64; + } + + //gain value is 6.4 bits float + //in order to use the max range, we deduct 1/16 + int gainv = gain << 4; + if(gainv){ + gainv -= 1; + } + + ret = write_reg(sensor->slv_addr, 0x350a, gainv >> 8) || write_reg(sensor->slv_addr, 0x350b, gainv & 0xff); + if (ret == 0) { + ESP_LOGD(TAG, "Set agc_gain to: %d", gain); + sensor->status.agc_gain = gain; + } + return ret; +} + +static int get_aec_value(sensor_t *sensor) +{ + int ra = read_reg(sensor->slv_addr, 0x3500); + if (ra < 0) { + return 0; + } + int rb = read_reg(sensor->slv_addr, 0x3501); + if (rb < 0) { + return 0; + } + int rc = read_reg(sensor->slv_addr, 0x3502); + if (rc < 0) { + return 0; + } + int res = (ra & 0x0F) << 12 | (rb & 0xFF) << 4 | (rc & 0xF0) >> 4; + return res; +} + +static int set_aec_value(sensor_t *sensor, int value) +{ + int ret = 0, max_val = 0; + max_val = read_reg16(sensor->slv_addr, 0x380e); + if (max_val < 0) { + ESP_LOGE(TAG, "Could not read max aec_value"); + return -1; + } + if (value > max_val) { + value =max_val; + } + + ret = write_reg(sensor->slv_addr, 0x3500, (value >> 12) & 0x0F) + || write_reg(sensor->slv_addr, 0x3501, (value >> 4) & 0xFF) + || write_reg(sensor->slv_addr, 0x3502, (value << 4) & 0xF0); + + if (ret == 0) { + ESP_LOGD(TAG, "Set aec_value to: %d / %d", value, max_val); + sensor->status.aec_value = value; + } + return ret; +} + +static int set_ae_level(sensor_t *sensor, int level) +{ + int ret = 0; + if (level < -5 || level > 5) { + return -1; + } + //good targets are between 5 and 115 + int target_level = ((level + 5) * 10) + 5; + + int level_high, level_low; + int fast_high, fast_low; + + level_low = target_level * 23 / 25; //0.92 (0.46) + level_high = target_level * 27 / 25; //1.08 (2.08) + + fast_low = level_low >> 1; + fast_high = level_high << 1; + + if(fast_high>255) { + fast_high = 255; + } + + ret = write_reg(sensor->slv_addr, 0x3a0f, level_high) + || write_reg(sensor->slv_addr, 0x3a10, level_low) + || write_reg(sensor->slv_addr, 0x3a1b, level_high) + || write_reg(sensor->slv_addr, 0x3a1e, level_low) + || write_reg(sensor->slv_addr, 0x3a11, fast_high) + || write_reg(sensor->slv_addr, 0x3a1f, fast_low); + + if (ret == 0) { + ESP_LOGD(TAG, "Set ae_level to: %d", level); + sensor->status.ae_level = level; + } + return ret; +} + +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, 0x3406, (mode != 0)); + if (ret) { + return ret; + } + switch (mode) { + case 1://Sunny + ret = write_reg16(sensor->slv_addr, 0x3400, 0x5e0) //AWB R GAIN + || write_reg16(sensor->slv_addr, 0x3402, 0x410) //AWB G GAIN + || write_reg16(sensor->slv_addr, 0x3404, 0x540);//AWB B GAIN + break; + case 2://Cloudy + ret = write_reg16(sensor->slv_addr, 0x3400, 0x650) //AWB R GAIN + || write_reg16(sensor->slv_addr, 0x3402, 0x410) //AWB G GAIN + || write_reg16(sensor->slv_addr, 0x3404, 0x4f0);//AWB B GAIN + break; + case 3://Office + ret = write_reg16(sensor->slv_addr, 0x3400, 0x520) //AWB R GAIN + || write_reg16(sensor->slv_addr, 0x3402, 0x410) //AWB G GAIN + || write_reg16(sensor->slv_addr, 0x3404, 0x660);//AWB B GAIN + break; + case 4://HOME + ret = write_reg16(sensor->slv_addr, 0x3400, 0x420) //AWB R GAIN + || write_reg16(sensor->slv_addr, 0x3402, 0x3f0) //AWB G GAIN + || write_reg16(sensor->slv_addr, 0x3404, 0x710);//AWB B GAIN + 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, 0x5580, regs[0]) + || write_reg(sensor->slv_addr, 0x5583, regs[1]) + || write_reg(sensor->slv_addr, 0x5584, regs[2]) + || write_reg(sensor->slv_addr, 0x5003, 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 = 0x30; + break; + case 2: + value = 0x20; + break; + case 1: + value = 0x10; + break; + case -1: + value = 0x10; + negative = true; + break; + case -2: + value = 0x20; + negative = true; + break; + case -3: + value = 0x30; + negative = true; + break; + default: // 0 + break; + } + + ret = write_reg(sensor->slv_addr, 0x5587, value); + if (ret == 0) { + ret = write_reg_bits(sensor->slv_addr, 0x5588, 0x08, negative); + } + + 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; + if(level > 3 || level < -3) { + return -1; + } + ret = write_reg(sensor->slv_addr, 0x5586, (level + 4) << 3); + + 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]; + for(int i=0; i<11; i++) { + ret = write_reg(sensor->slv_addr, 0x5381 + i, regs[i]); + if (ret) { + break; + } + } + + 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) +{ + int ret = 0, l = (int)level; + + ret = write_reg(sensor->slv_addr, 0x3A18, (l >> 8) & 3) + || write_reg(sensor->slv_addr, 0x3A19, l & 0xFF); + + if (ret == 0) { + ESP_LOGD(TAG, "Set gainceiling to: %d", l); + sensor->status.gainceiling = l; + } + return ret; +} + +static int get_denoise(sensor_t *sensor) +{ + if (!check_reg_mask(sensor->slv_addr, 0x5308, 0x10)) { + return 0; + } + return (read_reg(sensor->slv_addr, 0x5306) / 4) + 1; +} + +static int set_denoise(sensor_t *sensor, int level) +{ + int ret = 0; + if (level < 0 || level > 8) { + return -1; + } + + ret = write_reg_bits(sensor->slv_addr, 0x5308, 0x10, level > 0); + if (ret == 0 && level > 0) { + ret = write_reg(sensor->slv_addr, 0x5306, (level - 1) * 4); + } + + if (ret == 0) { + ESP_LOGD(TAG, "Set denoise to: %d", level); + sensor->status.denoise = level; + } + return ret; +} + +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) + || write_reg_bits(sensor->slv_addr, ISP_CONTROL_01, 0x20, scale); + 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) +{ + int ret = 0; + ret = set_pll(sensor, bypass > 0, multiplier, sys_div, pre_div, root_2x > 0, seld5, pclk_manual > 0, pclk_div); + return ret; +} + +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; +} + +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, 0x5303) / 8) - 3; + sensor->status.denoise = get_denoise(sensor); + sensor->status.ae_level = 0; + sensor->status.gainceiling = read_reg16(sensor->slv_addr, 0x3A18) & 0x3FF; + sensor->status.awb = check_reg_mask(sensor->slv_addr, ISP_CONTROL_01, 0x01); + 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, 0x3406, 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, 0x3a00, 0x04); + return 0; +} + +int ov5640_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/ov5640.h b/esp32-cam/ov5640.h new file mode 100644 index 0000000..7b572ad --- /dev/null +++ b/esp32-cam/ov5640.h @@ -0,0 +1,9 @@ + +#ifndef __OV5640_H__ +#define __OV5640_H__ + +#include "sensor.h" + +int ov5640_init(sensor_t *sensor); + +#endif // __OV5640_H__ diff --git a/esp32-cam/ov5640_regs.h b/esp32-cam/ov5640_regs.h new file mode 100644 index 0000000..c28d80f --- /dev/null +++ b/esp32-cam/ov5640_regs.h @@ -0,0 +1,213 @@ +/* + * OV5640 register definitions. + */ +#ifndef __OV5640_REG_REGS_H__ +#define __OV5640_REG_REGS_H__ + +/* system control registers */ +#define SYSTEM_CTROL0 0x3008 // Bit[7]: Software reset + // Bit[6]: Software power down + // Bit[5]: Reserved + // Bit[4]: SRB clock SYNC enable + // Bit[3]: Isolation suspend select + // Bit[2:0]: Not used + +#define DRIVE_CAPABILITY 0x302c // Bit[7:6]: + // 00: 1x + // 01: 2x + // 10: 3x + // 11: 4x + +#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 + +/* AEC/AGC control functions */ +#define AEC_PK_MANUAL 0x3503 // AEC Manual Mode Control + // Bit[7:6]: Reserved + // Bit[5]: Gain delay option + // Valid when 0x3503[4]=1’b0 + // 0: Delay one frame latch + // 1: One frame latch + // Bit[4:2]: Reserved + // Bit[1]: AGC manual + // 0: Auto enable + // 1: Manual enable + // Bit[0]: AEC manual + // 0: Auto enable + // 1: Manual enable + +//gain = {0x350A[1:0], 0x350B[7:0]} / 16 + + +#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)) + +/* mirror and flip registers */ +#define TIMING_TC_REG20 0x3820 // Timing Control Register + // Bit[2:1]: Vertical flip enable + // 00: Normal + // 11: Vertical flip + // Bit[0]: Vertical binning enable +#define TIMING_TC_REG21 0x3821 // Timing Control Register + // Bit[5]: Compression Enable + // Bit[2:1]: Horizontal mirror enable + // 00: Normal + // 11: Horizontal mirror + // Bit[0]: Horizontal binning enable + +#define PCLK_RATIO 0x3824 // Bit[4:0]: PCLK ratio manual + +/* 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 + +/* format control registers */ +#define FORMAT_CTRL00 0x4300 + +#define CLOCK_POL_CONTROL 0x4740// Bit[5]: PCLK polarity 0: active low + // 1: active high + // Bit[3]: Gate PCLK under VSYNC + // Bit[2]: Gate PCLK under HREF + // Bit[1]: HREF polarity + // 0: active low + // 1: active high + // Bit[0] VSYNC polarity + // 0: active low + // 1: active high + +#define ISP_CONTROL_01 0x5001 // Bit[5]: Scale enable + // 0: Disable + // 1: Enable + +/* 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 + +/* ISP top control registers */ +#define PRE_ISP_TEST_SETTING_1 0x503D // Bit[7]: Test enable + // 0: Test disable + // 1: Color bar enable + // Bit[6]: Rolling + // Bit[5]: Transparent + // Bit[4]: Square black and white + // Bit[3:2]: Color bar style + // 00: Standard 8 color bar + // 01: Gradual change at vertical mode 1 + // 10: Gradual change at horizontal + // 11: Gradual change at vertical mode 2 + // Bit[1:0]: Test select + // 00: Color bar + // 01: Random data + // 10: Square data + // 11: Black image + +//exposure = {0x3500[3:0], 0x3501[7:0], 0x3502[7:0]} / 16 × tROW + +#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 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 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 0x4407 // 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 0xC0 /* 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 0x06 /* Vertical flip enable */ +#define TIMING_TC_REG21_HMIRROR 0x06 /* Horizontal mirror enable */ + +#endif // __OV3660_REG_REGS_H__ diff --git a/esp32-cam/ov5640_settings.h b/esp32-cam/ov5640_settings.h new file mode 100644 index 0000000..fec7d67 --- /dev/null +++ b/esp32-cam/ov5640_settings.h @@ -0,0 +1,334 @@ +#ifndef _OV5640_SETTINGS_H_ +#define _OV5640_SETTINGS_H_ + +#include +#include +#include "esp_attr.h" +#include "ov5640_regs.h" + +static const ratio_settings_t ratio_table[] = { + // mw, mh, sx, sy, ex, ey, ox, oy, tx, ty + { 2560, 1920, 0, 0, 2623, 1951, 32, 16, 2844, 1968 }, //4x3 + { 2560, 1704, 0, 110, 2623, 1843, 32, 16, 2844, 1752 }, //3x2 + { 2560, 1600, 0, 160, 2623, 1791, 32, 16, 2844, 1648 }, //16x10 + { 2560, 1536, 0, 192, 2623, 1759, 32, 16, 2844, 1584 }, //5x3 + { 2560, 1440, 0, 240, 2623, 1711, 32, 16, 2844, 1488 }, //16x9 + { 2560, 1080, 0, 420, 2623, 1531, 32, 16, 2844, 1128 }, //21x9 + { 2400, 1920, 80, 0, 2543, 1951, 32, 16, 2684, 1968 }, //5x4 + { 1920, 1920, 320, 0, 2543, 1951, 32, 16, 2684, 1968 }, //1x1 + { 1088, 1920, 736, 0, 1887, 1951, 32, 16, 1884, 1968 } //9x16 +}; + +#define REG_DLY 0xffff +#define REGLIST_TAIL 0x0000 + +static const DRAM_ATTR uint16_t sensor_default_regs[][2] = { + {SYSTEM_CTROL0, 0x82}, // software reset + {REG_DLY, 10}, // delay 10ms + {SYSTEM_CTROL0, 0x42}, // power down + + //enable pll + {0x3103, 0x13}, + + //io direction + {0x3017, 0xff}, + {0x3018, 0xff}, + + {DRIVE_CAPABILITY, 0xc3}, + {CLOCK_POL_CONTROL, 0x21}, + + {0x4713, 0x02},//jpg mode select + + {ISP_CONTROL_01, 0x83}, // turn color matrix, awb and SDE + + //sys reset + {0x3000, 0x00}, + {0x3002, 0x1c}, + + //clock enable + {0x3004, 0xff}, + {0x3006, 0xc3}, + + //isp control + {0x5000, 0xa7}, + {ISP_CONTROL_01, 0xa3},//+scaling? + {0x5003, 0x08},//special_effect + + //unknown + {0x370c, 0x02},//!!IMPORTANT + {0x3634, 0x40},//!!IMPORTANT + + //AEC/AGC + {0x3a02, 0x03}, + {0x3a03, 0xd8}, + {0x3a08, 0x01}, + {0x3a09, 0x27}, + {0x3a0a, 0x00}, + {0x3a0b, 0xf6}, + {0x3a0d, 0x04}, + {0x3a0e, 0x03}, + {0x3a0f, 0x30},//ae_level + {0x3a10, 0x28},//ae_level + {0x3a11, 0x60},//ae_level + {0x3a13, 0x43}, + {0x3a14, 0x03}, + {0x3a15, 0xd8}, + {0x3a18, 0x00},//gainceiling + {0x3a19, 0xf8},//gainceiling + {0x3a1b, 0x30},//ae_level + {0x3a1e, 0x26},//ae_level + {0x3a1f, 0x14},//ae_level + + //vcm debug + {0x3600, 0x08}, + {0x3601, 0x33}, + + //50/60Hz + {0x3c01, 0xa4}, + {0x3c04, 0x28}, + {0x3c05, 0x98}, + {0x3c06, 0x00}, + {0x3c07, 0x08}, + {0x3c08, 0x00}, + {0x3c09, 0x1c}, + {0x3c0a, 0x9c}, + {0x3c0b, 0x40}, + + {0x460c, 0x22},//disable jpeg footer + + //BLC + {0x4001, 0x02}, + {0x4004, 0x02}, + + //AWB + {0x5180, 0xff}, + {0x5181, 0xf2}, + {0x5182, 0x00}, + {0x5183, 0x14}, + {0x5184, 0x25}, + {0x5185, 0x24}, + {0x5186, 0x09}, + {0x5187, 0x09}, + {0x5188, 0x09}, + {0x5189, 0x75}, + {0x518a, 0x54}, + {0x518b, 0xe0}, + {0x518c, 0xb2}, + {0x518d, 0x42}, + {0x518e, 0x3d}, + {0x518f, 0x56}, + {0x5190, 0x46}, + {0x5191, 0xf8}, + {0x5192, 0x04}, + {0x5193, 0x70}, + {0x5194, 0xf0}, + {0x5195, 0xf0}, + {0x5196, 0x03}, + {0x5197, 0x01}, + {0x5198, 0x04}, + {0x5199, 0x12}, + {0x519a, 0x04}, + {0x519b, 0x00}, + {0x519c, 0x06}, + {0x519d, 0x82}, + {0x519e, 0x38}, + + //color matrix (Saturation) + {0x5381, 0x1e}, + {0x5382, 0x5b}, + {0x5383, 0x08}, + {0x5384, 0x0a}, + {0x5385, 0x7e}, + {0x5386, 0x88}, + {0x5387, 0x7c}, + {0x5388, 0x6c}, + {0x5389, 0x10}, + {0x538a, 0x01}, + {0x538b, 0x98}, + + //CIP control (Sharpness) + {0x5300, 0x10},//sharpness + {0x5301, 0x10},//sharpness + {0x5302, 0x18},//sharpness + {0x5303, 0x19},//sharpness + {0x5304, 0x10}, + {0x5305, 0x10}, + {0x5306, 0x08},//denoise + {0x5307, 0x16}, + {0x5308, 0x40}, + {0x5309, 0x10},//sharpness + {0x530a, 0x10},//sharpness + {0x530b, 0x04},//sharpness + {0x530c, 0x06},//sharpness + + //GAMMA + {0x5480, 0x01}, + {0x5481, 0x00}, + {0x5482, 0x1e}, + {0x5483, 0x3b}, + {0x5484, 0x58}, + {0x5485, 0x66}, + {0x5486, 0x71}, + {0x5487, 0x7d}, + {0x5488, 0x83}, + {0x5489, 0x8f}, + {0x548a, 0x98}, + {0x548b, 0xa6}, + {0x548c, 0xb8}, + {0x548d, 0xca}, + {0x548e, 0xd7}, + {0x548f, 0xe3}, + {0x5490, 0x1d}, + + //Special Digital Effects (SDE) (UV adjust) + {0x5580, 0x06},//enable brightness and contrast + {0x5583, 0x40},//special_effect + {0x5584, 0x10},//special_effect + {0x5586, 0x20},//contrast + {0x5587, 0x00},//brightness + {0x5588, 0x00},//brightness + {0x5589, 0x10}, + {0x558a, 0x00}, + {0x558b, 0xf8}, + {0x501d, 0x40},// enable manual offset of contrast + + //power on + {0x3008, 0x02}, + + //50Hz + {0x3c00, 0x04}, + + {REG_DLY, 300}, + {REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_fmt_jpeg[][2] = { + {FORMAT_CTRL, 0x00}, // YUV422 + {FORMAT_CTRL00, 0x30}, // YUYV + {0x3002, 0x00},//0x1c to 0x00 !!! + {0x3006, 0xff},//0xc3 to 0xff !!! + {0x471c, 0x50},//0xd0 to 0x50 !!! + {REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_fmt_raw[][2] = { + {FORMAT_CTRL, 0x03}, // RAW (DPC) + {FORMAT_CTRL00, 0x00}, // RAW + {REGLIST_TAIL, 0x00} +}; + +static const DRAM_ATTR uint16_t sensor_fmt_grayscale[][2] = { + {FORMAT_CTRL, 0x00}, // YUV422 + {FORMAT_CTRL00, 0x10}, // Y8 + {REGLIST_TAIL, 0x00} +}; + +static const DRAM_ATTR uint16_t sensor_fmt_yuv422[][2] = { + {FORMAT_CTRL, 0x00}, // YUV422 + {FORMAT_CTRL00, 0x30}, // YUYV + {REGLIST_TAIL, 0x00} +}; + +static const DRAM_ATTR uint16_t sensor_fmt_rgb565[][2] = { + {FORMAT_CTRL, 0x01}, // RGB + {FORMAT_CTRL00, 0x61}, // RGB565 (BGR) + {REGLIST_TAIL, 0x00} +}; + +static const DRAM_ATTR uint8_t sensor_saturation_levels[9][11] = { + {0x1d, 0x60, 0x03, 0x07, 0x48, 0x4f, 0x4b, 0x40, 0x0b, 0x01, 0x98},//-4 + {0x1d, 0x60, 0x03, 0x08, 0x54, 0x5c, 0x58, 0x4b, 0x0d, 0x01, 0x98},//-3 + {0x1d, 0x60, 0x03, 0x0a, 0x60, 0x6a, 0x64, 0x56, 0x0e, 0x01, 0x98},//-2 + {0x1d, 0x60, 0x03, 0x0b, 0x6c, 0x77, 0x70, 0x60, 0x10, 0x01, 0x98},//-1 + {0x1d, 0x60, 0x03, 0x0c, 0x78, 0x84, 0x7d, 0x6b, 0x12, 0x01, 0x98},//0 + {0x1d, 0x60, 0x03, 0x0d, 0x84, 0x91, 0x8a, 0x76, 0x14, 0x01, 0x98},//+1 + {0x1d, 0x60, 0x03, 0x0e, 0x90, 0x9e, 0x96, 0x80, 0x16, 0x01, 0x98},//+2 + {0x1d, 0x60, 0x03, 0x10, 0x9c, 0xac, 0xa2, 0x8b, 0x17, 0x01, 0x98},//+3 + {0x1d, 0x60, 0x03, 0x11, 0xa8, 0xb9, 0xaf, 0x96, 0x19, 0x01, 0x98},//+4 +}; + +static const DRAM_ATTR uint8_t sensor_special_effects[7][4] = { + {0x06, 0x40, 0x2c, 0x08},//Normal + {0x46, 0x40, 0x28, 0x08},//Negative + {0x1e, 0x80, 0x80, 0x08},//Grayscale + {0x1e, 0x80, 0xc0, 0x08},//Red Tint + {0x1e, 0x60, 0x60, 0x08},//Green Tint + {0x1e, 0xa0, 0x40, 0x08},//Blue Tint + {0x1e, 0x40, 0xa0, 0x08},//Sepia +}; + +static const DRAM_ATTR uint16_t sensor_regs_gamma0[][2] = { + {0x5480, 0x01}, + {0x5481, 0x08}, + {0x5482, 0x14}, + {0x5483, 0x28}, + {0x5484, 0x51}, + {0x5485, 0x65}, + {0x5486, 0x71}, + {0x5487, 0x7d}, + {0x5488, 0x87}, + {0x5489, 0x91}, + {0x548a, 0x9a}, + {0x548b, 0xaa}, + {0x548c, 0xb8}, + {0x548d, 0xcd}, + {0x548e, 0xdd}, + {0x548f, 0xea}, + {0x5490, 0x1d} +}; + +static const DRAM_ATTR uint16_t sensor_regs_gamma1[][2] = { + {0x5480, 0x1}, + {0x5481, 0x0}, + {0x5482, 0x1e}, + {0x5483, 0x3b}, + {0x5484, 0x58}, + {0x5485, 0x66}, + {0x5486, 0x71}, + {0x5487, 0x7d}, + {0x5488, 0x83}, + {0x5489, 0x8f}, + {0x548a, 0x98}, + {0x548b, 0xa6}, + {0x548c, 0xb8}, + {0x548d, 0xca}, + {0x548e, 0xd7}, + {0x548f, 0xe3}, + {0x5490, 0x1d} +}; + +static const DRAM_ATTR uint16_t sensor_regs_awb0[][2] = { + {0x5180, 0xff}, + {0x5181, 0xf2}, + {0x5182, 0x00}, + {0x5183, 0x14}, + {0x5184, 0x25}, + {0x5185, 0x24}, + {0x5186, 0x09}, + {0x5187, 0x09}, + {0x5188, 0x09}, + {0x5189, 0x75}, + {0x518a, 0x54}, + {0x518b, 0xe0}, + {0x518c, 0xb2}, + {0x518d, 0x42}, + {0x518e, 0x3d}, + {0x518f, 0x56}, + {0x5190, 0x46}, + {0x5191, 0xf8}, + {0x5192, 0x04}, + {0x5193, 0x70}, + {0x5194, 0xf0}, + {0x5195, 0xf0}, + {0x5196, 0x03}, + {0x5197, 0x01}, + {0x5198, 0x04}, + {0x5199, 0x12}, + {0x519a, 0x04}, + {0x519b, 0x00}, + {0x519c, 0x06}, + {0x519d, 0x82}, + {0x519e, 0x38} +}; + +#endif diff --git a/esp32-cam/ov7725.c b/esp32-cam/ov7725.c new file mode 100644 index 0000000..b4e6710 --- /dev/null +++ b/esp32-cam/ov7725.c @@ -0,0 +1,382 @@ +/* + * 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. + * + * OV7725 driver. + * + */ +#include +#include +#include +#include +#include "sccb.h" +#include "ov7725.h" +#include "ov7725_regs.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 = "ov7725"; +#endif + + +static const uint8_t default_regs[][2] = { + {COM3, COM3_SWAP_YUV}, + {COM7, COM7_RES_QVGA | COM7_FMT_YUV}, + + {COM4, 0x01 | 0x00}, /* bypass PLL (0x00:off, 0x40:4x, 0x80:6x, 0xC0:8x) */ + {CLKRC, 0x80 | 0x03}, /* Res/Bypass pre-scalar (0x40:bypass, 0x00-0x3F:prescaler PCLK=XCLK/(prescaler + 1)/2 ) */ + + // QVGA Window Size + {HSTART, 0x3F}, + {HSIZE, 0x50}, + {VSTART, 0x03}, + {VSIZE, 0x78}, + {HREF, 0x00}, + + // Scale down to QVGA Resolution + {HOUTSIZE, 0x50}, + {VOUTSIZE, 0x78}, + {EXHCH, 0x00}, + + {COM12, 0x03}, + {TGT_B, 0x7F}, + {FIXGAIN, 0x09}, + {AWB_CTRL0, 0xE0}, + {DSP_CTRL1, 0xFF}, + + {DSP_CTRL2, DSP_CTRL2_VDCW_EN | DSP_CTRL2_HDCW_EN | DSP_CTRL2_HZOOM_EN | DSP_CTRL2_VZOOM_EN}, + + {DSP_CTRL3, 0x00}, + {DSP_CTRL4, 0x00}, + {DSPAUTO, 0xFF}, + + {COM8, 0xF0}, + {COM6, 0xC5}, + {COM9, 0x11}, + {COM10, COM10_VSYNC_NEG | COM10_PCLK_MASK}, //Invert VSYNC and MASK PCLK + {BDBASE, 0x7F}, + {DBSTEP, 0x03}, + {AEW, 0x96}, + {AEB, 0x64}, + {VPT, 0xA1}, + {EXHCL, 0x00}, + {AWB_CTRL3, 0xAA}, + {COM8, 0xFF}, + + //Gamma + {GAM1, 0x0C}, + {GAM2, 0x16}, + {GAM3, 0x2A}, + {GAM4, 0x4E}, + {GAM5, 0x61}, + {GAM6, 0x6F}, + {GAM7, 0x7B}, + {GAM8, 0x86}, + {GAM9, 0x8E}, + {GAM10, 0x97}, + {GAM11, 0xA4}, + {GAM12, 0xAF}, + {GAM13, 0xC5}, + {GAM14, 0xD7}, + {GAM15, 0xE8}, + + {SLOP, 0x20}, + {EDGE1, 0x05}, + {EDGE2, 0x03}, + {EDGE3, 0x00}, + {DNSOFF, 0x01}, + + {MTX1, 0xB0}, + {MTX2, 0x9D}, + {MTX3, 0x13}, + {MTX4, 0x16}, + {MTX5, 0x7B}, + {MTX6, 0x91}, + {MTX_CTRL, 0x1E}, + + {BRIGHTNESS, 0x08}, + {CONTRAST, 0x30}, + {UVADJ0, 0x81}, + {SDE, (SDE_CONT_BRIGHT_EN | SDE_SATURATION_EN)}, + + // For 30 fps/60Hz + {DM_LNL, 0x00}, + {DM_LNH, 0x00}, + {BDBASE, 0x7F}, + {DBSTEP, 0x03}, + + // Lens Correction, should be tuned with real camera module + {LC_RADI, 0x10}, + {LC_COEF, 0x10}, + {LC_COEFB, 0x14}, + {LC_COEFR, 0x17}, + {LC_CTR, 0x05}, + {COM5, 0xF5}, //0x65 + + {0x00, 0x00}, +}; + + +static int reset(sensor_t *sensor) +{ + int i=0; + const uint8_t (*regs)[2]; + + // Reset all registers + SCCB_Write(sensor->slv_addr, COM7, COM7_RESET); + + // Delay 10 ms + vTaskDelay(10 / portTICK_PERIOD_MS); + + // Write default regsiters + for (i=0, regs = default_regs; regs[i][0]; i++) { + SCCB_Write(sensor->slv_addr, regs[i][0], regs[i][1]); + } + + // Delay + vTaskDelay(30 / portTICK_PERIOD_MS); + + return 0; +} + + +static int set_pixformat(sensor_t *sensor, pixformat_t pixformat) +{ + int ret=0; + sensor->pixformat = pixformat; + // Read register COM7 + uint8_t reg = SCCB_Read(sensor->slv_addr, COM7); + + switch (pixformat) { + case PIXFORMAT_RGB565: + reg = COM7_SET_RGB(reg, COM7_FMT_RGB565); + break; + case PIXFORMAT_YUV422: + case PIXFORMAT_GRAYSCALE: + reg = COM7_SET_FMT(reg, COM7_FMT_YUV); + break; + default: + return -1; + } + + // Write back register COM7 + ret = SCCB_Write(sensor->slv_addr, COM7, reg); + + // Delay + vTaskDelay(30 / portTICK_PERIOD_MS); + + return ret; +} + +static int set_framesize(sensor_t *sensor, framesize_t framesize) +{ + int ret=0; + uint16_t w = resolution[framesize].width; + uint16_t h = resolution[framesize].height; + uint8_t reg = SCCB_Read(sensor->slv_addr, COM7); + + sensor->status.framesize = framesize; + + // Write MSBs + ret |= SCCB_Write(sensor->slv_addr, HOUTSIZE, w>>2); + ret |= SCCB_Write(sensor->slv_addr, VOUTSIZE, h>>1); + + ret |= SCCB_Write(sensor->slv_addr, HSIZE, w>>2); + ret |= SCCB_Write(sensor->slv_addr, VSIZE, h>>1); + + // Write LSBs + ret |= SCCB_Write(sensor->slv_addr, HREF, ((w&0x3) | ((h&0x1) << 2))); + + if (framesize < FRAMESIZE_VGA) { + // Enable auto-scaling/zooming factors + ret |= SCCB_Write(sensor->slv_addr, DSPAUTO, 0xFF); + + ret |= SCCB_Write(sensor->slv_addr, HSTART, 0x3F); + ret |= SCCB_Write(sensor->slv_addr, VSTART, 0x03); + + ret |= SCCB_Write(sensor->slv_addr, COM7, reg | COM7_RES_QVGA); + + ret |= SCCB_Write(sensor->slv_addr, CLKRC, 0x80 | 0x01); + + } else { + // Disable auto-scaling/zooming factors + ret |= SCCB_Write(sensor->slv_addr, DSPAUTO, 0xF3); + + // Clear auto-scaling/zooming factors + ret |= SCCB_Write(sensor->slv_addr, SCAL0, 0x00); + ret |= SCCB_Write(sensor->slv_addr, SCAL1, 0x00); + ret |= SCCB_Write(sensor->slv_addr, SCAL2, 0x00); + + ret |= SCCB_Write(sensor->slv_addr, HSTART, 0x23); + ret |= SCCB_Write(sensor->slv_addr, VSTART, 0x07); + + ret |= SCCB_Write(sensor->slv_addr, COM7, reg & ~COM7_RES_QVGA); + + ret |= SCCB_Write(sensor->slv_addr, CLKRC, 0x80 | 0x03); + } + + // Delay + vTaskDelay(30 / portTICK_PERIOD_MS); + + return ret; +} + +static int set_colorbar(sensor_t *sensor, int enable) +{ + int ret=0; + uint8_t reg; + sensor->status.colorbar = enable; + + // Read reg COM3 + reg = SCCB_Read(sensor->slv_addr, COM3); + // Enable colorbar test pattern output + reg = COM3_SET_CBAR(reg, enable); + // Write back COM3 + ret |= SCCB_Write(sensor->slv_addr, COM3, reg); + + // Read reg DSP_CTRL3 + reg = SCCB_Read(sensor->slv_addr, DSP_CTRL3); + // Enable DSP colorbar output + reg = DSP_CTRL3_SET_CBAR(reg, enable); + // Write back DSP_CTRL3 + ret |= SCCB_Write(sensor->slv_addr, DSP_CTRL3, reg); + + return ret; +} + +static int set_whitebal(sensor_t *sensor, int enable) +{ + // Read register COM8 + uint8_t reg = SCCB_Read(sensor->slv_addr, COM8); + + sensor->status.awb = enable; + // 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) +{ + sensor->status.agc = 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) +{ + sensor->status.aec = 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) +{ + sensor->status.hmirror = enable; + // Read register COM3 + uint8_t reg = SCCB_Read(sensor->slv_addr, COM3); + + // Set mirror on/off + reg = COM3_SET_MIRROR(reg, enable); + + // Write back register COM3 + return SCCB_Write(sensor->slv_addr, COM3, reg); +} + +static int set_vflip(sensor_t *sensor, int enable) +{ + sensor->status.vflip = enable; + // Read register COM3 + uint8_t reg = SCCB_Read(sensor->slv_addr, COM3); + + // Set mirror on/off + reg = COM3_SET_FLIP(reg, enable); + + // Write back register COM3 + return SCCB_Write(sensor->slv_addr, COM3, reg); +} + +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; + 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 ov7725_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_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; + + + + + // 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, "OV7725 Attached"); + + return 0; +} diff --git a/esp32-cam/ov7725.h b/esp32-cam/ov7725.h new file mode 100644 index 0000000..f8c3516 --- /dev/null +++ b/esp32-cam/ov7725.h @@ -0,0 +1,14 @@ +/* + * 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. + * + * OV7725 driver. + * + */ +#ifndef __OV7725_H__ +#define __OV7725_H__ +#include "sensor.h" + +int ov7725_init(sensor_t *sensor); +#endif // __OV7725_H__ diff --git a/esp32-cam/ov7725_regs.h b/esp32-cam/ov7725_regs.h new file mode 100644 index 0000000..5cb233d --- /dev/null +++ b/esp32-cam/ov7725_regs.h @@ -0,0 +1,335 @@ +/* + * 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. + * + * OV2640 register definitions. + */ +#ifndef __REG_REGS_H__ +#define __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 GREEN 0x03 /* AWB – Green channel gain setting */ +#define BAVG 0x05 /* U/B Average Level */ +#define GAVG 0x06 /* Y/Gb Average Level */ +#define RAVG 0x07 /* V/R Average Level */ +#define AECH 0x08 /* Exposure Value – AEC MSBs */ + +#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_VFLIP 0x80 /* Vertical flip image ON/OFF selection */ +#define COM3_MIRROR 0x40 /* Horizontal mirror image ON/OFF selection */ +#define COM3_SWAP_BR 0x20 /* Swap B/R output sequence in RGB output mode */ +#define COM3_SWAP_YUV 0x10 /* Swap Y/UV output sequence in YUV output mode */ +#define COM3_SWAP_MSB 0x08 /* Swap output MSB/LSB */ +#define COM3_TRI_CLOCK 0x04 /* Tri-state option for output clock at power-down period */ +#define COM3_TRI_DATA 0x02 /* Tri-state option for output data at power-down period */ +#define COM3_COLOR_BAR 0x01 /* Sensor color bar test pattern output enable */ +#define COM3_SET_CBAR(r, x) ((r&0xFE)|((x&1)<<0)) +#define COM3_SET_MIRROR(r, x) ((r&0xBF)|((x&1)<<6)) +#define COM3_SET_FLIP(r, x) ((r&0x7F)|((x&1)<<7)) + +#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 0x02 /* Output format RGB */ +#define COM7_FMT_R_BAYER 0x03 /* Output format Bayer RAW */ +#define COM7_SET_FMT(r, x) ((r&0xFC)|((x&0x3)<<0)) +#define COM7_SET_RGB(r, x) ((r&0xF0)|(x&0x0C)|COM7_FMT_RGB) + +#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 REG16 0x16 /* Register 16 */ +#define REG16_BIT_SHIFT 0x80 /* Bit shift test pattern options */ +#define HSTART 0x17 /* Horizontal Frame (HREF column) Start 8 MSBs (2 LSBs are at HREF[5:4]) */ +#define HSIZE 0x18 /* Horizontal Sensor Size (2 LSBs are at HREF[1:0]) */ +#define VSTART 0x19 /* Vertical Frame (row) Start 8 MSBs (1 LSB is at HREF[6]) */ +#define VSIZE 0x1A /* Vertical Sensor Size (1 LSB is at HREF[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 LAEC 0x1F /* Fine AEC Value - defines exposure value less than one row period */ + +#define COM11 0x20 /* Common Control 11 */ +#define COM11_SNGL_FRAME_EN 0x02 /* Single frame ON/OFF selection */ +#define COM11_SNGL_XFR_TRIG 0x01 /* Single frame transfer trigger */ + +#define BDBASE 0x22 /* Banding Filter Minimum AEC Value */ +#define DBSTEP 0x23 /* Banding Filter Maximum Step */ +#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 REG28 0x28 /* Selection on the number of dummy rows, N */ +#define HOUTSIZE 0x29 /* Horizontal Data Output Size MSBs (2 LSBs at register EXHCH[1:0]) */ +#define EXHCH 0x2A /* Dummy Pixel Insert MSB */ +#define EXHCL 0x2B /* Dummy Pixel Insert LSB */ +#define VOUTSIZE 0x2C /* Vertical Data Output Size MSBs (LSB at register EXHCH[2]) */ +#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 LUMHTH 0x30 /* Histogram AEC/AGC Luminance High Level Threshold */ +#define LUMLTH 0x31 /* Histogram AEC/AGC Luminance Low Level Threshold */ +#define HREF 0x32 /* Image Start and Size Control */ +#define DM_LNL 0x33 /* Dummy Row Low 8 Bits */ +#define DM_LNH 0x34 /* Dummy Row High 8 Bits */ +#define ADOFF_B 0x35 /* AD Offset Compensation Value for B Channel */ +#define ADOFF_R 0x36 /* AD Offset Compensation Value for R Channel */ +#define ADOFF_GB 0x37 /* AD Offset Compensation Value for GB Channel */ +#define ADOFF_GR 0x38 /* AD Offset Compensation Value for GR Channel */ +#define OFF_B 0x39 /* AD Offset Compensation Value for B Channel */ +#define OFF_R 0x3A /* AD Offset Compensation Value for R Channel */ +#define OFF_GB 0x3B /* AD Offset Compensation Value for GB Channel */ +#define OFF_GR 0x3C /* AD Offset Compensation Value for GR Channel */ +#define COM12 0x3D /* DC offset compensation for analog process */ + +#define COM13 0x3E /* Common Control 13 */ +#define COM13_BLC_EN 0x80 /* BLC enable */ +#define COM13_ADC_EN 0x40 /* ADC channel BLC ON/OFF control */ +#define COM13_ANALOG_BLC 0x20 /* Analog processing channel BLC ON/OFF control */ +#define COM13_ABLC_GAIN_EN 0x04 /* ABLC gain trigger enable */ + +#define COM14 0x3F /* Common Control 14 */ +#define COM15 0x40 /* Common Control 15 */ +#define COM16 0x41 /* Common Control 16 */ +#define TGT_B 0x42 /* BLC Blue Channel Target Value */ +#define TGT_R 0x43 /* BLC Red Channel Target Value */ +#define TGT_GB 0x44 /* BLC Gb Channel Target Value */ +#define TGT_GR 0x45 /* BLC Gr Channel Target Value */ + +#define LC_CTR 0x46 /* Lens Correction Control */ +#define LC_CTR_RGB_COMP_1 0x00 /* R, G, and B channel compensation coefficient is set by LC_COEF (0x49) */ +#define LC_CTR_RGB_COMP_3 0x04 /* R, G, and B channel compensation coefficient is set by registers + LC_COEFB (0x4B), LC_COEF (0x49), and LC_COEFR (0x4C), respectively */ +#define LC_CTR_EN 0x01 /* Lens correction enable */ +#define LC_XC 0x47 /* X Coordinate of Lens Correction Center Relative to Array Center */ +#define LC_YC 0x48 /* Y Coordinate of Lens Correction Center Relative to Array Center */ +#define LC_COEF 0x49 /* Lens Correction Coefficient */ +#define LC_RADI 0x4A /* Lens Correction Radius */ +#define LC_COEFB 0x4B /* Lens Correction B Channel Compensation Coefficient */ +#define LC_COEFR 0x4C /* Lens Correction R Channel Compensation Coefficient */ + +#define FIXGAIN 0x4D /* Analog Fix Gain Amplifier */ +#define AREF0 0x4E /* Sensor Reference Control */ +#define AREF1 0x4F /* Sensor Reference Current Control */ +#define AREF2 0x50 /* Analog Reference Control */ +#define AREF3 0x51 /* ADC Reference Control */ +#define AREF4 0x52 /* ADC Reference Control */ +#define AREF5 0x53 /* ADC Reference Control */ +#define AREF6 0x54 /* Analog Reference Control */ +#define AREF7 0x55 /* Analog Reference Control */ +#define UFIX 0x60 /* U Channel Fixed Value Output */ +#define VFIX 0x61 /* V Channel Fixed Value Output */ +#define AWBB_BLK 0x62 /* AWB Option for Advanced AWB */ + +#define AWB_CTRL0 0x63 /* AWB Control Byte 0 */ +#define AWB_CTRL0_GAIN_EN 0x80 /* AWB gain enable */ +#define AWB_CTRL0_CALC_EN 0x40 /* AWB calculate enable */ +#define AWB_CTRL0_WBC_MASK 0x0F /* WBC threshold 2 */ + +#define DSP_CTRL1 0x64 /* DSP Control Byte 1 */ +#define DSP_CTRL1_FIFO_EN 0x80 /* FIFO enable/disable selection */ +#define DSP_CTRL1_UV_EN 0x40 /* UV adjust function ON/OFF selection */ +#define DSP_CTRL1_SDE_EN 0x20 /* SDE enable */ +#define DSP_CTRL1_MTRX_EN 0x10 /* Color matrix ON/OFF selection */ +#define DSP_CTRL1_INTRP_EN 0x08 /* Interpolation ON/OFF selection */ +#define DSP_CTRL1_GAMMA_EN 0x04 /* Gamma function ON/OFF selection */ +#define DSP_CTRL1_BLACK_EN 0x02 /* Black defect auto correction ON/OFF */ +#define DSP_CTRL1_WHITE_EN 0x01 /* White defect auto correction ON/OFF */ + +#define DSP_CTRL2 0x65 /* DSP Control Byte 2 */ +#define DSP_CTRL2_VDCW_EN 0x08 /* Vertical DCW enable */ +#define DSP_CTRL2_HDCW_EN 0x04 /* Horizontal DCW enable */ +#define DSP_CTRL2_VZOOM_EN 0x02 /* Vertical zoom out enable */ +#define DSP_CTRL2_HZOOM_EN 0x01 /* Horizontal zoom out enable */ + +#define DSP_CTRL3 0x66 /* DSP Control Byte 3 */ +#define DSP_CTRL3_UV_EN 0x80 /* UV output sequence option */ +#define DSP_CTRL3_CBAR_EN 0x20 /* DSP color bar ON/OFF selection */ +#define DSP_CTRL3_FIFO_EN 0x08 /* FIFO power down ON/OFF selection */ +#define DSP_CTRL3_SCAL1_PWDN 0x04 /* Scaling module power down control 1 */ +#define DSP_CTRL3_SCAL2_PWDN 0x02 /* Scaling module power down control 2 */ +#define DSP_CTRL3_INTRP_PWDN 0x01 /* Interpolation module power down control */ +#define DSP_CTRL3_SET_CBAR(r, x) ((r&0xDF)|((x&1)<<5)) + + +#define DSP_CTRL4 0x67 /* DSP Control Byte 4 */ +#define DSP_CTRL4_YUV_RGB 0x00 /* Output selection YUV or RGB */ +#define DSP_CTRL4_RAW8 0x02 /* Output selection RAW8 */ +#define DSP_CTRL4_RAW10 0x03 /* Output selection RAW10 */ + + +#define AWB_BIAS 0x68 /* AWB BLC Level Clip */ +#define AWB_CTRL1 0x69 /* AWB Control 1 */ +#define AWB_CTRL2 0x6A /* AWB Control 2 */ + +#define AWB_CTRL3 0x6B /* AWB Control 3 */ +#define AWB_CTRL3_ADVANCED 0x80 /* AWB mode select - Advanced AWB */ +#define AWB_CTRL3_SIMPLE 0x00 /* AWB mode select - Simple AWB */ + +#define AWB_CTRL4 0x6C /* AWB Control 4 */ +#define AWB_CTRL5 0x6D /* AWB Control 5 */ +#define AWB_CTRL6 0x6E /* AWB Control 6 */ +#define AWB_CTRL7 0x6F /* AWB Control 7 */ +#define AWB_CTRL8 0x70 /* AWB Control 8 */ +#define AWB_CTRL9 0x71 /* AWB Control 9 */ +#define AWB_CTRL10 0x72 /* AWB Control 10 */ +#define AWB_CTRL11 0x73 /* AWB Control 11 */ +#define AWB_CTRL12 0x74 /* AWB Control 12 */ +#define AWB_CTRL13 0x75 /* AWB Control 13 */ +#define AWB_CTRL14 0x76 /* AWB Control 14 */ +#define AWB_CTRL15 0x77 /* AWB Control 15 */ +#define AWB_CTRL16 0x78 /* AWB Control 16 */ +#define AWB_CTRL17 0x79 /* AWB Control 17 */ +#define AWB_CTRL18 0x7A /* AWB Control 18 */ +#define AWB_CTRL19 0x7B /* AWB Control 19 */ +#define AWB_CTRL20 0x7C /* AWB Control 20 */ +#define AWB_CTRL21 0x7D /* AWB Control 21 */ +#define GAM1 0x7E /* Gamma Curve 1st Segment Input End Point 0x04 Output Value */ +#define GAM2 0x7F /* Gamma Curve 2nd Segment Input End Point 0x08 Output Value */ +#define GAM3 0x80 /* Gamma Curve 3rd Segment Input End Point 0x10 Output Value */ +#define GAM4 0x81 /* Gamma Curve 4th Segment Input End Point 0x20 Output Value */ +#define GAM5 0x82 /* Gamma Curve 5th Segment Input End Point 0x28 Output Value */ +#define GAM6 0x83 /* Gamma Curve 6th Segment Input End Point 0x30 Output Value */ +#define GAM7 0x84 /* Gamma Curve 7th Segment Input End Point 0x38 Output Value */ +#define GAM8 0x85 /* Gamma Curve 8th Segment Input End Point 0x40 Output Value */ +#define GAM9 0x86 /* Gamma Curve 9th Segment Input End Point 0x48 Output Value */ +#define GAM10 0x87 /* Gamma Curve 10th Segment Input End Point 0x50 Output Value */ +#define GAM11 0x88 /* Gamma Curve 11th Segment Input End Point 0x60 Output Value */ +#define GAM12 0x89 /* Gamma Curve 12th Segment Input End Point 0x70 Output Value */ +#define GAM13 0x8A /* Gamma Curve 13th Segment Input End Point 0x90 Output Value */ +#define GAM14 0x8B /* Gamma Curve 14th Segment Input End Point 0xB0 Output Value */ +#define GAM15 0x8C /* Gamma Curve 15th Segment Input End Point 0xD0 Output Value */ +#define SLOP 0x8D /* Gamma Curve Highest Segment Slope */ +#define DNSTH 0x8E /* De-noise Threshold */ +#define EDGE0 0x8F /* Edge Enhancement Strength Control */ +#define EDGE1 0x90 /* Edge Enhancement Threshold Control */ +#define DNSOFF 0x91 /* Auto De-noise Threshold Control */ +#define EDGE2 0x92 /* Edge Enhancement Strength Upper Limit */ +#define EDGE3 0x93 /* Edge Enhancement Strength Upper Limit */ +#define MTX1 0x94 /* Matrix Coefficient 1 */ +#define MTX2 0x95 /* Matrix Coefficient 2 */ +#define MTX3 0x96 /* Matrix Coefficient 3 */ +#define MTX4 0x97 /* Matrix Coefficient 4 */ +#define MTX5 0x98 /* Matrix Coefficient 5 */ +#define MTX6 0x99 /* Matrix Coefficient 6 */ + +#define MTX_CTRL 0x9A /* Matrix Control */ +#define MTX_CTRL_DBL_EN 0x80 /* Matrix double ON/OFF selection */ + +#define BRIGHTNESS 0x9B /* Brightness Control */ +#define CONTRAST 0x9C /* Contrast Gain */ +#define UVADJ0 0x9E /* Auto UV Adjust Control 0 */ +#define UVADJ1 0x9F /* Auto UV Adjust Control 1 */ +#define SCAL0 0xA0 /* DCW Ratio Control */ +#define SCAL1 0xA1 /* Horizontal Zoom Out Control */ +#define SCAL2 0xA2 /* Vertical Zoom Out Control */ +#define FIFODLYM 0xA3 /* FIFO Manual Mode Delay Control */ +#define FIFODLYA 0xA4 /* FIFO Auto Mode Delay Control */ + +#define SDE 0xA6 /* Special Digital Effect Control */ +#define SDE_NEGATIVE_EN 0x40 /* Negative image enable */ +#define SDE_GRAYSCALE_EN 0x20 /* Gray scale image enable */ +#define SDE_V_FIXED_EN 0x10 /* V fixed value enable */ +#define SDE_U_FIXED_EN 0x08 /* U fixed value enable */ +#define SDE_CONT_BRIGHT_EN 0x04 /* Contrast/Brightness enable */ +#define SDE_SATURATION_EN 0x02 /* Saturation enable */ +#define SDE_HUE_EN 0x01 /* Hue enable */ + +#define USAT 0xA7 /* U Component Saturation Gain */ +#define VSAT 0xA8 /* V Component Saturation Gain */ +#define HUECOS 0xA9 /* Cosine value × 0x80 */ +#define HUESIN 0xAA /* Sine value × 0x80 */ +#define SIGN_BIT 0xAB /* Sign Bit for Hue and Brightness */ + +#define DSPAUTO 0xAC /* DSP Auto Function ON/OFF Control */ +#define DSPAUTO_AWB_EN 0x80 /* AWB auto threshold control */ +#define DSPAUTO_DENOISE_EN 0x40 /* De-noise auto threshold control */ +#define DSPAUTO_EDGE_EN 0x20 /* Sharpness (edge enhancement) auto strength control */ +#define DSPAUTO_UV_EN 0x10 /* UV adjust auto slope control */ +#define DSPAUTO_SCAL0_EN 0x08 /* Auto scaling factor control (register SCAL0 (0xA0)) */ +#define DSPAUTO_SCAL1_EN 0x04 /* Auto scaling factor control (registers SCAL1 (0xA1 and SCAL2 (0xA2))*/ +#define SET_REG(reg, x) (##reg_DEFAULT|x) +#endif //__REG_REGS_H__ diff --git a/esp32-cam/sccb.c b/esp32-cam/sccb.c new file mode 100644 index 0000000..30e725c --- /dev/null +++ b/esp32-cam/sccb.c @@ -0,0 +1,260 @@ +/* + * 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. + * + * SCCB (I2C like) driver. + * + */ +#include +#include +#include +#include "sccb.h" +#include +#include "sdkconfig.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 = "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 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*/ +#define ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */ +#define ACK_VAL 0x0 /*!< I2C ack value */ +#define NACK_VAL 0x1 /*!< I2C nack value */ +#if CONFIG_SCCB_HARDWARE_I2C_PORT1 +const int SCCB_I2C_PORT = 1; +#else +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; + conf.mode = I2C_MODE_MASTER; + conf.sda_io_num = pin_sda; + conf.sda_pullup_en = GPIO_PULLUP_ENABLE; + conf.scl_io_num = pin_scl; + conf.scl_pullup_en = GPIO_PULLUP_ENABLE; + conf.master.clk_speed = SCCB_FREQ; + + 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(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( slave_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); + i2c_master_stop(cmd); + esp_err_t ret = i2c_master_cmd_begin(SCCB_I2C_PORT, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + if( ret == ESP_OK) { + ESP_SLAVE_ADDR = slave_addr; + return ESP_SLAVE_ADDR; + } + 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(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); + i2c_master_write_byte(cmd, reg, ACK_CHECK_EN); + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin(SCCB_I2C_PORT, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + if(ret != ESP_OK) return -1; + cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( slv_addr << 1 ) | READ_BIT, ACK_CHECK_EN); + i2c_master_read_byte(cmd, &data, NACK_VAL); + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin(SCCB_I2C_PORT, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + if(ret != ESP_OK) { + 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); + i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); + i2c_master_write_byte(cmd, reg, ACK_CHECK_EN); + i2c_master_write_byte(cmd, data, ACK_CHECK_EN); + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin(SCCB_I2C_PORT, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + if(ret != ESP_OK) { + 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); + uint8_t *reg_u8 = (uint8_t *)®_htons; + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); + i2c_master_write_byte(cmd, reg_u8[0], ACK_CHECK_EN); + i2c_master_write_byte(cmd, reg_u8[1], ACK_CHECK_EN); + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin(SCCB_I2C_PORT, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + if(ret != ESP_OK) return -1; + cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( slv_addr << 1 ) | READ_BIT, ACK_CHECK_EN); + i2c_master_read_byte(cmd, &data, NACK_VAL); + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin(SCCB_I2C_PORT, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + if(ret != ESP_OK) { + 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; + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); + i2c_master_write_byte(cmd, reg_u8[0], ACK_CHECK_EN); + i2c_master_write_byte(cmd, reg_u8[1], ACK_CHECK_EN); + i2c_master_write_byte(cmd, data, ACK_CHECK_EN); + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin(SCCB_I2C_PORT, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + if(ret != ESP_OK) { + 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/sccb.h b/esp32-cam/sccb.h new file mode 100644 index 0000000..4d5b5b4 --- /dev/null +++ b/esp32-cam/sccb.h @@ -0,0 +1,18 @@ +/* + * 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. + * + * SCCB (I2C like) driver. + * + */ +#ifndef __SCCB_H__ +#define __SCCB_H__ +#include +int SCCB_Init(int pin_sda, int pin_scl); +uint8_t SCCB_Probe(); +uint8_t SCCB_Read(uint8_t slv_addr, uint8_t reg); +uint8_t SCCB_Write(uint8_t slv_addr, uint8_t reg, uint8_t data); +uint8_t SCCB_Read16(uint8_t slv_addr, uint16_t reg); +uint8_t SCCB_Write16(uint8_t slv_addr, uint16_t reg, uint8_t data); +#endif // __SCCB_H__ diff --git a/esp32-cam/sensor.c b/esp32-cam/sensor.c new file mode 100644 index 0000000..2e6d111 --- /dev/null +++ b/esp32-cam/sensor.c @@ -0,0 +1,28 @@ +#include "sensor.h" + +const resolution_info_t resolution[FRAMESIZE_INVALID] = { + { 96, 96, ASPECT_RATIO_1X1 }, /* 96x96 */ + { 160, 120, ASPECT_RATIO_4X3 }, /* QQVGA */ + { 176, 144, ASPECT_RATIO_5X4 }, /* QCIF */ + { 240, 176, ASPECT_RATIO_4X3 }, /* HQVGA */ + { 240, 240, ASPECT_RATIO_1X1 }, /* 240x240 */ + { 320, 240, ASPECT_RATIO_4X3 }, /* QVGA */ + { 400, 296, ASPECT_RATIO_4X3 }, /* CIF */ + { 480, 320, ASPECT_RATIO_3X2 }, /* HVGA */ + { 640, 480, ASPECT_RATIO_4X3 }, /* VGA */ + { 800, 600, ASPECT_RATIO_4X3 }, /* SVGA */ + { 1024, 768, ASPECT_RATIO_4X3 }, /* XGA */ + { 1280, 720, ASPECT_RATIO_16X9 }, /* HD */ + { 1280, 1024, ASPECT_RATIO_5X4 }, /* SXGA */ + { 1600, 1200, ASPECT_RATIO_4X3 }, /* UXGA */ + // 3MP Sensors + { 1920, 1080, ASPECT_RATIO_16X9 }, /* FHD */ + { 720, 1280, ASPECT_RATIO_9X16 }, /* Portrait HD */ + { 864, 1536, ASPECT_RATIO_9X16 }, /* Portrait 3MP */ + { 2048, 1536, ASPECT_RATIO_4X3 }, /* QXGA */ + // 5MP Sensors + { 2560, 1440, ASPECT_RATIO_16X9 }, /* QHD */ + { 2560, 1600, ASPECT_RATIO_16X10 }, /* WQXGA */ + { 1088, 1920, ASPECT_RATIO_9X16 }, /* Portrait FHD */ + { 2560, 1920, ASPECT_RATIO_4X3 }, /* QSXGA */ +}; diff --git a/esp32-cam/sensor.h b/esp32-cam/sensor.h new file mode 100644 index 0000000..3ea7e2c --- /dev/null +++ b/esp32-cam/sensor.h @@ -0,0 +1,191 @@ +/* + * 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. + * + * Sensor abstraction layer. + * + */ +#ifndef __SENSOR_H__ +#define __SENSOR_H__ +#include +#include + +#define OV9650_PID (0x96) +#define OV7725_PID (0x77) +#define OV2640_PID (0x26) +#define OV3660_PID (0x36) +#define OV5640_PID (0x56) + +typedef enum { + PIXFORMAT_RGB565, // 2BPP/RGB565 + PIXFORMAT_YUV422, // 2BPP/YUV422 + PIXFORMAT_GRAYSCALE, // 1BPP/GRAYSCALE + PIXFORMAT_JPEG, // JPEG/COMPRESSED + PIXFORMAT_RGB888, // 3BPP/RGB888 + PIXFORMAT_RAW, // RAW + PIXFORMAT_RGB444, // 3BP2P/RGB444 + PIXFORMAT_RGB555, // 3BP2P/RGB555 +} pixformat_t; + +typedef enum { + FRAMESIZE_96X96, // 96x96 + FRAMESIZE_QQVGA, // 160x120 + FRAMESIZE_QCIF, // 176x144 + FRAMESIZE_HQVGA, // 240x176 + FRAMESIZE_240X240, // 240x240 + FRAMESIZE_QVGA, // 320x240 + FRAMESIZE_CIF, // 400x296 + FRAMESIZE_HVGA, // 480x320 + FRAMESIZE_VGA, // 640x480 + FRAMESIZE_SVGA, // 800x600 + FRAMESIZE_XGA, // 1024x768 + FRAMESIZE_HD, // 1280x720 + FRAMESIZE_SXGA, // 1280x1024 + FRAMESIZE_UXGA, // 1600x1200 + // 3MP Sensors + FRAMESIZE_FHD, // 1920x1080 + FRAMESIZE_P_HD, // 720x1280 + FRAMESIZE_P_3MP, // 864x1536 + FRAMESIZE_QXGA, // 2048x1536 + // 5MP Sensors + FRAMESIZE_QHD, // 2560x1440 + FRAMESIZE_WQXGA, // 2560x1600 + FRAMESIZE_P_FHD, // 1080x1920 + FRAMESIZE_QSXGA, // 2560x1920 + FRAMESIZE_INVALID +} framesize_t; + +typedef enum { + ASPECT_RATIO_4X3, + ASPECT_RATIO_3X2, + ASPECT_RATIO_16X10, + ASPECT_RATIO_5X3, + ASPECT_RATIO_16X9, + ASPECT_RATIO_21X9, + ASPECT_RATIO_5X4, + ASPECT_RATIO_1X1, + ASPECT_RATIO_9X16 +} aspect_ratio_t; + +typedef enum { + GAINCEILING_2X, + GAINCEILING_4X, + GAINCEILING_8X, + GAINCEILING_16X, + GAINCEILING_32X, + GAINCEILING_64X, + GAINCEILING_128X, +} gainceiling_t; + +typedef struct { + uint16_t max_width; + uint16_t max_height; + uint16_t start_x; + uint16_t start_y; + uint16_t end_x; + uint16_t end_y; + uint16_t offset_x; + uint16_t offset_y; + uint16_t total_x; + uint16_t total_y; +} ratio_settings_t; + +typedef struct { + const uint16_t width; + const uint16_t height; + const aspect_ratio_t aspect_ratio; +} resolution_info_t; + +// Resolution table (in sensor.c) +extern const resolution_info_t resolution[]; + +typedef struct { + uint8_t MIDH; + uint8_t MIDL; + uint8_t PID; + uint8_t VER; +} sensor_id_t; + +typedef struct { + framesize_t framesize;//0 - 10 + bool scale; + bool binning; + uint8_t quality;//0 - 63 + int8_t brightness;//-2 - 2 + int8_t contrast;//-2 - 2 + int8_t saturation;//-2 - 2 + int8_t sharpness;//-2 - 2 + uint8_t denoise; + uint8_t special_effect;//0 - 6 + uint8_t wb_mode;//0 - 4 + uint8_t awb; + uint8_t awb_gain; + uint8_t aec; + uint8_t aec2; + int8_t ae_level;//-2 - 2 + uint16_t aec_value;//0 - 1200 + uint8_t agc; + uint8_t agc_gain;//0 - 30 + uint8_t gainceiling;//0 - 6 + uint8_t bpc; + uint8_t wpc; + uint8_t raw_gma; + uint8_t lenc; + uint8_t hmirror; + uint8_t vflip; + uint8_t dcw; + uint8_t colorbar; +} camera_status_t; + +typedef struct _sensor sensor_t; +typedef struct _sensor { + sensor_id_t id; // Sensor ID. + uint8_t slv_addr; // Sensor I2C slave address. + pixformat_t pixformat; + camera_status_t status; + int xclk_freq_hz; + + // Sensor function pointers + int (*init_status) (sensor_t *sensor); + int (*reset) (sensor_t *sensor); + int (*set_pixformat) (sensor_t *sensor, pixformat_t pixformat); + int (*set_framesize) (sensor_t *sensor, framesize_t framesize); + int (*set_contrast) (sensor_t *sensor, int level); + int (*set_brightness) (sensor_t *sensor, int level); + int (*set_saturation) (sensor_t *sensor, int level); + int (*set_sharpness) (sensor_t *sensor, int level); + int (*set_denoise) (sensor_t *sensor, int level); + int (*set_gainceiling) (sensor_t *sensor, gainceiling_t gainceiling); + int (*set_quality) (sensor_t *sensor, int quality); + int (*set_colorbar) (sensor_t *sensor, int enable); + int (*set_whitebal) (sensor_t *sensor, int enable); + int (*set_gain_ctrl) (sensor_t *sensor, int enable); + int (*set_exposure_ctrl) (sensor_t *sensor, int enable); + int (*set_hmirror) (sensor_t *sensor, int enable); + int (*set_vflip) (sensor_t *sensor, int enable); + + int (*set_aec2) (sensor_t *sensor, int enable); + int (*set_awb_gain) (sensor_t *sensor, int enable); + int (*set_agc_gain) (sensor_t *sensor, int gain); + int (*set_aec_value) (sensor_t *sensor, int gain); + + int (*set_special_effect) (sensor_t *sensor, int effect); + int (*set_wb_mode) (sensor_t *sensor, int mode); + int (*set_ae_level) (sensor_t *sensor, int level); + + int (*set_dcw) (sensor_t *sensor, int enable); + int (*set_bpc) (sensor_t *sensor, int enable); + int (*set_wpc) (sensor_t *sensor, int enable); + + int (*set_raw_gma) (sensor_t *sensor, int enable); + int (*set_lenc) (sensor_t *sensor, int enable); + + int (*get_reg) (sensor_t *sensor, int reg, int mask); + int (*set_reg) (sensor_t *sensor, int reg, int mask, int value); + 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 (*set_pll) (sensor_t *sensor, int bypass, int mul, int sys, int root, int pre, int seld5, int pclken, int pclk); + int (*set_xclk) (sensor_t *sensor, int timer, int xclk); +} sensor_t; + +#endif /* __SENSOR_H__ */ diff --git a/esp32-cam/to_bmp.c b/esp32-cam/to_bmp.c new file mode 100644 index 0000000..59455de --- /dev/null +++ b/esp32-cam/to_bmp.c @@ -0,0 +1,315 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include +#include +#include "img_converters.h" +#include "soc/efuse_reg.h" +#include "esp_heap_caps.h" +#include "yuv.h" +#include "sdkconfig.h" +#include "esp_jpg_decode.h" + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define TAG "" +#else +#include "esp_log.h" +static const char* TAG = "to_bmp"; +#endif + +static const int BMP_HEADER_LEN = 54; + +typedef struct { + uint32_t filesize; + uint32_t reserved; + uint32_t fileoffset_to_pixelarray; + uint32_t dibheadersize; + int32_t width; + int32_t height; + uint16_t planes; + uint16_t bitsperpixel; + uint32_t compression; + uint32_t imagesize; + uint32_t ypixelpermeter; + uint32_t xpixelpermeter; + uint32_t numcolorspallette; + uint32_t mostimpcolor; +} bmp_header_t; + +typedef struct { + uint16_t width; + uint16_t height; + uint16_t data_offset; + const uint8_t *input; + uint8_t *output; +} rgb_jpg_decoder; + +static void *_malloc(size_t size) +{ + return heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); +} + +//output buffer and image width +static bool _rgb_write(void * arg, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t *data) +{ + rgb_jpg_decoder * jpeg = (rgb_jpg_decoder *)arg; + if(!data){ + if(x == 0 && y == 0){ + //write start + jpeg->width = w; + jpeg->height = h; + //if output is null, this is BMP + if(!jpeg->output){ + jpeg->output = (uint8_t *)_malloc((w*h*3)+jpeg->data_offset); + if(!jpeg->output){ + return false; + } + } + } else { + //write end + } + return true; + } + + size_t jw = jpeg->width*3; + size_t t = y * jw; + size_t b = t + (h * jw); + size_t l = x * 3; + uint8_t *out = jpeg->output+jpeg->data_offset; + uint8_t *o = out; + size_t iy, ix; + + w = w * 3; + + for(iy=t; iyinput + index, len); + } + return len; +} + +static bool jpg2rgb888(const uint8_t *src, size_t src_len, uint8_t * out, jpg_scale_t scale) +{ + rgb_jpg_decoder jpeg; + jpeg.width = 0; + jpeg.height = 0; + jpeg.input = src; + jpeg.output = out; + jpeg.data_offset = 0; + + if(esp_jpg_decode(src_len, scale, _jpg_read, _rgb_write, (void*)&jpeg) != ESP_OK){ + return false; + } + return true; +} + +bool jpg2bmp(const uint8_t *src, size_t src_len, uint8_t ** out, size_t * out_len) +{ + + rgb_jpg_decoder jpeg; + jpeg.width = 0; + jpeg.height = 0; + jpeg.input = src; + jpeg.output = NULL; + jpeg.data_offset = BMP_HEADER_LEN; + + if(esp_jpg_decode(src_len, JPG_SCALE_NONE, _jpg_read, _rgb_write, (void*)&jpeg) != ESP_OK){ + return false; + } + + size_t output_size = jpeg.width*jpeg.height*3; + + jpeg.output[0] = 'B'; + jpeg.output[1] = 'M'; + bmp_header_t * bitmap = (bmp_header_t*)&jpeg.output[2]; + bitmap->reserved = 0; + bitmap->filesize = output_size+BMP_HEADER_LEN; + bitmap->fileoffset_to_pixelarray = BMP_HEADER_LEN; + bitmap->dibheadersize = 40; + bitmap->width = jpeg.width; + bitmap->height = -jpeg.height;//set negative for top to bottom + bitmap->planes = 1; + bitmap->bitsperpixel = 24; + bitmap->compression = 0; + bitmap->imagesize = output_size; + bitmap->ypixelpermeter = 0x0B13 ; //2835 , 72 DPI + bitmap->xpixelpermeter = 0x0B13 ; //2835 , 72 DPI + bitmap->numcolorspallette = 0; + bitmap->mostimpcolor = 0; + + *out = jpeg.output; + *out_len = output_size+BMP_HEADER_LEN; + + return true; +} + +bool fmt2rgb888(const uint8_t *src_buf, size_t src_len, pixformat_t format, uint8_t * rgb_buf) +{ + int pix_count = 0; + if(format == PIXFORMAT_JPEG) { + return jpg2rgb888(src_buf, src_len, rgb_buf, JPG_SCALE_NONE); + } else if(format == PIXFORMAT_RGB888) { + memcpy(rgb_buf, src_buf, src_len); + } else if(format == PIXFORMAT_RGB565) { + int i; + uint8_t hb, lb; + pix_count = src_len / 2; + for(i=0; i> 3; + *rgb_buf++ = hb & 0xF8; + } + } else if(format == PIXFORMAT_GRAYSCALE) { + int i; + uint8_t b; + pix_count = src_len; + for(i=0; ireserved = 0; + bitmap->filesize = out_size; + bitmap->fileoffset_to_pixelarray = BMP_HEADER_LEN; + bitmap->dibheadersize = 40; + bitmap->width = width; + bitmap->height = -height;//set negative for top to bottom + bitmap->planes = 1; + bitmap->bitsperpixel = 24; + bitmap->compression = 0; + bitmap->imagesize = pix_count * 3; + bitmap->ypixelpermeter = 0x0B13 ; //2835 , 72 DPI + bitmap->xpixelpermeter = 0x0B13 ; //2835 , 72 DPI + bitmap->numcolorspallette = 0; + bitmap->mostimpcolor = 0; + + uint8_t * rgb_buf = out_buf + BMP_HEADER_LEN; + uint8_t * src_buf = src; + + + //convert data to RGB888 + if(format == PIXFORMAT_RGB888) { + memcpy(rgb_buf, src_buf, pix_count*3); + } else if(format == PIXFORMAT_RGB565) { + int i; + uint8_t hb, lb; + for(i=0; i> 3; + *rgb_buf++ = hb & 0xF8; + } + } else if(format == PIXFORMAT_GRAYSCALE) { + int i; + uint8_t b; + for(i=0; ibuf, fb->len, fb->width, fb->height, fb->format, out, out_len); +} diff --git a/esp32-cam/to_jpg.cpp b/esp32-cam/to_jpg.cpp new file mode 100644 index 0000000..f8987a8 --- /dev/null +++ b/esp32-cam/to_jpg.cpp @@ -0,0 +1,241 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include +#include +#include "esp_attr.h" +#include "soc/efuse_reg.h" +#include "esp_heap_caps.h" +#include "esp_camera.h" +#include "img_converters.h" +#include "jpge.h" +#include "yuv.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 "" +#else +#include "esp_log.h" +static const char* TAG = "to_jpg"; +#endif + +static void *_malloc(size_t size) +{ + void * res = malloc(size); + if(res) { + return res; + } + return heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); +} + +static IRAM_ATTR void convert_line_format(uint8_t * src, pixformat_t format, uint8_t * dst, size_t width, size_t in_channels, size_t line) +{ + int i=0, o=0, l=0; + if(format == PIXFORMAT_GRAYSCALE) { + memcpy(dst, src + line * width, width); + } else if(format == PIXFORMAT_RGB888) { + l = width * 3; + src += l * line; + for(i=0; i> 3; + dst[o++] = (src[i+1] & 0x1F) << 3; + } + } else if(format == PIXFORMAT_YUV422) { + uint8_t y0, y1, u, v; + uint8_t r, g, b; + l = width * 2; + src += l * line; + for(i=0; i 100) { + quality = 100; + } + + jpge::params comp_params = jpge::params(); + comp_params.m_subsampling = subsampling; + comp_params.m_quality = quality; + + jpge::jpeg_encoder dst_image; + + if (!dst_image.init(dst_stream, width, height, num_channels, comp_params)) { + ESP_LOGE(TAG, "JPG encoder init failed"); + return false; + } + + uint8_t* line = (uint8_t*)_malloc(width * num_channels); + if(!line) { + ESP_LOGE(TAG, "Scan line malloc failed"); + return false; + } + + for (int i = 0; i < height; i++) { + convert_line_format(src, format, line, width, num_channels, i); + if (!dst_image.process_scanline(line)) { + ESP_LOGE(TAG, "JPG process line %u failed", i); + free(line); + return false; + } + } + free(line); + + if (!dst_image.process_scanline(NULL)) { + ESP_LOGE(TAG, "JPG image finish failed"); + return false; + } + dst_image.deinit(); + return true; +} + +class callback_stream : public jpge::output_stream { +protected: + jpg_out_cb ocb; + void * oarg; + size_t index; + +public: + callback_stream(jpg_out_cb cb, void * arg) : ocb(cb), oarg(arg), index(0) { } + virtual ~callback_stream() { } + virtual bool put_buf(const void* data, int len) + { + index += ocb(oarg, index, data, len); + return true; + } + virtual size_t get_size() const + { + return index; + } +}; + +bool fmt2jpg_cb(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, jpg_out_cb cb, void * arg) +{ + callback_stream dst_stream(cb, arg); + return convert_image(src, width, height, format, quality, &dst_stream); +} + +bool frame2jpg_cb(camera_fb_t * fb, uint8_t quality, jpg_out_cb cb, void * arg) +{ + return fmt2jpg_cb(fb->buf, fb->len, fb->width, fb->height, fb->format, quality, cb, arg); +} + + + +class memory_stream : public jpge::output_stream { +protected: + uint8_t *out_buf; + size_t max_len, index; + +public: + memory_stream(void *pBuf, uint buf_size) : out_buf(static_cast(pBuf)), max_len(buf_size), index(0) { } + + virtual ~memory_stream() { } + + virtual bool put_buf(const void* pBuf, int len) + { + if (!pBuf) { + //end of image + return true; + } + if ((size_t)len > (max_len - index)) { + ESP_LOGW(TAG, "JPG output overflow: %d bytes", len - (max_len - index)); + len = max_len - index; + } + if (len) { + memcpy(out_buf + index, pBuf, len); + index += len; + } + return true; + } + + virtual size_t get_size() const + { + return index; + } +}; + +bool fmt2jpg(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, uint8_t ** out, size_t * out_len) +{ + //todo: allocate proper buffer for holding JPEG data + //this should be enough for CIF frame size + int jpg_buf_len = 64*1024; + + + uint8_t * jpg_buf = (uint8_t *)_malloc(jpg_buf_len); + if(jpg_buf == NULL) { + ESP_LOGE(TAG, "JPG buffer malloc failed"); + return false; + } + memory_stream dst_stream(jpg_buf, jpg_buf_len); + + if(!convert_image(src, width, height, format, quality, &dst_stream)) { + free(jpg_buf); + return false; + } + + *out = jpg_buf; + *out_len = dst_stream.get_size(); + return true; +} + +bool frame2jpg(camera_fb_t * fb, uint8_t quality, uint8_t ** out, size_t * out_len) +{ + return fmt2jpg(fb->buf, fb->len, fb->width, fb->height, fb->format, quality, out, out_len); +} diff --git a/esp32-cam/twi.c b/esp32-cam/twi.c new file mode 100644 index 0000000..25d71fc --- /dev/null +++ b/esp32-cam/twi.c @@ -0,0 +1,432 @@ +/* + si2c.c - Software I2C library for ESP31B + + Copyright (c) 2015 Hristo Gochkov. All rights reserved. + This file is part of the ESP31B core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include +#include +#include "twi.h" +#include "soc/gpio_reg.h" +#include "soc/gpio_struct.h" +#include "soc/io_mux_reg.h" +#include "driver/rtc_io.h" +#include + + +#define LOW 0x0 +#define HIGH 0x1 + +//GPIO FUNCTIONS +#define INPUT 0x01 +#define OUTPUT 0x02 +#define PULLUP 0x04 +#define INPUT_PULLUP 0x05 +#define PULLDOWN 0x08 +#define INPUT_PULLDOWN 0x09 +#define OPEN_DRAIN 0x10 +#define OUTPUT_OPEN_DRAIN 0x12 +#define SPECIAL 0xF0 +#define FUNCTION_1 0x00 +#define FUNCTION_2 0x20 +#define FUNCTION_3 0x40 +#define FUNCTION_4 0x60 +#define FUNCTION_5 0x80 +#define FUNCTION_6 0xA0 + +#define ESP_REG(addr) *((volatile uint32_t *)(addr)) + +const uint8_t pin_to_mux[40] = { 0x44, 0x88, 0x40, 0x84, 0x48, 0x6c, 0x60, 0x64, 0x68, 0x54, 0x58, 0x5c, 0x34, 0x38, 0x30, 0x3c, 0x4c, 0x50, 0x70, 0x74, 0x78, 0x7c, 0x80, 0x8c, 0, 0x24, 0x28, 0x2c, 0, 0, 0, 0, 0x1c, 0x20, 0x14, 0x18, 0x04, 0x08, 0x0c, 0x10}; + +static void pinMode(uint8_t pin, uint8_t mode) +{ + if(pin >= 40) { + return; + } + + uint32_t rtc_reg = rtc_gpio_desc[pin].reg; + + //RTC pins PULL settings + if(rtc_reg) { + //lock rtc + ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].mux); + if(mode & PULLUP) { + ESP_REG(rtc_reg) = (ESP_REG(rtc_reg) | rtc_gpio_desc[pin].pullup) & ~(rtc_gpio_desc[pin].pulldown); + } else if(mode & PULLDOWN) { + ESP_REG(rtc_reg) = (ESP_REG(rtc_reg) | rtc_gpio_desc[pin].pulldown) & ~(rtc_gpio_desc[pin].pullup); + } else { + ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].pullup | rtc_gpio_desc[pin].pulldown); + } + //unlock rtc + } + + uint32_t pinFunction = 0, pinControl = 0; + + //lock gpio + if(mode & INPUT) { + if(pin < 32) { + GPIO.enable_w1tc = BIT(pin); + } else { + GPIO.enable1_w1tc.val = BIT(pin - 32); + } + } else if(mode & OUTPUT) { + if(pin > 33) { + //unlock gpio + return;//pins above 33 can be only inputs + } else if(pin < 32) { + GPIO.enable_w1ts = BIT(pin); + } else { + GPIO.enable1_w1ts.val = BIT(pin - 32); + } + } + + if(mode & PULLUP) { + pinFunction |= FUN_PU; + } else if(mode & PULLDOWN) { + pinFunction |= FUN_PD; + } + + pinFunction |= ((uint32_t)2 << FUN_DRV_S);//what are the drivers? + pinFunction |= FUN_IE;//input enable but required for output as well? + + if(mode & (INPUT | OUTPUT)) { + pinFunction |= ((uint32_t)2 << MCU_SEL_S); + } else if(mode == SPECIAL) { + pinFunction |= ((uint32_t)(((pin)==1||(pin)==3)?0:1) << MCU_SEL_S); + } else { + pinFunction |= ((uint32_t)(mode >> 5) << MCU_SEL_S); + } + + ESP_REG(DR_REG_IO_MUX_BASE + pin_to_mux[pin]) = pinFunction; + + if(mode & OPEN_DRAIN) { + pinControl = (1 << GPIO_PIN0_PAD_DRIVER_S); + } + + GPIO.pin[pin].val = pinControl; + //unlock gpio +} + +static void digitalWrite(uint8_t pin, uint8_t val) +{ + if(val) { + if(pin < 32) { + GPIO.out_w1ts = BIT(pin); + } else if(pin < 34) { + GPIO.out1_w1ts.val = BIT(pin - 32); + } + } else { + if(pin < 32) { + GPIO.out_w1tc = BIT(pin); + } else if(pin < 34) { + GPIO.out1_w1tc.val = BIT(pin - 32); + } + } +} + + +unsigned char twi_dcount = 18; +static unsigned char twi_sda, twi_scl; + + +static inline void SDA_LOW() +{ + //Enable SDA (becomes output and since GPO is 0 for the pin, + // it will pull the line low) + if (twi_sda < 32) { + GPIO.enable_w1ts = BIT(twi_sda); + } else { + GPIO.enable1_w1ts.val = BIT(twi_sda - 32); + } +} + +static inline void SDA_HIGH() +{ + //Disable SDA (becomes input and since it has pullup it will go high) + if (twi_sda < 32) { + GPIO.enable_w1tc = BIT(twi_sda); + } else { + GPIO.enable1_w1tc.val = BIT(twi_sda - 32); + } +} + +static inline uint32_t SDA_READ() +{ + if (twi_sda < 32) { + return (GPIO.in & BIT(twi_sda)) != 0; + } else { + return (GPIO.in1.val & BIT(twi_sda - 32)) != 0; + } +} + +static void SCL_LOW() +{ + if (twi_scl < 32) { + GPIO.enable_w1ts = BIT(twi_scl); + } else { + GPIO.enable1_w1ts.val = BIT(twi_scl - 32); + } +} + +static void SCL_HIGH() +{ + if (twi_scl < 32) { + GPIO.enable_w1tc = BIT(twi_scl); + } else { + GPIO.enable1_w1tc.val = BIT(twi_scl - 32); + } +} + +static uint32_t SCL_READ() +{ + if (twi_scl < 32) { + return (GPIO.in & BIT(twi_scl)) != 0; + } else { + return (GPIO.in1.val & BIT(twi_scl - 32)) != 0; + } +} + + +#ifndef FCPU80 +#define FCPU80 80000000L +#endif + +#if F_CPU == FCPU80 +#define TWI_CLOCK_STRETCH 800 +#else +#define TWI_CLOCK_STRETCH 1600 +#endif + +void twi_setClock(unsigned int freq) +{ +#if F_CPU == FCPU80 + if(freq <= 100000) { + twi_dcount = 19; //about 100KHz + } else if(freq <= 200000) { + twi_dcount = 8; //about 200KHz + } else if(freq <= 300000) { + twi_dcount = 3; //about 300KHz + } else if(freq <= 400000) { + twi_dcount = 1; //about 400KHz + } else { + twi_dcount = 1; //about 400KHz + } +#else + if(freq <= 100000) { + twi_dcount = 32; //about 100KHz + } else if(freq <= 200000) { + twi_dcount = 14; //about 200KHz + } else if(freq <= 300000) { + twi_dcount = 8; //about 300KHz + } else if(freq <= 400000) { + twi_dcount = 5; //about 400KHz + } else if(freq <= 500000) { + twi_dcount = 3; //about 500KHz + } else if(freq <= 600000) { + twi_dcount = 2; //about 600KHz + } else { + twi_dcount = 1; //about 700KHz + } +#endif +} + +void twi_init(unsigned char sda, unsigned char scl) +{ + twi_sda = sda; + twi_scl = scl; + pinMode(twi_sda, OUTPUT); + pinMode(twi_scl, OUTPUT); + + digitalWrite(twi_sda, 0); + digitalWrite(twi_scl, 0); + + pinMode(twi_sda, INPUT_PULLUP); + pinMode(twi_scl, INPUT_PULLUP); + twi_setClock(100000); +} + +void twi_stop(void) +{ + pinMode(twi_sda, INPUT); + pinMode(twi_scl, INPUT); +} + +static void twi_delay(unsigned char v) +{ + unsigned int i; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" + unsigned int reg; + for(i=0; i= 4 + timer_conf.clk_cfg = LEDC_AUTO_CLK; +#endif + timer_conf.timer_num = (ledc_timer_t)ledc_timer; + esp_err_t err = ledc_timer_config(&timer_conf); + if (err != ESP_OK) { + ESP_LOGE(TAG, "ledc_timer_config failed for freq %d, rc=%x", xclk_freq_hz, err); + } + return err; +} + +esp_err_t camera_enable_out_clock(camera_config_t* config) +{ + periph_module_enable(PERIPH_LEDC_MODULE); + + esp_err_t err = xclk_timer_conf(config->ledc_timer, config->xclk_freq_hz); + if (err != ESP_OK) { + ESP_LOGE(TAG, "ledc_timer_config failed, rc=%x", err); + return err; + } + + ledc_channel_config_t ch_conf; + ch_conf.gpio_num = config->pin_xclk; + ch_conf.speed_mode = LEDC_HIGH_SPEED_MODE; + ch_conf.channel = config->ledc_channel; + ch_conf.intr_type = LEDC_INTR_DISABLE; + ch_conf.timer_sel = config->ledc_timer; + ch_conf.duty = 2; + ch_conf.hpoint = 0; + err = ledc_channel_config(&ch_conf); + if (err != ESP_OK) { + ESP_LOGE(TAG, "ledc_channel_config failed, rc=%x", err); + return err; + } + return ESP_OK; +} + +void camera_disable_out_clock() +{ + periph_module_disable(PERIPH_LEDC_MODULE); +} diff --git a/esp32-cam/xclk.h b/esp32-cam/xclk.h new file mode 100644 index 0000000..15ed736 --- /dev/null +++ b/esp32-cam/xclk.h @@ -0,0 +1,7 @@ +#pragma once + +#include "camera_common.h" + +esp_err_t camera_enable_out_clock(); + +void camera_disable_out_clock(); diff --git a/esp32-cam/yuv.c b/esp32-cam/yuv.c new file mode 100644 index 0000000..46034cc --- /dev/null +++ b/esp32-cam/yuv.c @@ -0,0 +1,298 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "yuv.h" +#include "esp_attr.h" + +typedef struct { + int16_t vY; + int16_t vVr; + int16_t vVg; + int16_t vUg; + int16_t vUb; +} yuv_table_row; + +static const yuv_table_row yuv_table[256] = { + // Y Vr Vg Ug Ub // # + { -18, -204, 50, 104, -258 }, // 0 + { -17, -202, 49, 103, -256 }, // 1 + { -16, -201, 49, 102, -254 }, // 2 + { -15, -199, 48, 101, -252 }, // 3 + { -13, -197, 48, 100, -250 }, // 4 + { -12, -196, 48, 99, -248 }, // 5 + { -11, -194, 47, 99, -246 }, // 6 + { -10, -193, 47, 98, -244 }, // 7 + { -9, -191, 46, 97, -242 }, // 8 + { -8, -189, 46, 96, -240 }, // 9 + { -6, -188, 46, 95, -238 }, // 10 + { -5, -186, 45, 95, -236 }, // 11 + { -4, -185, 45, 94, -234 }, // 12 + { -3, -183, 44, 93, -232 }, // 13 + { -2, -181, 44, 92, -230 }, // 14 + { -1, -180, 44, 91, -228 }, // 15 + { 0, -178, 43, 91, -226 }, // 16 + { 1, -177, 43, 90, -223 }, // 17 + { 2, -175, 43, 89, -221 }, // 18 + { 3, -173, 42, 88, -219 }, // 19 + { 4, -172, 42, 87, -217 }, // 20 + { 5, -170, 41, 86, -215 }, // 21 + { 6, -169, 41, 86, -213 }, // 22 + { 8, -167, 41, 85, -211 }, // 23 + { 9, -165, 40, 84, -209 }, // 24 + { 10, -164, 40, 83, -207 }, // 25 + { 11, -162, 39, 82, -205 }, // 26 + { 12, -161, 39, 82, -203 }, // 27 + { 13, -159, 39, 81, -201 }, // 28 + { 15, -158, 38, 80, -199 }, // 29 + { 16, -156, 38, 79, -197 }, // 30 + { 17, -154, 37, 78, -195 }, // 31 + { 18, -153, 37, 78, -193 }, // 32 + { 19, -151, 37, 77, -191 }, // 33 + { 20, -150, 36, 76, -189 }, // 34 + { 22, -148, 36, 75, -187 }, // 35 + { 23, -146, 35, 74, -185 }, // 36 + { 24, -145, 35, 73, -183 }, // 37 + { 25, -143, 35, 73, -181 }, // 38 + { 26, -142, 34, 72, -179 }, // 39 + { 27, -140, 34, 71, -177 }, // 40 + { 29, -138, 34, 70, -175 }, // 41 + { 30, -137, 33, 69, -173 }, // 42 + { 31, -135, 33, 69, -171 }, // 43 + { 32, -134, 32, 68, -169 }, // 44 + { 33, -132, 32, 67, -167 }, // 45 + { 34, -130, 32, 66, -165 }, // 46 + { 36, -129, 31, 65, -163 }, // 47 + { 37, -127, 31, 65, -161 }, // 48 + { 38, -126, 30, 64, -159 }, // 49 + { 39, -124, 30, 63, -157 }, // 50 + { 40, -122, 30, 62, -155 }, // 51 + { 41, -121, 29, 61, -153 }, // 52 + { 43, -119, 29, 60, -151 }, // 53 + { 44, -118, 28, 60, -149 }, // 54 + { 45, -116, 28, 59, -147 }, // 55 + { 46, -114, 28, 58, -145 }, // 56 + { 47, -113, 27, 57, -143 }, // 57 + { 48, -111, 27, 56, -141 }, // 58 + { 50, -110, 26, 56, -139 }, // 59 + { 51, -108, 26, 55, -137 }, // 60 + { 52, -106, 26, 54, -135 }, // 61 + { 53, -105, 25, 53, -133 }, // 62 + { 54, -103, 25, 52, -131 }, // 63 + { 55, -102, 25, 52, -129 }, // 64 + { 57, -100, 24, 51, -127 }, // 65 + { 58, -98, 24, 50, -125 }, // 66 + { 59, -97, 23, 49, -123 }, // 67 + { 60, -95, 23, 48, -121 }, // 68 + { 61, -94, 23, 47, -119 }, // 69 + { 62, -92, 22, 47, -117 }, // 70 + { 64, -90, 22, 46, -115 }, // 71 + { 65, -89, 21, 45, -113 }, // 72 + { 66, -87, 21, 44, -110 }, // 73 + { 67, -86, 21, 43, -108 }, // 74 + { 68, -84, 20, 43, -106 }, // 75 + { 69, -82, 20, 42, -104 }, // 76 + { 71, -81, 19, 41, -102 }, // 77 + { 72, -79, 19, 40, -100 }, // 78 + { 73, -78, 19, 39, -98 }, // 79 + { 74, -76, 18, 39, -96 }, // 80 + { 75, -75, 18, 38, -94 }, // 81 + { 76, -73, 17, 37, -92 }, // 82 + { 77, -71, 17, 36, -90 }, // 83 + { 79, -70, 17, 35, -88 }, // 84 + { 80, -68, 16, 34, -86 }, // 85 + { 81, -67, 16, 34, -84 }, // 86 + { 82, -65, 16, 33, -82 }, // 87 + { 83, -63, 15, 32, -80 }, // 88 + { 84, -62, 15, 31, -78 }, // 89 + { 86, -60, 14, 30, -76 }, // 90 + { 87, -59, 14, 30, -74 }, // 91 + { 88, -57, 14, 29, -72 }, // 92 + { 89, -55, 13, 28, -70 }, // 93 + { 90, -54, 13, 27, -68 }, // 94 + { 91, -52, 12, 26, -66 }, // 95 + { 93, -51, 12, 26, -64 }, // 96 + { 94, -49, 12, 25, -62 }, // 97 + { 95, -47, 11, 24, -60 }, // 98 + { 96, -46, 11, 23, -58 }, // 99 + { 97, -44, 10, 22, -56 }, // 100 + { 98, -43, 10, 21, -54 }, // 101 + { 100, -41, 10, 21, -52 }, // 102 + { 101, -39, 9, 20, -50 }, // 103 + { 102, -38, 9, 19, -48 }, // 104 + { 103, -36, 8, 18, -46 }, // 105 + { 104, -35, 8, 17, -44 }, // 106 + { 105, -33, 8, 17, -42 }, // 107 + { 107, -31, 7, 16, -40 }, // 108 + { 108, -30, 7, 15, -38 }, // 109 + { 109, -28, 7, 14, -36 }, // 110 + { 110, -27, 6, 13, -34 }, // 111 + { 111, -25, 6, 13, -32 }, // 112 + { 112, -23, 5, 12, -30 }, // 113 + { 114, -22, 5, 11, -28 }, // 114 + { 115, -20, 5, 10, -26 }, // 115 + { 116, -19, 4, 9, -24 }, // 116 + { 117, -17, 4, 8, -22 }, // 117 + { 118, -15, 3, 8, -20 }, // 118 + { 119, -14, 3, 7, -18 }, // 119 + { 121, -12, 3, 6, -16 }, // 120 + { 122, -11, 2, 5, -14 }, // 121 + { 123, -9, 2, 4, -12 }, // 122 + { 124, -7, 1, 4, -10 }, // 123 + { 125, -6, 1, 3, -8 }, // 124 + { 126, -4, 1, 2, -6 }, // 125 + { 128, -3, 0, 1, -4 }, // 126 + { 129, -1, 0, 0, -2 }, // 127 + { 130, 0, 0, 0, 0 }, // 128 + { 131, 1, 0, 0, 2 }, // 129 + { 132, 3, 0, -1, 4 }, // 130 + { 133, 4, -1, -2, 6 }, // 131 + { 135, 6, -1, -3, 8 }, // 132 + { 136, 7, -1, -4, 10 }, // 133 + { 137, 9, -2, -4, 12 }, // 134 + { 138, 11, -2, -5, 14 }, // 135 + { 139, 12, -3, -6, 16 }, // 136 + { 140, 14, -3, -7, 18 }, // 137 + { 142, 15, -3, -8, 20 }, // 138 + { 143, 17, -4, -8, 22 }, // 139 + { 144, 19, -4, -9, 24 }, // 140 + { 145, 20, -5, -10, 26 }, // 141 + { 146, 22, -5, -11, 28 }, // 142 + { 147, 23, -5, -12, 30 }, // 143 + { 148, 25, -6, -13, 32 }, // 144 + { 150, 27, -6, -13, 34 }, // 145 + { 151, 28, -7, -14, 36 }, // 146 + { 152, 30, -7, -15, 38 }, // 147 + { 153, 31, -7, -16, 40 }, // 148 + { 154, 33, -8, -17, 42 }, // 149 + { 155, 35, -8, -17, 44 }, // 150 + { 157, 36, -8, -18, 46 }, // 151 + { 158, 38, -9, -19, 48 }, // 152 + { 159, 39, -9, -20, 50 }, // 153 + { 160, 41, -10, -21, 52 }, // 154 + { 161, 43, -10, -21, 54 }, // 155 + { 162, 44, -10, -22, 56 }, // 156 + { 164, 46, -11, -23, 58 }, // 157 + { 165, 47, -11, -24, 60 }, // 158 + { 166, 49, -12, -25, 62 }, // 159 + { 167, 51, -12, -26, 64 }, // 160 + { 168, 52, -12, -26, 66 }, // 161 + { 169, 54, -13, -27, 68 }, // 162 + { 171, 55, -13, -28, 70 }, // 163 + { 172, 57, -14, -29, 72 }, // 164 + { 173, 59, -14, -30, 74 }, // 165 + { 174, 60, -14, -30, 76 }, // 166 + { 175, 62, -15, -31, 78 }, // 167 + { 176, 63, -15, -32, 80 }, // 168 + { 178, 65, -16, -33, 82 }, // 169 + { 179, 67, -16, -34, 84 }, // 170 + { 180, 68, -16, -34, 86 }, // 171 + { 181, 70, -17, -35, 88 }, // 172 + { 182, 71, -17, -36, 90 }, // 173 + { 183, 73, -17, -37, 92 }, // 174 + { 185, 75, -18, -38, 94 }, // 175 + { 186, 76, -18, -39, 96 }, // 176 + { 187, 78, -19, -39, 98 }, // 177 + { 188, 79, -19, -40, 100 }, // 178 + { 189, 81, -19, -41, 102 }, // 179 + { 190, 82, -20, -42, 104 }, // 180 + { 192, 84, -20, -43, 106 }, // 181 + { 193, 86, -21, -43, 108 }, // 182 + { 194, 87, -21, -44, 110 }, // 183 + { 195, 89, -21, -45, 113 }, // 184 + { 196, 90, -22, -46, 115 }, // 185 + { 197, 92, -22, -47, 117 }, // 186 + { 199, 94, -23, -47, 119 }, // 187 + { 200, 95, -23, -48, 121 }, // 188 + { 201, 97, -23, -49, 123 }, // 189 + { 202, 98, -24, -50, 125 }, // 190 + { 203, 100, -24, -51, 127 }, // 191 + { 204, 102, -25, -52, 129 }, // 192 + { 206, 103, -25, -52, 131 }, // 193 + { 207, 105, -25, -53, 133 }, // 194 + { 208, 106, -26, -54, 135 }, // 195 + { 209, 108, -26, -55, 137 }, // 196 + { 210, 110, -26, -56, 139 }, // 197 + { 211, 111, -27, -56, 141 }, // 198 + { 213, 113, -27, -57, 143 }, // 199 + { 214, 114, -28, -58, 145 }, // 200 + { 215, 116, -28, -59, 147 }, // 201 + { 216, 118, -28, -60, 149 }, // 202 + { 217, 119, -29, -60, 151 }, // 203 + { 218, 121, -29, -61, 153 }, // 204 + { 219, 122, -30, -62, 155 }, // 205 + { 221, 124, -30, -63, 157 }, // 206 + { 222, 126, -30, -64, 159 }, // 207 + { 223, 127, -31, -65, 161 }, // 208 + { 224, 129, -31, -65, 163 }, // 209 + { 225, 130, -32, -66, 165 }, // 210 + { 226, 132, -32, -67, 167 }, // 211 + { 228, 134, -32, -68, 169 }, // 212 + { 229, 135, -33, -69, 171 }, // 213 + { 230, 137, -33, -69, 173 }, // 214 + { 231, 138, -34, -70, 175 }, // 215 + { 232, 140, -34, -71, 177 }, // 216 + { 233, 142, -34, -72, 179 }, // 217 + { 235, 143, -35, -73, 181 }, // 218 + { 236, 145, -35, -73, 183 }, // 219 + { 237, 146, -35, -74, 185 }, // 220 + { 238, 148, -36, -75, 187 }, // 221 + { 239, 150, -36, -76, 189 }, // 222 + { 240, 151, -37, -77, 191 }, // 223 + { 242, 153, -37, -78, 193 }, // 224 + { 243, 154, -37, -78, 195 }, // 225 + { 244, 156, -38, -79, 197 }, // 226 + { 245, 158, -38, -80, 199 }, // 227 + { 246, 159, -39, -81, 201 }, // 228 + { 247, 161, -39, -82, 203 }, // 229 + { 249, 162, -39, -82, 205 }, // 230 + { 250, 164, -40, -83, 207 }, // 231 + { 251, 165, -40, -84, 209 }, // 232 + { 252, 167, -41, -85, 211 }, // 233 + { 253, 169, -41, -86, 213 }, // 234 + { 254, 170, -41, -86, 215 }, // 235 + { 256, 172, -42, -87, 217 }, // 236 + { 257, 173, -42, -88, 219 }, // 237 + { 258, 175, -43, -89, 221 }, // 238 + { 259, 177, -43, -90, 223 }, // 239 + { 260, 178, -43, -91, 226 }, // 240 + { 261, 180, -44, -91, 228 }, // 241 + { 263, 181, -44, -92, 230 }, // 242 + { 264, 183, -44, -93, 232 }, // 243 + { 265, 185, -45, -94, 234 }, // 244 + { 266, 186, -45, -95, 236 }, // 245 + { 267, 188, -46, -95, 238 }, // 246 + { 268, 189, -46, -96, 240 }, // 247 + { 270, 191, -46, -97, 242 }, // 248 + { 271, 193, -47, -98, 244 }, // 249 + { 272, 194, -47, -99, 246 }, // 250 + { 273, 196, -48, -99, 248 }, // 251 + { 274, 197, -48, -100, 250 }, // 252 + { 275, 199, -48, -101, 252 }, // 253 + { 277, 201, -49, -102, 254 }, // 254 + { 278, 202, -49, -103, 256 } // 255 +}; + +#define YUYV_CONSTRAIN(v) ((v)<0)?0:(((v)>255)?255:(v)) + +void IRAM_ATTR yuv2rgb(uint8_t y, uint8_t u, uint8_t v, uint8_t *r, uint8_t *g, uint8_t *b) +{ + int16_t ri, gi, bi; + + ri = yuv_table[y].vY + yuv_table[v].vVr; + gi = yuv_table[y].vY + yuv_table[u].vUg + yuv_table[v].vVg; + bi = yuv_table[y].vY + yuv_table[u].vUb; + + *r = YUYV_CONSTRAIN(ri); + *g = YUYV_CONSTRAIN(gi); + *b = YUYV_CONSTRAIN(bi); +} diff --git a/esp32-cam/yuv.h b/esp32-cam/yuv.h new file mode 100644 index 0000000..c5a0577 --- /dev/null +++ b/esp32-cam/yuv.h @@ -0,0 +1,29 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef _CONVERSIONS_YUV_H_ +#define _CONVERSIONS_YUV_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +void yuv2rgb(uint8_t y, uint8_t u, uint8_t v, uint8_t *r, uint8_t *g, uint8_t *b); + +#ifdef __cplusplus +} +#endif + +#endif /* _CONVERSIONS_YUV_H_ */