From ca6babbd7c4546c01cd5b87162c90329c11ee99b Mon Sep 17 00:00:00 2001 From: Anatoli Arkhipenko Date: Thu, 1 Jul 2021 10:51:38 -0400 Subject: [PATCH] July 2021 update --- README.md | 18 +- esp32-cam-rtos-allframes/.gitignore | 1 + esp32-cam-rtos-allframes/CMakeLists.txt | 61 + esp32-cam-rtos-allframes/Kconfig | 120 +- esp32-cam-rtos-allframes/README.md | 352 +++- esp32-cam-rtos-allframes/cam_hal.c | 472 +++++ esp32-cam-rtos-allframes/cam_hal.h | 60 + esp32-cam-rtos-allframes/camera.c | 1592 ----------------- esp32-cam-rtos-allframes/camera_common.h | 29 +- esp32-cam-rtos-allframes/component.mk | 4 + .../esp32-camera-master.zip | Bin 0 -> 122571 bytes esp32-cam-rtos-allframes/esp_camera.c | 430 +++++ esp32-cam-rtos-allframes/esp_camera.h | 14 +- esp32-cam-rtos-allframes/esp_jpg_decode.c | 6 +- esp32-cam-rtos-allframes/img_converters.h | 3 + esp32-cam-rtos-allframes/library.json | 26 + esp32-cam-rtos-allframes/ll_cam.c | 515 ++++++ esp32-cam-rtos-allframes/ll_cam.h | 136 ++ esp32-cam-rtos-allframes/nt99141.c | 29 +- esp32-cam-rtos-allframes/ov2640.c | 36 +- esp32-cam-rtos-allframes/ov3660.c | 26 +- esp32-cam-rtos-allframes/ov5640.c | 15 +- esp32-cam-rtos-allframes/sccb.c | 35 +- esp32-cam-rtos-allframes/sccb.h | 1 + esp32-cam-rtos-allframes/sensor.c | 9 + esp32-cam-rtos-allframes/sensor.h | 55 +- esp32-cam-rtos-allframes/to_bmp.c | 67 + esp32-cam-rtos-allframes/to_jpg.cpp | 8 +- esp32-cam-rtos-allframes/twi.c | 432 ----- esp32-cam-rtos-allframes/twi.h | 38 - esp32-cam-rtos-allframes/xclk.c | 12 +- esp32-cam-rtos/.gitignore | 1 + esp32-cam-rtos/CMakeLists.txt | 61 + esp32-cam-rtos/Kconfig | 120 +- esp32-cam-rtos/README.md | 352 +++- esp32-cam-rtos/cam_hal.c | 472 +++++ esp32-cam-rtos/cam_hal.h | 60 + esp32-cam-rtos/camera.c | 1592 ----------------- esp32-cam-rtos/camera_common.h | 29 +- esp32-cam-rtos/component.mk | 4 + esp32-cam-rtos/esp32-cam-rtos.ino | 7 +- esp32-cam-rtos/esp32-camera-master.zip | Bin 0 -> 122571 bytes esp32-cam-rtos/esp_camera.c | 430 +++++ esp32-cam-rtos/esp_camera.h | 14 +- esp32-cam-rtos/esp_jpg_decode.c | 6 +- esp32-cam-rtos/img_converters.h | 3 + esp32-cam-rtos/library.json | 26 + esp32-cam-rtos/ll_cam.c | 515 ++++++ esp32-cam-rtos/ll_cam.h | 136 ++ esp32-cam-rtos/nt99141.c | 29 +- esp32-cam-rtos/ov2640.c | 36 +- esp32-cam-rtos/ov3660.c | 26 +- esp32-cam-rtos/ov5640.c | 15 +- esp32-cam-rtos/sccb.c | 35 +- esp32-cam-rtos/sccb.h | 1 + esp32-cam-rtos/sensor.c | 9 + esp32-cam-rtos/sensor.h | 55 +- esp32-cam-rtos/to_bmp.c | 67 + esp32-cam-rtos/to_jpg.cpp | 8 +- esp32-cam-rtos/twi.c | 432 ----- esp32-cam-rtos/twi.h | 38 - esp32-cam-rtos/xclk.c | 12 +- esp32-cam/.gitignore | 1 + esp32-cam/CMakeLists.txt | 61 + esp32-cam/Kconfig | 71 + esp32-cam/LICENSE | 202 +++ esp32-cam/README.md | 352 +++- esp32-cam/cam_hal.c | 472 +++++ esp32-cam/cam_hal.h | 60 + esp32-cam/camera.c | 1592 ----------------- esp32-cam/camera_common.h | 29 +- esp32-cam/component.mk | 4 + esp32-cam/esp32-camera-master.zip | Bin 0 -> 122571 bytes esp32-cam/esp_camera.c | 430 +++++ esp32-cam/esp_camera.h | 14 +- esp32-cam/esp_jpg_decode.c | 6 +- esp32-cam/img_converters.h | 3 + esp32-cam/library.json | 26 + esp32-cam/ll_cam.c | 515 ++++++ esp32-cam/ll_cam.h | 136 ++ esp32-cam/nt99141.c | 29 +- esp32-cam/ov2640.c | 36 +- esp32-cam/ov3660.c | 26 +- esp32-cam/ov5640.c | 15 +- esp32-cam/sccb.c | 35 +- esp32-cam/sccb.h | 1 + esp32-cam/sensor.c | 9 + esp32-cam/sensor.h | 55 +- esp32-cam/to_bmp.c | 67 + esp32-cam/to_jpg.cpp | 8 +- esp32-cam/twi.c | 432 ----- esp32-cam/twi.h | 38 - esp32-cam/xclk.c | 12 +- 93 files changed, 7302 insertions(+), 6628 deletions(-) create mode 100644 esp32-cam-rtos-allframes/.gitignore create mode 100644 esp32-cam-rtos-allframes/CMakeLists.txt create mode 100644 esp32-cam-rtos-allframes/cam_hal.c create mode 100644 esp32-cam-rtos-allframes/cam_hal.h delete mode 100644 esp32-cam-rtos-allframes/camera.c create mode 100644 esp32-cam-rtos-allframes/component.mk create mode 100644 esp32-cam-rtos-allframes/esp32-camera-master.zip create mode 100644 esp32-cam-rtos-allframes/esp_camera.c create mode 100644 esp32-cam-rtos-allframes/library.json create mode 100644 esp32-cam-rtos-allframes/ll_cam.c create mode 100644 esp32-cam-rtos-allframes/ll_cam.h delete mode 100644 esp32-cam-rtos-allframes/twi.c delete mode 100644 esp32-cam-rtos-allframes/twi.h create mode 100644 esp32-cam-rtos/.gitignore create mode 100644 esp32-cam-rtos/CMakeLists.txt create mode 100644 esp32-cam-rtos/cam_hal.c create mode 100644 esp32-cam-rtos/cam_hal.h delete mode 100644 esp32-cam-rtos/camera.c create mode 100644 esp32-cam-rtos/component.mk create mode 100644 esp32-cam-rtos/esp32-camera-master.zip create mode 100644 esp32-cam-rtos/esp_camera.c create mode 100644 esp32-cam-rtos/library.json create mode 100644 esp32-cam-rtos/ll_cam.c create mode 100644 esp32-cam-rtos/ll_cam.h delete mode 100644 esp32-cam-rtos/twi.c delete mode 100644 esp32-cam-rtos/twi.h create mode 100644 esp32-cam/.gitignore create mode 100644 esp32-cam/CMakeLists.txt create mode 100644 esp32-cam/Kconfig create mode 100644 esp32-cam/LICENSE create mode 100644 esp32-cam/cam_hal.c create mode 100644 esp32-cam/cam_hal.h delete mode 100644 esp32-cam/camera.c create mode 100644 esp32-cam/component.mk create mode 100644 esp32-cam/esp32-camera-master.zip create mode 100644 esp32-cam/esp_camera.c create mode 100644 esp32-cam/library.json create mode 100644 esp32-cam/ll_cam.c create mode 100644 esp32-cam/ll_cam.h delete mode 100644 esp32-cam/twi.c delete mode 100644 esp32-cam/twi.h diff --git a/README.md b/README.md index d3a52ae..b50862b 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,13 @@ -**Updated on 2021-03-03:** +**Updated on 2021-07-01:** -- Recompiled with ESP32 Arduino Core 1.0.5 +- OP updated to account for recent repo changes by Espressif -- Updated with latest ESP CAM drivers +- Recompiled with ESP32 Arduino Core 1.0.6 -- Added vertical flip capability +- Updated with latest ESP CAM drivers @@ -41,13 +41,17 @@ All captured frames are stored in PSRAM (until you run out of memory) and served 1. Download latest ZIP file from https://github.com/espressif/esp32-camera.git into the esp32-cam subfolder -2. Delete `examples` folder from the archive +2. Delete `examples` and `test` folders from the archive + +3. Delete **ALL FILES** in the sketch folder except `esp32-cam*.ino` and `camera_pins.h` + +4. In the archive subfolder `esp32-camera-master/target` delete subfolders for `esp32s2` and `esp32s3` - I have not tested with those ones -3. unzip using `unzip -j esp32-cam-master.zip` command. This will place all files in the same folder +5. unzip using `unzip -jo esp32-camera-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. + **NOTE:** please observe the `-jo` flag: the sketch assumes all files are in the same folder and will overwrite the existing old files without asking for confirmation. diff --git a/esp32-cam-rtos-allframes/.gitignore b/esp32-cam-rtos-allframes/.gitignore new file mode 100644 index 0000000..5509140 --- /dev/null +++ b/esp32-cam-rtos-allframes/.gitignore @@ -0,0 +1 @@ +*.DS_Store diff --git a/esp32-cam-rtos-allframes/CMakeLists.txt b/esp32-cam-rtos-allframes/CMakeLists.txt new file mode 100644 index 0000000..536f6ba --- /dev/null +++ b/esp32-cam-rtos-allframes/CMakeLists.txt @@ -0,0 +1,61 @@ +if(IDF_TARGET STREQUAL "esp32" OR IDF_TARGET STREQUAL "esp32s2" OR IDF_TARGET STREQUAL "esp32s3") + set(COMPONENT_SRCS + driver/esp_camera.c + driver/cam_hal.c + driver/sccb.c + driver/sensor.c + sensors/ov2640.c + sensors/ov3660.c + sensors/ov5640.c + sensors/ov7725.c + sensors/ov7670.c + sensors/nt99141.c + conversions/yuv.c + conversions/to_jpg.cpp + conversions/to_bmp.c + conversions/jpge.cpp + conversions/esp_jpg_decode.c + ) + + set(COMPONENT_ADD_INCLUDEDIRS + driver/include + conversions/include + ) + + set(COMPONENT_PRIV_INCLUDEDIRS + driver/private_include + sensors/private_include + conversions/private_include + target/private_include + ) + + if(IDF_TARGET STREQUAL "esp32") + list(APPEND COMPONENT_SRCS + target/xclk.c + target/esp32/ll_cam.c + ) + endif() + + if(IDF_TARGET STREQUAL "esp32s2") + list(APPEND COMPONENT_SRCS + target/xclk.c + target/esp32s2/ll_cam.c + target/esp32s2/tjpgd.c + ) + + list(APPEND COMPONENT_PRIV_INCLUDEDIRS + target/esp32s2/private_include + ) + endif() + + if(IDF_TARGET STREQUAL "esp32s3") + list(APPEND COMPONENT_SRCS + target/esp32s3/ll_cam.c + ) + endif() + + set(COMPONENT_REQUIRES driver) + set(COMPONENT_PRIV_REQUIRES freertos nvs_flash) + + register_component() +endif() diff --git a/esp32-cam-rtos-allframes/Kconfig b/esp32-cam-rtos-allframes/Kconfig index 78fc607..49813f6 100644 --- a/esp32-cam-rtos-allframes/Kconfig +++ b/esp32-cam-rtos-allframes/Kconfig @@ -1,65 +1,71 @@ menu "Camera configuration" - -config OV2640_SUPPORT - bool "OV2640 Support" - default y - help - Enable this option if you want to use the OV2640. - Disable this option to save memory. -config OV7725_SUPPORT - bool "OV7725 Support" - default n - help - Enable this option if you want to use the OV7725. - Disable this option to save memory. - -config OV3660_SUPPORT - bool "OV3660 Support" - default y - help - Enable this option if you want to use the OV3360. - Disable this option to save memory. - -config OV5640_SUPPORT - bool "OV5640 Support" - default y - help - Enable this option if you want to use the OV5640. - Disable this option to save memory. - -config SCCB_HARDWARE_I2C - bool "Use hardware I2C for SCCB" - default y - help - Enable this option if you want to use hardware I2C to control the camera. - Disable this option to use software I2C. - -choice SCCB_HARDWARE_I2C_PORT - bool "I2C peripheral to use for SCCB" - depends on SCCB_HARDWARE_I2C - default SCCB_HARDWARE_I2C_PORT1 + config OV7670_SUPPORT + bool "Support OV7670 VGA" + default y + help + Enable this option if you want to use the OV7670. + Disable this option to save memory. - config SCCB_HARDWARE_I2C_PORT0 - bool "I2C0" - config SCCB_HARDWARE_I2C_PORT1 - bool "I2C1" + config OV7725_SUPPORT + bool "Support OV7725 SVGA" + default n + help + Enable this option if you want to use the OV7725. + Disable this option to save memory. -endchoice + config NT99141_SUPPORT + bool "Support NT99141 HD" + default y + help + Enable this option if you want to use the NT99141. + Disable this option to save memory. -choice CAMERA_TASK_PINNED_TO_CORE - bool "Camera task pinned to core" - default CAMERA_CORE0 - help - Pin the camera handle task to a certain core(0/1). It can also be done automatically choosing NO_AFFINITY. + config OV2640_SUPPORT + bool "Support OV2640 2MP" + default y + help + Enable this option if you want to use the OV2640. + Disable this option to save memory. - config CAMERA_CORE0 - bool "CORE0" - config CAMERA_CORE1 - bool "CORE1" - config CAMERA_NO_AFFINITY - bool "NO_AFFINITY" + config OV3660_SUPPORT + bool "Support OV3660 3MP" + default y + help + Enable this option if you want to use the OV3360. + Disable this option to save memory. + + config OV5640_SUPPORT + bool "Support OV5640 5MP" + default y + help + Enable this option if you want to use the OV5640. + Disable this option to save memory. + + choice SCCB_HARDWARE_I2C_PORT + bool "I2C peripheral to use for SCCB" + default SCCB_HARDWARE_I2C_PORT1 + + config SCCB_HARDWARE_I2C_PORT0 + bool "I2C0" + config SCCB_HARDWARE_I2C_PORT1 + bool "I2C1" + + endchoice + + choice CAMERA_TASK_PINNED_TO_CORE + bool "Camera task pinned to core" + default CAMERA_CORE0 + help + Pin the camera handle task to a certain core(0/1). It can also be done automatically choosing NO_AFFINITY. + + config CAMERA_CORE0 + bool "CORE0" + config CAMERA_CORE1 + bool "CORE1" + config CAMERA_NO_AFFINITY + bool "NO_AFFINITY" + + endchoice -endchoice - endmenu diff --git a/esp32-cam-rtos-allframes/README.md b/esp32-cam-rtos-allframes/README.md index 99a1f88..96872e6 100644 --- a/esp32-cam-rtos-allframes/README.md +++ b/esp32-cam-rtos-allframes/README.md @@ -1,16 +1,358 @@ -# ESP32 MJPEG Multiclient Streaming All Frames Server +# ESP32 Camera Driver -This is a MJPEG streaming webserver implemented for AI-Thinker ESP32-CAM or ESP-EYE modules. +## General Information -This is tested to work with **VLC** and **Blynk** video widget. +This repository hosts ESP32, ESP32-S2 and ESP32-S3 compatible driver for OV2640, OV3660, OV5640, OV7670 and OV7725 image sensors. Additionally it provides a few tools, which allow converting the captured frame data to the more common BMP and JPEG formats. +## Important to Remember +- Except when using CIF or lower resolution with JPEG, the driver requires PSRAM to be installed and activated. +- Using YUV or RGB puts a lot of strain on the chip because writing to PSRAM is not particularly fast. The result is that image data might be missing. This is particularly true if WiFi is enabled. If you need RGB data, it is recommended that JPEG is captured and then turned into RGB using `fmt2rgb888` or `fmt2bmp`/`frame2bmp`. +- When 1 frame buffer is used, the driver will wait for the current frame to finish (VSYNC) and start I2S DMA. After the frame is acquired, I2S will be stopped and the frame buffer returned to the application. This approach gives more control over the system, but results in longer time to get the frame. +- When 2 or more frame bufers are used, I2S is running in continuous mode and each frame is pushed to a queue that the application can access. This approach puts more strain on the CPU/Memory, but allows for double the frame rate. Please use only with JPEG. -**This version uses FreeRTOS tasks to enable streaming to up to 10 connected clients and is buffering and sending every frame to every client** +## Installation Instructions +### Using esp-idf -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/) +- Clone or download and extract the repository to the components folder of your ESP-IDF project +- Enable PSRAM in `menuconfig` (also set Flash and PSRAM frequiencies to 80MHz) +- Include `esp_camera.h` in your code + +### Using PlatformIO + +The easy way -- on the `env` section of `platformio.ini`, add the following: + +```ini +[env] +lib_deps = + esp32-camera +``` + +Now the `esp_camera.h` is available to be included: + +```c +#include "esp_camera.h" +``` + +Enable PSRAM on `menuconfig` or type it direclty on `sdkconfig`. Check the [official doc](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/kconfig.html#config-esp32-spiram-support) for more info. + +``` +CONFIG_ESP32_SPIRAM_SUPPORT=y +``` + +***Arduino*** The easy-way (content above) only seems to work if you're using `framework=arduino` which seems to take a bunch of the guesswork out (thanks Arduino!) but also suck up a lot more memory and flash, almost crippling the performance. If you plan to use the `framework=espidf` then read the sections below carefully!! + +## Platform.io lib/submodule (for framework=espidf) + +It's probably easier to just skip the platform.io library registry version and link the git repo as a submodule. (i.e. using code outside the platform.io library management). In this example we will install this as a submodule inside the platform.io $project/lib folder: +``` +cd $project\lib +git submodule add -b master https://github.com/espressif/esp32-camera.git +``` + +Then in `platformio.ini` file +``` +build_flags = + -I../lib/esp32-camera +``` +After that `#include "esp_camera.h"` statement will be available. Now the module is included, and you're hopefully back to the same place as the easy-Arduino way. + +**Warning about platform.io/espidf and fresh (not initialized) git repos** +There is a sharp-edge on you'll discover in the platform.io build process (in espidf v3.3 & 4.0.1) where a project which has only had `git init` but nothing committed will crash platform.io build process with highly non-useful output. The cause is due to lack of a version (making you think you did something wrong, when you didn't at all) - the output is horribly non-descript. Solution: the devs want you to create a file called version.txt with a number in it, or simply commit any file to the projects git repo and use git. This happens because platform.io build process tries to be too clever and determine the build version number from the git repo - it's a sharp edge you'll only encounter if you're experimenting on a new project with no commits .. like wtf is my camera not working let's try a 'clean project'?! + +## Platform.io Kconfig +Kconfig is used by the platform.io menuconfig (accessed by running: `pio run -t menuconfig`) to interactively manage the various #ifdef statements throughout the espidf and supporting libraries (i.e. this repo: esp32-camera and arduino-esp32.git). The menuconfig process generates the `sdkconfig` file which is ultimately used behind the scenes by espidf compile+build process. + +**Make sure to append or symlink** [this `Kconfig`](./Kconfig) content into the `Kconfig` of your project. + +You symlink (or copy) the included Kconfig into your platform.io projects src directory. The file should be named `Kconfig.projbuild` in your projects src\ directory or you could also add the library path to a CMakefile.txt and hope the `Kconfig` (or `Kconfig.projbuild`) gets discovered by the menuconfig process, though this unpredictable for me. + +The unpredictable wonky behavior in platform.io build process around Kconfig naming (Kconfig vs. Kconfig.projbuild) occurs between espidf versions 3.3 and 4.0 - but if you don't see "Camera configuration" in your `pio run -t menuconfig` then there is no point trying to test camera code (it may compile, but it probably won't work!) and it seems the platform.io devs (when they built their wrapper around the espidf menuconfig) didn't implement it properly. You've probably already figured out you can't use the espidf build tools since the files are in totally different locations and also different versions with sometimes different syntax. This is one of those times you might consider changing the `platformio.ini` from `platform=espressif32` to `platform=https://github.com/platformio/platform-espressif32.git#develop` to get a more recent version of the espidf 4.0 tools. + +However with a bit of patience and experimenting you'll figure the Kconfig out. Once Kconfig (or Kconfig.projbuild) is working then you will be able to choose the configurations according to your setup or the camera libraries will be compiled. Although you might also need to delete your .pio/build directory before the options appear .. again, the `pio run -t menuconfig` doens't always notice the new Kconfig files! + +If you miss-skip-ignore this critical step the camera module will compile but camera logic inside the library will be 'empty' because the Kconfig sets the proper #ifdef statements during the build process to initialize the selected cameras. It's very not optional! + +### Kconfig options + +| config | description | default | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------ | +| CONFIG_OV2640_SUPPORT | Support for OV2640 camera | enabled | +| CONFIG_OV7725_SUPPORT | Support for OV7725 camera | disabled | +| CONFIG_OV3660_SUPPORT | Support for OV3660 camera | enabled | +| CONFIG_OV5640_SUPPORT | Support for OV5640 camera | enabled | +| CONFIG_SCCB_HARDWARE_I2C | Enable this option if you want to use hardware I2C to control the camera. Disable this option to use software I2C. | enabled | +| CONFIG_SCCB_HARDWARE_I2C_PORT | I2C peripheral to use for SCCB. Can be I2C0 and I2C1. | CONFIG_SCCB_HARDWARE_I2C_PORT1 | +| CONFIG_CAMERA_TASK_PINNED_TO_CORE | Pin the camera handle task to a certain core(0/1). It can also be done automatically choosing NO_AFFINITY. Can be CAMERA_CORE0, CAMERA_CORE1 or NO_AFFINITY. | CONFIG_CAMERA_CORE0 | + +## Examples + +### Initialization + +```c +#include "esp_camera.h" + +//WROVER-KIT PIN Map +#define CAM_PIN_PWDN -1 //power down is not used +#define CAM_PIN_RESET -1 //software reset will be performed +#define CAM_PIN_XCLK 21 +#define CAM_PIN_SIOD 26 +#define CAM_PIN_SIOC 27 + +#define CAM_PIN_D7 35 +#define CAM_PIN_D6 34 +#define CAM_PIN_D5 39 +#define CAM_PIN_D4 36 +#define CAM_PIN_D3 19 +#define CAM_PIN_D2 18 +#define CAM_PIN_D1 5 +#define CAM_PIN_D0 4 +#define CAM_PIN_VSYNC 25 +#define CAM_PIN_HREF 23 +#define CAM_PIN_PCLK 22 + +static camera_config_t camera_config = { + .pin_pwdn = CAM_PIN_PWDN, + .pin_reset = CAM_PIN_RESET, + .pin_xclk = CAM_PIN_XCLK, + .pin_sscb_sda = CAM_PIN_SIOD, + .pin_sscb_scl = CAM_PIN_SIOC, + + .pin_d7 = CAM_PIN_D7, + .pin_d6 = CAM_PIN_D6, + .pin_d5 = CAM_PIN_D5, + .pin_d4 = CAM_PIN_D4, + .pin_d3 = CAM_PIN_D3, + .pin_d2 = CAM_PIN_D2, + .pin_d1 = CAM_PIN_D1, + .pin_d0 = CAM_PIN_D0, + .pin_vsync = CAM_PIN_VSYNC, + .pin_href = CAM_PIN_HREF, + .pin_pclk = CAM_PIN_PCLK, + + .xclk_freq_hz = 20000000,//EXPERIMENTAL: Set to 16MHz on ESP32-S2 or ESP32-S3 to enable EDMA mode + .ledc_timer = LEDC_TIMER_0, + .ledc_channel = LEDC_CHANNEL_0, + + .pixel_format = PIXFORMAT_JPEG,//YUV422,GRAYSCALE,RGB565,JPEG + .frame_size = FRAMESIZE_UXGA,//QQVGA-QXGA Do not use sizes above QVGA when not JPEG + + .jpeg_quality = 12, //0-63 lower number means higher quality + .fb_count = 1, //if more than one, i2s runs in continuous mode. Use only with JPEG + .grab_mode = CAMERA_GRAB_WHEN_EMPTY//CAMERA_GRAB_LATEST. Sets when buffers should be filled +}; + +esp_err_t camera_init(){ + //power up the camera if PWDN pin is defined + if(CAM_PIN_PWDN != -1){ + pinMode(CAM_PIN_PWDN, OUTPUT); + digitalWrite(CAM_PIN_PWDN, LOW); + } + + //initialize the camera + esp_err_t err = esp_camera_init(&camera_config); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Camera Init Failed"); + return err; + } + + return ESP_OK; +} + +esp_err_t camera_capture(){ + //acquire a frame + camera_fb_t * fb = esp_camera_fb_get(); + if (!fb) { + ESP_LOGE(TAG, "Camera Capture Failed"); + return ESP_FAIL; + } + //replace this with your own function + process_image(fb->width, fb->height, fb->format, fb->buf, fb->len); + + //return the frame buffer back to the driver for reuse + esp_camera_fb_return(fb); + return ESP_OK; +} +``` + +### JPEG HTTP Capture + +```c +#include "esp_camera.h" +#include "esp_http_server.h" +#include "esp_timer.h" + +typedef struct { + httpd_req_t *req; + size_t len; +} jpg_chunking_t; + +static size_t jpg_encode_stream(void * arg, size_t index, const void* data, size_t len){ + jpg_chunking_t *j = (jpg_chunking_t *)arg; + if(!index){ + j->len = 0; + } + if(httpd_resp_send_chunk(j->req, (const char *)data, len) != ESP_OK){ + return 0; + } + j->len += len; + return len; +} + +esp_err_t jpg_httpd_handler(httpd_req_t *req){ + camera_fb_t * fb = NULL; + esp_err_t res = ESP_OK; + size_t fb_len = 0; + int64_t fr_start = esp_timer_get_time(); + + fb = esp_camera_fb_get(); + if (!fb) { + ESP_LOGE(TAG, "Camera capture failed"); + httpd_resp_send_500(req); + return ESP_FAIL; + } + res = httpd_resp_set_type(req, "image/jpeg"); + if(res == ESP_OK){ + res = httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg"); + } + + if(res == ESP_OK){ + if(fb->format == PIXFORMAT_JPEG){ + fb_len = fb->len; + res = httpd_resp_send(req, (const char *)fb->buf, fb->len); + } else { + jpg_chunking_t jchunk = {req, 0}; + res = frame2jpg_cb(fb, 80, jpg_encode_stream, &jchunk)?ESP_OK:ESP_FAIL; + httpd_resp_send_chunk(req, NULL, 0); + fb_len = jchunk.len; + } + } + esp_camera_fb_return(fb); + int64_t fr_end = esp_timer_get_time(); + ESP_LOGI(TAG, "JPG: %uKB %ums", (uint32_t)(fb_len/1024), (uint32_t)((fr_end - fr_start)/1000)); + return res; +} +``` + +### JPEG HTTP Stream + +```c +#include "esp_camera.h" +#include "esp_http_server.h" +#include "esp_timer.h" + +#define PART_BOUNDARY "123456789000000000000987654321" +static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY; +static const char* _STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n"; +static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n"; + +esp_err_t jpg_stream_httpd_handler(httpd_req_t *req){ + camera_fb_t * fb = NULL; + esp_err_t res = ESP_OK; + size_t _jpg_buf_len; + uint8_t * _jpg_buf; + char * part_buf[64]; + static int64_t last_frame = 0; + if(!last_frame) { + last_frame = esp_timer_get_time(); + } + + res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE); + if(res != ESP_OK){ + return res; + } + + while(true){ + fb = esp_camera_fb_get(); + if (!fb) { + ESP_LOGE(TAG, "Camera capture failed"); + res = ESP_FAIL; + break; + } + if(fb->format != PIXFORMAT_JPEG){ + bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len); + if(!jpeg_converted){ + ESP_LOGE(TAG, "JPEG compression failed"); + esp_camera_fb_return(fb); + res = ESP_FAIL; + } + } else { + _jpg_buf_len = fb->len; + _jpg_buf = fb->buf; + } + + if(res == ESP_OK){ + res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY)); + } + if(res == ESP_OK){ + size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, _jpg_buf_len); + + res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen); + } + if(res == ESP_OK){ + res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len); + } + if(fb->format != PIXFORMAT_JPEG){ + free(_jpg_buf); + } + esp_camera_fb_return(fb); + if(res != ESP_OK){ + break; + } + int64_t fr_end = esp_timer_get_time(); + int64_t frame_time = fr_end - last_frame; + last_frame = fr_end; + frame_time /= 1000; + ESP_LOGI(TAG, "MJPG: %uKB %ums (%.1ffps)", + (uint32_t)(_jpg_buf_len/1024), + (uint32_t)frame_time, 1000.0 / (uint32_t)frame_time); + } + + last_frame = 0; + return res; +} +``` + +### BMP HTTP Capture + +```c +#include "esp_camera.h" +#include "esp_http_server.h" +#include "esp_timer.h" + +esp_err_t bmp_httpd_handler(httpd_req_t *req){ + camera_fb_t * fb = NULL; + esp_err_t res = ESP_OK; + int64_t fr_start = esp_timer_get_time(); + + fb = esp_camera_fb_get(); + if (!fb) { + ESP_LOGE(TAG, "Camera capture failed"); + httpd_resp_send_500(req); + return ESP_FAIL; + } + + uint8_t * buf = NULL; + size_t buf_len = 0; + bool converted = frame2bmp(fb, &buf, &buf_len); + esp_camera_fb_return(fb); + if(!converted){ + ESP_LOGE(TAG, "BMP conversion failed"); + httpd_resp_send_500(req); + return ESP_FAIL; + } + + res = httpd_resp_set_type(req, "image/x-windows-bmp") + || httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.bmp") + || httpd_resp_send(req, (const char *)buf, buf_len); + free(buf); + int64_t fr_end = esp_timer_get_time(); + ESP_LOGI(TAG, "BMP: %uKB %ums", (uint32_t)(buf_len/1024), (uint32_t)((fr_end - fr_start)/1000)); + return res; +} +``` diff --git a/esp32-cam-rtos-allframes/cam_hal.c b/esp32-cam-rtos-allframes/cam_hal.c new file mode 100644 index 0000000..f2b65de --- /dev/null +++ b/esp32-cam-rtos-allframes/cam_hal.c @@ -0,0 +1,472 @@ +// Copyright 2010-2020 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_heap_caps.h" +#include "ll_cam.h" +#include "cam_hal.h" + +static const char *TAG = "cam_hal"; + +static cam_obj_t *cam_obj = NULL; + +static const uint32_t JPEG_SOI_MARKER = 0xFFD8FF; // written in little-endian for esp32 +static const uint16_t JPEG_EOI_MARKER = 0xD9FF; // written in little-endian for esp32 + +static int cam_verify_jpeg_soi(const uint8_t *inbuf, uint32_t length) +{ + uint32_t sig = *((uint32_t *)inbuf) & 0xFFFFFF; + if(sig != JPEG_SOI_MARKER) { + for (uint32_t i = 0; i < length; i++) { + sig = *((uint32_t *)(&inbuf[i])) & 0xFFFFFF; + if (sig == JPEG_SOI_MARKER) { + ESP_LOGW(TAG, "SOI: %d", i); + return i; + } + } + ESP_LOGW(TAG, "NO-SOI"); + return -1; + } + return 0; +} + +static int cam_verify_jpeg_eoi(const uint8_t *inbuf, uint32_t length) +{ + int offset = -1; + uint8_t *dptr = (uint8_t *)inbuf + length - 2; + while (dptr > inbuf) { + uint16_t sig = *((uint16_t *)dptr); + if (JPEG_EOI_MARKER == sig) { + offset = dptr - inbuf; + //ESP_LOGW(TAG, "EOI: %d", length - (offset + 2)); + return offset; + } + dptr--; + } + return -1; +} + +static bool cam_get_next_frame(int * frame_pos) +{ + if(!cam_obj->frames[*frame_pos].en){ + for (int x = 0; x < cam_obj->frame_cnt; x++) { + if (cam_obj->frames[x].en) { + *frame_pos = x; + return true; + } + } + } else { + return true; + } + return false; +} + +static bool cam_start_frame(int * frame_pos) +{ + if (cam_get_next_frame(frame_pos)) { + if(ll_cam_start(cam_obj, *frame_pos)){ + // Vsync the frame manually + ll_cam_do_vsync(cam_obj); + uint64_t us = (uint64_t)esp_timer_get_time(); + cam_obj->frames[*frame_pos].fb.timestamp.tv_sec = us / 1000000UL; + cam_obj->frames[*frame_pos].fb.timestamp.tv_usec = us % 1000000UL; + return true; + } + } + return false; +} + +void IRAM_ATTR ll_cam_send_event(cam_obj_t *cam, cam_event_t cam_event, BaseType_t * HPTaskAwoken) +{ + if (xQueueSendFromISR(cam->event_queue, (void *)&cam_event, HPTaskAwoken) != pdTRUE) { + ll_cam_stop(cam); + cam->state = CAM_STATE_IDLE; + ESP_EARLY_LOGE(TAG, "EV-OVF"); + } +} + +//Copy fram from DMA dma_buffer to fram dma_buffer +static void cam_task(void *arg) +{ + int cnt = 0; + int frame_pos = 0; + cam_obj->state = CAM_STATE_IDLE; + cam_event_t cam_event = 0; + + xQueueReset(cam_obj->event_queue); + + while (1) { + xQueueReceive(cam_obj->event_queue, (void *)&cam_event, portMAX_DELAY); + DBG_PIN_SET(1); + switch (cam_obj->state) { + + case CAM_STATE_IDLE: { + if (cam_event == CAM_VSYNC_EVENT) { + //DBG_PIN_SET(1); + if(cam_start_frame(&frame_pos)){ + cam_obj->frames[frame_pos].fb.len = 0; + cam_obj->state = CAM_STATE_READ_BUF; + } + cnt = 0; + } + } + break; + + case CAM_STATE_READ_BUF: { + camera_fb_t * frame_buffer_event = &cam_obj->frames[frame_pos].fb; + size_t pixels_per_dma = (cam_obj->dma_half_buffer_size * cam_obj->fb_bytes_per_pixel) / (cam_obj->dma_bytes_per_item * cam_obj->in_bytes_per_pixel); + + if (cam_event == CAM_IN_SUC_EOF_EVENT) { + if(!cam_obj->psram_mode){ + if (cam_obj->fb_size < (frame_buffer_event->len + pixels_per_dma)) { + ESP_LOGW(TAG, "FB-OVF"); + ll_cam_stop(cam_obj); + DBG_PIN_SET(0); + continue; + } + frame_buffer_event->len += ll_cam_memcpy(cam_obj, + &frame_buffer_event->buf[frame_buffer_event->len], + &cam_obj->dma_buffer[(cnt % cam_obj->dma_half_buffer_cnt) * cam_obj->dma_half_buffer_size], + cam_obj->dma_half_buffer_size); + } + //Check for JPEG SOI in the first buffer. stop if not found + if (cam_obj->jpeg_mode && cnt == 0 && cam_verify_jpeg_soi(frame_buffer_event->buf, frame_buffer_event->len) != 0) { + ll_cam_stop(cam_obj); + cam_obj->state = CAM_STATE_IDLE; + } + cnt++; + + } else if (cam_event == CAM_VSYNC_EVENT) { + //DBG_PIN_SET(1); + ll_cam_stop(cam_obj); + + if (cnt || !cam_obj->jpeg_mode || cam_obj->psram_mode) { + if (cam_obj->jpeg_mode) { + if (!cam_obj->psram_mode) { + if (cam_obj->fb_size < (frame_buffer_event->len + pixels_per_dma)) { + ESP_LOGW(TAG, "FB-OVF"); + cnt--; + } else { + frame_buffer_event->len += ll_cam_memcpy(cam_obj, + &frame_buffer_event->buf[frame_buffer_event->len], + &cam_obj->dma_buffer[(cnt % cam_obj->dma_half_buffer_cnt) * cam_obj->dma_half_buffer_size], + cam_obj->dma_half_buffer_size); + } + } + cnt++; + } + + cam_obj->frames[frame_pos].en = 0; + + if (cam_obj->psram_mode) { + if (cam_obj->jpeg_mode) { + frame_buffer_event->len = cnt * cam_obj->dma_half_buffer_size; + } else { + frame_buffer_event->len = cam_obj->recv_size; + } + } else if (!cam_obj->jpeg_mode) { + if (frame_buffer_event->len != cam_obj->fb_size) { + cam_obj->frames[frame_pos].en = 1; + ESP_LOGE(TAG, "FB-SIZE: %u != %u", frame_buffer_event->len, cam_obj->fb_size); + } + } + //send frame + if(!cam_obj->frames[frame_pos].en && xQueueSend(cam_obj->frame_buffer_queue, (void *)&frame_buffer_event, 0) != pdTRUE) { + //pop frame buffer from the queue + camera_fb_t * fb2 = NULL; + if(xQueueReceive(cam_obj->frame_buffer_queue, &fb2, 0) == pdTRUE) { + //push the new frame to the end of the queue + if (xQueueSend(cam_obj->frame_buffer_queue, (void *)&frame_buffer_event, 0) != pdTRUE) { + cam_obj->frames[frame_pos].en = 1; + ESP_LOGE(TAG, "FBQ-SND"); + } + //free the popped buffer + cam_give(fb2); + } else { + //queue is full and we could not pop a frame from it + cam_obj->frames[frame_pos].en = 1; + ESP_LOGE(TAG, "FBQ-RCV"); + } + } + } + + if(!cam_start_frame(&frame_pos)){ + cam_obj->state = CAM_STATE_IDLE; + } else { + cam_obj->frames[frame_pos].fb.len = 0; + } + cnt = 0; + } + } + break; + } + DBG_PIN_SET(0); + } +} + +static lldesc_t * allocate_dma_descriptors(uint32_t count, uint16_t size, uint8_t * buffer) +{ + lldesc_t *dma = (lldesc_t *)heap_caps_malloc(count * sizeof(lldesc_t), MALLOC_CAP_DMA); + if (dma == NULL) { + return dma; + } + + for (int x = 0; x < count; x++) { + dma[x].size = size; + dma[x].length = 0; + dma[x].sosf = 0; + dma[x].eof = 0; + dma[x].owner = 1; + dma[x].buf = (buffer + size * x); + dma[x].empty = (uint32_t)&dma[(x + 1) % count]; + } + return dma; +} + +static esp_err_t cam_dma_config() +{ + bool ret = ll_cam_dma_sizes(cam_obj); + if (0 == ret) { + return ESP_FAIL; + } + + cam_obj->dma_node_cnt = (cam_obj->dma_buffer_size) / cam_obj->dma_node_buffer_size; // Number of DMA nodes + cam_obj->frame_copy_cnt = cam_obj->recv_size / cam_obj->dma_half_buffer_size; // Number of interrupted copies, ping-pong copy + + ESP_LOGI(TAG, "buffer_size: %d, half_buffer_size: %d, node_buffer_size: %d, node_cnt: %d, total_cnt: %d", + cam_obj->dma_buffer_size, cam_obj->dma_half_buffer_size, cam_obj->dma_node_buffer_size, cam_obj->dma_node_cnt, cam_obj->frame_copy_cnt); + + cam_obj->dma_buffer = NULL; + cam_obj->dma = NULL; + + cam_obj->frames = (cam_frame_t *)heap_caps_calloc(1, cam_obj->frame_cnt * sizeof(cam_frame_t), MALLOC_CAP_DEFAULT); + CAM_CHECK(cam_obj->frames != NULL, "frames malloc failed", ESP_FAIL); + + uint8_t dma_align = 0; + size_t fb_size = cam_obj->fb_size; + if (cam_obj->psram_mode) { + dma_align = ll_cam_get_dma_align(cam_obj); + if (cam_obj->fb_size < cam_obj->recv_size) { + fb_size = cam_obj->recv_size; + } + } + for (int x = 0; x < cam_obj->frame_cnt; x++) { + cam_obj->frames[x].dma = NULL; + cam_obj->frames[x].fb_offset = 0; + cam_obj->frames[x].en = 0; + cam_obj->frames[x].fb.buf = (uint8_t *)heap_caps_malloc(fb_size * sizeof(uint8_t) + dma_align, MALLOC_CAP_SPIRAM); + CAM_CHECK(cam_obj->frames[x].fb.buf != NULL, "frame buffer malloc failed", ESP_FAIL); + if (cam_obj->psram_mode) { + //align PSRAM buffer. TODO: save the offset so proper address can be freed later + cam_obj->frames[x].fb_offset = dma_align - ((uint32_t)cam_obj->frames[x].fb.buf & (dma_align - 1)); + cam_obj->frames[x].fb.buf += cam_obj->frames[x].fb_offset; + ESP_LOGI(TAG, "Frame[%d]: Offset: %u, Addr: 0x%08X", x, cam_obj->frames[x].fb_offset, (uint32_t)cam_obj->frames[x].fb.buf); + cam_obj->frames[x].dma = allocate_dma_descriptors(cam_obj->dma_node_cnt, cam_obj->dma_node_buffer_size, cam_obj->frames[x].fb.buf); + CAM_CHECK(cam_obj->frames[x].dma != NULL, "frame dma malloc failed", ESP_FAIL); + } + cam_obj->frames[x].en = 1; + } + + if (!cam_obj->psram_mode) { + cam_obj->dma_buffer = (uint8_t *)heap_caps_malloc(cam_obj->dma_buffer_size * sizeof(uint8_t), MALLOC_CAP_DMA); + CAM_CHECK(cam_obj->dma_buffer != NULL, "dma_buffer malloc failed", ESP_FAIL); + + cam_obj->dma = allocate_dma_descriptors(cam_obj->dma_node_cnt, cam_obj->dma_node_buffer_size, cam_obj->dma_buffer); + CAM_CHECK(cam_obj->dma != NULL, "dma malloc failed", ESP_FAIL); + } + + return ESP_OK; +} + +esp_err_t cam_init(const camera_config_t *config) +{ + CAM_CHECK(NULL != config, "config pointer is invalid", ESP_ERR_INVALID_ARG); + + esp_err_t ret = ESP_OK; + cam_obj = (cam_obj_t *)heap_caps_calloc(1, sizeof(cam_obj_t), MALLOC_CAP_DMA); + CAM_CHECK(NULL != cam_obj, "lcd_cam object malloc error", ESP_ERR_NO_MEM); + + cam_obj->swap_data = 0; + cam_obj->vsync_pin = config->pin_vsync; + cam_obj->vsync_invert = true; + + ll_cam_set_pin(cam_obj, config); + ret = ll_cam_config(cam_obj, config); + CAM_CHECK_GOTO(ret == ESP_OK, "ll_cam initialize failed", err); + +#if CAMERA_DBG_PIN_ENABLE + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[DBG_PIN_NUM], PIN_FUNC_GPIO); + gpio_set_direction(DBG_PIN_NUM, GPIO_MODE_OUTPUT); + gpio_set_pull_mode(DBG_PIN_NUM, GPIO_FLOATING); +#endif + + ESP_LOGI(TAG, "cam init ok"); + return ESP_OK; + +err: + free(cam_obj); + cam_obj = NULL; + return ESP_FAIL; +} + +esp_err_t cam_config(const camera_config_t *config, framesize_t frame_size, uint8_t sensor_pid) +{ + CAM_CHECK(NULL != config, "config pointer is invalid", ESP_ERR_INVALID_ARG); + esp_err_t ret = ESP_OK; + + ret = ll_cam_set_sample_mode(cam_obj, (pixformat_t)config->pixel_format, config->xclk_freq_hz, sensor_pid); + + cam_obj->jpeg_mode = config->pixel_format == PIXFORMAT_JPEG; +#if CONFIG_IDF_TARGET_ESP32 + cam_obj->psram_mode = false; +#else + cam_obj->psram_mode = (config->xclk_freq_hz == 16000000); +#endif + cam_obj->frame_cnt = config->fb_count; + cam_obj->width = resolution[frame_size].width; + cam_obj->height = resolution[frame_size].height; + + if(cam_obj->jpeg_mode){ + cam_obj->recv_size = cam_obj->width * cam_obj->height / 5; + cam_obj->fb_size = cam_obj->recv_size; + } else { + cam_obj->recv_size = cam_obj->width * cam_obj->height * cam_obj->in_bytes_per_pixel; + cam_obj->fb_size = cam_obj->width * cam_obj->height * cam_obj->fb_bytes_per_pixel; + } + + ret = cam_dma_config(); + CAM_CHECK_GOTO(ret == ESP_OK, "cam_dma_config failed", err); + + cam_obj->event_queue = xQueueCreate(cam_obj->dma_half_buffer_cnt - 1, sizeof(cam_event_t)); + CAM_CHECK_GOTO(cam_obj->event_queue != NULL, "event_queue create failed", err); + + size_t frame_buffer_queue_len = cam_obj->frame_cnt; + if (config->grab_mode == CAMERA_GRAB_LATEST && cam_obj->frame_cnt > 1) { + frame_buffer_queue_len = cam_obj->frame_cnt - 1; + } + cam_obj->frame_buffer_queue = xQueueCreate(frame_buffer_queue_len, sizeof(camera_fb_t*)); + CAM_CHECK_GOTO(cam_obj->frame_buffer_queue != NULL, "frame_buffer_queue create failed", err); + + ret = ll_cam_init_isr(cam_obj); + CAM_CHECK_GOTO(ret == ESP_OK, "cam intr alloc failed", err); + + +#if CONFIG_CAMERA_CORE0 + xTaskCreatePinnedToCore(cam_task, "cam_task", 2048, NULL, configMAX_PRIORITIES - 2, &cam_obj->task_handle, 0); +#elif CONFIG_CAMERA_CORE1 + xTaskCreatePinnedToCore(cam_task, "cam_task", 2048, NULL, configMAX_PRIORITIES - 2, &cam_obj->task_handle, 1); +#else + xTaskCreate(cam_task, "cam_task", 2048, NULL, configMAX_PRIORITIES - 2, &cam_obj->task_handle); +#endif + + ESP_LOGI(TAG, "cam config ok"); + return ESP_OK; + +err: + cam_deinit(); + return ESP_FAIL; +} + +esp_err_t cam_deinit(void) +{ + if (!cam_obj) { + return ESP_FAIL; + } + + cam_stop(); + gpio_isr_handler_remove(cam_obj->vsync_pin); + if (cam_obj->task_handle) { + vTaskDelete(cam_obj->task_handle); + } + if (cam_obj->event_queue) { + vQueueDelete(cam_obj->event_queue); + } + if (cam_obj->frame_buffer_queue) { + vQueueDelete(cam_obj->frame_buffer_queue); + } + if (cam_obj->dma) { + free(cam_obj->dma); + } + if (cam_obj->dma_buffer) { + free(cam_obj->dma_buffer); + } + if (cam_obj->frames) { + for (int x = 0; x < cam_obj->frame_cnt; x++) { + free(cam_obj->frames[x].fb.buf - cam_obj->frames[x].fb_offset); + if (cam_obj->frames[x].dma) { + free(cam_obj->frames[x].dma); + } + } + free(cam_obj->frames); + } + + if (cam_obj->cam_intr_handle) { + esp_intr_free(cam_obj->cam_intr_handle); + } + + free(cam_obj); + cam_obj = NULL; + return ESP_OK; +} + +void cam_stop(void) +{ + ll_cam_vsync_intr_enable(cam_obj, false); + ll_cam_stop(cam_obj); +} + +void cam_start(void) +{ + ll_cam_vsync_intr_enable(cam_obj, true); +} + +camera_fb_t *cam_take(TickType_t timeout) +{ + camera_fb_t *dma_buffer = NULL; + TickType_t start = xTaskGetTickCount(); + xQueueReceive(cam_obj->frame_buffer_queue, (void *)&dma_buffer, timeout); + if (dma_buffer) { + if(cam_obj->jpeg_mode){ + // find the end marker for JPEG. Data after that can be discarded + int offset_e = cam_verify_jpeg_eoi(dma_buffer->buf, dma_buffer->len); + if (offset_e >= 0) { + // adjust buffer length + dma_buffer->len = offset_e + sizeof(JPEG_EOI_MARKER); + return dma_buffer; + } else { + ESP_LOGW(TAG, "NO-EOI"); + cam_give(dma_buffer); + return cam_take(timeout - (xTaskGetTickCount() - start));//recurse!!!! + } + } else if(cam_obj->psram_mode && cam_obj->in_bytes_per_pixel != cam_obj->fb_bytes_per_pixel){ + //currently this is used only for YUV to GRAYSCALE + dma_buffer->len = ll_cam_memcpy(cam_obj, dma_buffer->buf, dma_buffer->buf, dma_buffer->len); + } + return dma_buffer; + } else { + ESP_LOGI(TAG, "Failed to get the frame on time!"); + } + return NULL; +} + +void cam_give(camera_fb_t *dma_buffer) +{ + for (int x = 0; x < cam_obj->frame_cnt; x++) { + if (&cam_obj->frames[x].fb == dma_buffer) { + cam_obj->frames[x].en = 1; + break; + } + } +} diff --git a/esp32-cam-rtos-allframes/cam_hal.h b/esp32-cam-rtos-allframes/cam_hal.h new file mode 100644 index 0000000..a271865 --- /dev/null +++ b/esp32-cam-rtos-allframes/cam_hal.h @@ -0,0 +1,60 @@ +// Copyright 2010-2020 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. + +#pragma once + +#include "esp_camera.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Uninitialize the lcd_cam module + * + * @param handle Provide handle pointer to release resources + * + * @return + * - ESP_OK Success + * - ESP_FAIL Uninitialize fail + */ +esp_err_t cam_deinit(void); + +/** + * @brief Initialize the lcd_cam module + * + * @param config Configurations - see lcd_cam_config_t struct + * + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_ERR_NO_MEM No memory to initialize lcd_cam + * - ESP_FAIL Initialize fail + */ +esp_err_t cam_init(const camera_config_t *config); + +esp_err_t cam_config(const camera_config_t *config, framesize_t frame_size, uint8_t sensor_pid); + +void cam_stop(void); + +void cam_start(void); + +camera_fb_t *cam_take(TickType_t timeout); + +void cam_give(camera_fb_t *dma_buffer); + +#ifdef __cplusplus +} +#endif diff --git a/esp32-cam-rtos-allframes/camera.c b/esp32-cam-rtos-allframes/camera.c deleted file mode 100644 index f0f3a2e..0000000 --- a/esp32-cam-rtos-allframes/camera.c +++ /dev/null @@ -1,1592 +0,0 @@ -// 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 -#if CONFIG_NT99141_SUPPORT -#include "nt99141.h" -#endif -#if CONFIG_OV7670_SUPPORT -#include "ov7670.h" -#endif - -typedef enum { - CAMERA_NONE = 0, - CAMERA_UNKNOWN = 1, - CAMERA_OV7725 = 7725, - CAMERA_OV2640 = 2640, - CAMERA_OV3660 = 3660, - CAMERA_OV5640 = 5640, - CAMERA_OV7670 = 7670, - CAMERA_NT99141 = 9141, -} 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_gpio_init(const camera_config_t* config) -{ - // Configure input GPIOs - const 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, - .pin_bit_mask = 0LL - }; - for (int i = 0; i < sizeof(pins) / sizeof(gpio_num_t); ++i) { - if (rtc_gpio_is_valid_gpio(pins[i])) { - rtc_gpio_deinit(pins[i]); - } - conf.pin_bit_mask |= 1LL << pins[i]; - } - gpio_config(&conf); -} - -static void i2s_init() -{ - camera_config_t* config = &s_state->config; - - // Route input GPIOs to I2S peripheral using GPIO matrix - gpio_matrix_in(config->pin_d0, I2S0I_DATA_IN0_IDX, false); - 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) { - ESP_LOGD(TAG,"unexpected JPEG signature 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; - } - - if(config->pin_xclk >= 0) { - ESP_LOGD(TAG, "Enabling XCLK output"); - camera_enable_out_clock(config); - } - - if (config->pin_sscb_sda != -1) { - ESP_LOGD(TAG, "Initializing SSCB"); - SCCB_Init(config->pin_sscb_sda, config->pin_sscb_scl); - } - - if(config->pin_pwdn >= 0) { - ESP_LOGD(TAG, "Resetting camera by power down line"); - 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 -#if CONFIG_NT99141_SUPPORT - if (slv_addr == 0x2a) - { - ESP_LOGD(TAG, "Resetting NT99141"); - SCCB_Write16(0x2a, 0x3008, 0x01);//bank sensor - } -#endif - - s_state->sensor.slv_addr = slv_addr; - s_state->sensor.xclk_freq_hz = config->xclk_freq_hz; - -#if (CONFIG_OV3660_SUPPORT || CONFIG_OV5640_SUPPORT || CONFIG_NT99141_SUPPORT) - if(s_state->sensor.slv_addr == 0x3c){ - id->PID = SCCB_Read16(s_state->sensor.slv_addr, REG16_CHIDH); - id->VER = SCCB_Read16(s_state->sensor.slv_addr, REG16_CHIDL); - vTaskDelay(10 / portTICK_PERIOD_MS); - ESP_LOGD(TAG, "Camera PID=0x%02x VER=0x%02x", id->PID, id->VER); - } else if(s_state->sensor.slv_addr == 0x2a){ - id->PID = SCCB_Read16(s_state->sensor.slv_addr, 0x3000); - id->VER = SCCB_Read16(s_state->sensor.slv_addr, 0x3001); - vTaskDelay(10 / portTICK_PERIOD_MS); - ESP_LOGD(TAG, "Camera PID=0x%02x VER=0x%02x", id->PID, id->VER); - if(config->xclk_freq_hz > 10000000) - { - ESP_LOGE(TAG, "NT99141: only XCLK under 10MHz is supported, and XCLK is now set to 10M"); - s_state->sensor.xclk_freq_hz = 10000000; - } - } else { -#endif - id->PID = SCCB_Read(s_state->sensor.slv_addr, REG_PID); - 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 || CONFIG_NT99141_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 -#if CONFIG_OV7670_SUPPORT - case OV7670_PID: - *out_camera_model = CAMERA_OV7670; - ov7670_init(&s_state->sensor); - break; -#endif -#if CONFIG_NT99141_SUPPORT - case NT99141_PID: - *out_camera_model = CAMERA_NT99141; - NT99141_init(&s_state->sensor); - break; -#endif - default: - id->PID = 0; - *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 -#if CONFIG_OV7670_SUPPORT - case OV7670_PID: - if (frame_size > FRAMESIZE_VGA) { - frame_size = FRAMESIZE_VGA; - } - break; -#endif -#if CONFIG_NT99141_SUPPORT - case NT99141_PID: - if (frame_size > FRAMESIZE_HD) { - frame_size = FRAMESIZE_HD; - } - break; -#endif - default: - return ESP_ERR_CAMERA_NOT_SUPPORTED; - } - - 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 || s_state->sensor.id.PID == NT99141_PID) { - if (is_hs_mode()) { - s_state->sampling_mode = SM_0A00_0B00; - s_state->dma_filter = &dma_filter_yuyv_highspeed; - } 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) { - if(s_state->sensor.id.PID == OV7670_PID) { - s_state->sampling_mode = SM_0A0B_0B0C; - }else{ - s_state->sampling_mode = SM_0A00_0B00; - } - s_state->dma_filter = &dma_filter_yuyv_highspeed; - } else { - s_state->sampling_mode = SM_0A0B_0C0D; - s_state->dma_filter = &dma_filter_yuyv; - } - s_state->in_bytes_per_pixel = 2; // camera sends YU/YV - s_state->fb_bytes_per_pixel = 2; // frame buffer stores YU/YV/RGB565 - } else if (pix_format == PIXFORMAT_RGB888) { - s_state->fb_size = s_state->width * s_state->height * 3; - if (is_hs_mode()) { - if(s_state->sensor.id.PID == OV7670_PID) { - s_state->sampling_mode = SM_0A0B_0B0C; - }else{ - s_state->sampling_mode = SM_0A00_0B00; - } - s_state->dma_filter = &dma_filter_rgb888_highspeed; - } else { - s_state->sampling_mode = SM_0A0B_0C0D; - 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 && s_state->sensor.id.PID != NT99141_PID) { - ESP_LOGE(TAG, "JPEG format is only supported for ov2640, ov3660 and ov5640"); - err = ESP_ERR_NOT_SUPPORTED; - goto fail; - } - 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) { - if (err != ESP_ERR_INVALID_STATE) { - ESP_LOGE(TAG, "gpio_install_isr_service failed (%x)", err); - goto fail; - } - else { - ESP_LOGW(TAG, "gpio_install_isr_service already installed"); - } - } - err = gpio_isr_handler_add(s_state->config.pin_vsync, &vsync_isr, NULL); - if (err != ESP_OK) { - 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; - i2s_gpio_init(config); - esp_err_t err = camera_probe(config, &camera_model); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Camera probe failed with error 0x%x", err); - goto fail; - } - if (camera_model == CAMERA_OV7725) { - ESP_LOGI(TAG, "Detected OV7725 camera"); - if(config->pixel_format == PIXFORMAT_JPEG) { - ESP_LOGE(TAG, "Camera does not support JPEG"); - err = ESP_ERR_CAMERA_NOT_SUPPORTED; - goto fail; - } - } else if (camera_model == CAMERA_OV2640) { - ESP_LOGI(TAG, "Detected OV2640 camera"); - } else if (camera_model == CAMERA_OV3660) { - ESP_LOGI(TAG, "Detected OV3660 camera"); - } else if (camera_model == CAMERA_OV5640) { - ESP_LOGI(TAG, "Detected OV5640 camera"); - } else if (camera_model == CAMERA_OV7670) { - ESP_LOGI(TAG, "Detected OV7670 camera"); - } else if (camera_model == CAMERA_NT99141) { - ESP_LOGI(TAG, "Detected NT99141 camera"); - } else { - ESP_LOGI(TAG, "Camera not supported"); - err = ESP_ERR_CAMERA_NOT_SUPPORTED; - 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(); - - if(s_state->config.pin_xclk >= 0) { - camera_disable_out_clock(); - } - free(s_state); - s_state = NULL; - 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) -{ -//#if ESP_IDF_VERSION_MAJOR > 3 -// 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) -{ -//#if ESP_IDF_VERSION_MAJOR > 3 -// 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-rtos-allframes/camera_common.h b/esp32-cam-rtos-allframes/camera_common.h index 8a2db5c..bdc24d2 100644 --- a/esp32-cam-rtos-allframes/camera_common.h +++ b/esp32-cam-rtos-allframes/camera_common.h @@ -15,6 +15,10 @@ #if ESP_IDF_VERSION_MAJOR >= 4 // IDF 4+ #if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 #include "esp32/rom/lldesc.h" +#elif CONFIG_IDF_TARGET_ESP32S2 // ESP32-S2 +#include "esp32s2/rom/lldesc.h" +#elif CONFIG_IDF_TARGET_ESP32S3 // ESP32-S3 +#include "esp32s3/rom/lldesc.h" #else #error Target CONFIG_IDF_TARGET is not supported #endif @@ -22,28 +26,3 @@ #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-rtos-allframes/component.mk b/esp32-cam-rtos-allframes/component.mk new file mode 100644 index 0000000..8db15eb --- /dev/null +++ b/esp32-cam-rtos-allframes/component.mk @@ -0,0 +1,4 @@ +COMPONENT_ADD_INCLUDEDIRS := driver/include conversions/include +COMPONENT_PRIV_INCLUDEDIRS := driver/private_include conversions/private_include sensors/private_include target/private_include +COMPONENT_SRCDIRS := driver conversions sensors target target/esp32 +CXXFLAGS += -fno-rtti diff --git a/esp32-cam-rtos-allframes/esp32-camera-master.zip b/esp32-cam-rtos-allframes/esp32-camera-master.zip new file mode 100644 index 0000000000000000000000000000000000000000..ce89eab405a80ff153968ab0d81cd844c25eb26c GIT binary patch literal 122571 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 jU}k1zH=t)`VA5yjFk~|_W@Th#)@NWcVlgl@WH +#include +#include +#include "time.h" +#include "sys/time.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/gpio.h" +#include "esp_system.h" +#include "nvs_flash.h" +#include "nvs.h" +#include "sensor.h" +#include "sccb.h" +#include "cam_hal.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 +#if CONFIG_NT99141_SUPPORT +#include "nt99141.h" +#endif +#if CONFIG_OV7670_SUPPORT +#include "ov7670.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 = "camera"; +#endif + +typedef struct { + sensor_t sensor; + camera_fb_t fb; +} camera_state_t; + +static const char* CAMERA_SENSOR_NVS_KEY = "sensor"; +static const char* CAMERA_PIXFORMAT_NVS_KEY = "pixformat"; +static camera_state_t *s_state = NULL; + +#if CONFIG_IDF_TARGET_ESP32S3 // LCD_CAM module of ESP32-S3 will generate xclk +#define CAMERA_ENABLE_OUT_CLOCK(v) +#define CAMERA_DISABLE_OUT_CLOCK() +#else +#define CAMERA_ENABLE_OUT_CLOCK(v) camera_enable_out_clock((v)) +#define CAMERA_DISABLE_OUT_CLOCK() camera_disable_out_clock() +#endif + +static esp_err_t camera_probe(const camera_config_t *config, camera_model_t *out_camera_model) +{ + *out_camera_model = CAMERA_NONE; + if (s_state != NULL) { + return ESP_ERR_INVALID_STATE; + } + + s_state = (camera_state_t *) calloc(sizeof(camera_state_t), 1); + if (!s_state) { + return ESP_ERR_NO_MEM; + } + + if (config->pin_xclk >= 0) { + ESP_LOGD(TAG, "Enabling XCLK output"); + CAMERA_ENABLE_OUT_CLOCK(config); + } + + if (config->pin_sscb_sda != -1) { + ESP_LOGD(TAG, "Initializing SSCB"); + SCCB_Init(config->pin_sscb_sda, config->pin_sscb_scl); + } + + if (config->pin_pwdn >= 0) { + ESP_LOGD(TAG, "Resetting camera by power down line"); + 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) { + CAMERA_DISABLE_OUT_CLOCK(); + return ESP_ERR_NOT_FOUND; + } + + ESP_LOGI(TAG, "Detected camera at address=0x%02x", slv_addr); + s_state->sensor.slv_addr = slv_addr; + s_state->sensor.xclk_freq_hz = config->xclk_freq_hz; + + /** + * Read sensor ID + */ + sensor_id_t *id = &s_state->sensor.id; + + if (slv_addr == OV2640_SCCB_ADDR || slv_addr == OV7725_SCCB_ADDR) { + SCCB_Write(slv_addr, 0xFF, 0x01);//bank sensor + id->PID = SCCB_Read(slv_addr, REG_PID); + id->VER = SCCB_Read(slv_addr, REG_VER); + id->MIDL = SCCB_Read(slv_addr, REG_MIDL); + id->MIDH = SCCB_Read(slv_addr, REG_MIDH); + } else if (slv_addr == OV5640_SCCB_ADDR || slv_addr == OV3660_SCCB_ADDR) { + id->PID = SCCB_Read16(slv_addr, REG16_CHIDH); + id->VER = SCCB_Read16(slv_addr, REG16_CHIDL); + } else if (slv_addr == NT99141_SCCB_ADDR) { + SCCB_Write16(slv_addr, 0x3008, 0x01);//bank sensor + id->PID = SCCB_Read16(slv_addr, 0x3000); + id->VER = SCCB_Read16(slv_addr, 0x3001); + if (config->xclk_freq_hz > 10000000) { + ESP_LOGE(TAG, "NT99141: only XCLK under 10MHz is supported, and XCLK is now set to 10M"); + s_state->sensor.xclk_freq_hz = 10000000; + } + } + vTaskDelay(10 / portTICK_PERIOD_MS); + ESP_LOGI(TAG, "Camera PID=0x%02x VER=0x%02x MIDL=0x%02x MIDH=0x%02x", + id->PID, id->VER, id->MIDH, id->MIDL); + + /** + * Initialize sensor according to sensor ID + */ + 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 +#if CONFIG_OV7670_SUPPORT + case OV7670_PID: + *out_camera_model = CAMERA_OV7670; + ov7670_init(&s_state->sensor); + break; +#endif +#if CONFIG_NT99141_SUPPORT + case NT99141_PID: + *out_camera_model = CAMERA_NT99141; + NT99141_init(&s_state->sensor); + break; +#endif + default: + id->PID = 0; + CAMERA_DISABLE_OUT_CLOCK(); + ESP_LOGE(TAG, "Detected camera not supported."); + return ESP_ERR_NOT_SUPPORTED; + } + + ESP_LOGD(TAG, "Doing SW reset of sensor"); + s_state->sensor.reset(&s_state->sensor); + + return ESP_OK; +} + + +esp_err_t esp_camera_init(const camera_config_t *config) +{ + esp_err_t err; + err = cam_init(config); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Camera init failed with error 0x%x", err); + return err; + } + + camera_model_t camera_model = CAMERA_NONE; + err = camera_probe(config, &camera_model); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Camera probe failed with error 0x%x(%s)", err, esp_err_to_name(err)); + goto fail; + } + if (camera_model == CAMERA_OV7725) { + ESP_LOGI(TAG, "Detected OV7725 camera"); + } else if (camera_model == CAMERA_OV2640) { + ESP_LOGI(TAG, "Detected OV2640 camera"); + } else if (camera_model == CAMERA_OV3660) { + ESP_LOGI(TAG, "Detected OV3660 camera"); + } else if (camera_model == CAMERA_OV5640) { + ESP_LOGI(TAG, "Detected OV5640 camera"); + } else if (camera_model == CAMERA_OV7670) { + ESP_LOGI(TAG, "Detected OV7670 camera"); + } else if (camera_model == CAMERA_NT99141) { + ESP_LOGI(TAG, "Detected NT99141 camera"); + } else { + ESP_LOGI(TAG, "Camera not supported"); + err = ESP_ERR_CAMERA_NOT_SUPPORTED; + goto fail; + } + + framesize_t frame_size = (framesize_t) config->frame_size; + pixformat_t pix_format = (pixformat_t) config->pixel_format; + + if (frame_size > camera_sensor[camera_model].max_size) { + frame_size = camera_sensor[camera_model].max_size; + } + + err = cam_config(config, frame_size, s_state->sensor.id.PID); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Camera config failed with error 0x%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 (pix_format == PIXFORMAT_JPEG) { + (*s_state->sensor.set_quality)(&s_state->sensor, config->jpeg_quality); + } + s_state->sensor.init_status(&s_state->sensor); + + cam_start(); + + return ESP_OK; + +fail: + CAMERA_DISABLE_OUT_CLOCK(); + return err; +} + +esp_err_t esp_camera_deinit() +{ + esp_err_t ret = cam_deinit(); + if (s_state) { + SCCB_Deinit(); + + free(s_state); + s_state = NULL; + } + + return ret; +} + +#define FB_GET_TIMEOUT (4000 / portTICK_PERIOD_MS) + +camera_fb_t *esp_camera_fb_get() +{ + if (s_state == NULL) { + return NULL; + } + camera_fb_t *fb = cam_take(FB_GET_TIMEOUT); + //set the frame properties + if (fb) { + fb->width = resolution[s_state->sensor.status.framesize].width; + fb->height = resolution[s_state->sensor.status.framesize].height; + fb->format = s_state->sensor.pixformat; + } + return fb; +} + +void esp_camera_fb_return(camera_fb_t *fb) +{ + if (s_state == NULL) { + return; + } + cam_give(fb); +} + +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) +{ +#if ESP_IDF_VERSION_MAJOR > 3 + 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) +{ +#if ESP_IDF_VERSION_MAJOR > 3 + 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-rtos-allframes/esp_camera.h b/esp32-cam-rtos-allframes/esp_camera.h index dadd0c0..bfb729b 100644 --- a/esp32-cam-rtos-allframes/esp_camera.h +++ b/esp32-cam-rtos-allframes/esp_camera.h @@ -38,7 +38,8 @@ .pixel_format = PIXFORMAT_JPEG, .frame_size = FRAMESIZE_SVGA, .jpeg_quality = 10, - .fb_count = 2 + .fb_count = 2, + .grab_mode = CAMERA_GRAB_WHEN_EMPTY }; esp_err_t camera_example_init(){ @@ -74,6 +75,14 @@ extern "C" { #endif +/** + * @brief Configuration structure for camera initialization + */ +typedef enum { + CAMERA_GRAB_WHEN_EMPTY, /*!< Fills buffers when they are empty. Less resources but first 'fb_count' frames might be old */ + CAMERA_GRAB_LATEST /*!< Except when 1 frame buffer is used, queue will always contain the last 'fb_count' frames */ +} camera_grab_mode_t; + /** * @brief Configuration structure for camera initialization */ @@ -95,7 +104,7 @@ typedef struct { 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) */ + int xclk_freq_hz; /*!< Frequency of XCLK signal, in Hz. EXPERIMENTAL: Set to 16MHz on ESP32-S2 or ESP32-S3 to enable EDMA mode */ 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 */ @@ -105,6 +114,7 @@ typedef struct { 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_grab_mode_t grab_mode; /*!< When buffers should be filled */ } camera_config_t; /** diff --git a/esp32-cam-rtos-allframes/esp_jpg_decode.c b/esp32-cam-rtos-allframes/esp_jpg_decode.c index d42794f..a9615e3 100644 --- a/esp32-cam-rtos-allframes/esp_jpg_decode.c +++ b/esp32-cam-rtos-allframes/esp_jpg_decode.c @@ -17,7 +17,11 @@ #if ESP_IDF_VERSION_MAJOR >= 4 // IDF 4+ #if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 #include "esp32/rom/tjpgd.h" -#else +#elif CONFIG_IDF_TARGET_ESP32S2 +#include "tjpgd.h" +#elif CONFIG_IDF_TARGET_ESP32S3 +#include "esp32s3/rom/tjpgd.h" +#else #error Target CONFIG_IDF_TARGET is not supported #endif #else // ESP32 Before IDF 4.0 diff --git a/esp32-cam-rtos-allframes/img_converters.h b/esp32-cam-rtos-allframes/img_converters.h index 330f8db..f736200 100644 --- a/esp32-cam-rtos-allframes/img_converters.h +++ b/esp32-cam-rtos-allframes/img_converters.h @@ -22,6 +22,7 @@ extern "C" { #include #include #include "esp_camera.h" +#include "esp_jpg_decode.h" typedef size_t (* jpg_out_cb)(void * arg, size_t index, const void* data, size_t len); @@ -120,6 +121,8 @@ bool frame2bmp(camera_fb_t * fb, uint8_t ** out, size_t * out_len); */ bool fmt2rgb888(const uint8_t *src_buf, size_t src_len, pixformat_t format, uint8_t * rgb_buf); +bool jpg2rgb565(const uint8_t *src, size_t src_len, uint8_t * out, jpg_scale_t scale); + #ifdef __cplusplus } #endif diff --git a/esp32-cam-rtos-allframes/library.json b/esp32-cam-rtos-allframes/library.json new file mode 100644 index 0000000..322e932 --- /dev/null +++ b/esp32-cam-rtos-allframes/library.json @@ -0,0 +1,26 @@ +{ + "name": "esp32-camera", + "version": "1.0.0", + "keywords": "esp32, camera, espressif, esp32-cam", + "description": "ESP32 compatible driver for OV2640, OV3660, OV5640, OV7670 and OV7725 image sensors.", + "repository": { + "type": "git", + "url": "https://github.com/espressif/esp32-camera" + }, + "frameworks": "espidf", + "platforms": "*", + "build": { + "flags": [ + "-Idriver/include", + "-Iconversions/include", + "-Idriver/private_include", + "-Iconversions/private_include", + "-Isensors/private_include", + "-Itarget/private_include", + "-fno-rtti" + ], + "includeDir": ".", + "srcDir": ".", + "srcFilter": ["-<*>", "+", "+", "+"] + } +} diff --git a/esp32-cam-rtos-allframes/ll_cam.c b/esp32-cam-rtos-allframes/ll_cam.c new file mode 100644 index 0000000..8dc1fc6 --- /dev/null +++ b/esp32-cam-rtos-allframes/ll_cam.c @@ -0,0 +1,515 @@ +// Copyright 2010-2020 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 "soc/i2s_struct.h" +#include "esp_idf_version.h" +#if ESP_IDF_VERSION_MAJOR >= 4 +#include "hal/gpio_ll.h" +#else +#include "rom/ets_sys.h" +#include "soc/gpio_periph.h" +#define esp_rom_delay_us ets_delay_us +static inline int gpio_ll_get_level(gpio_dev_t *hw, int gpio_num) +{ + if (gpio_num < 32) { + return (hw->in >> gpio_num) & 0x1; + } else { + return (hw->in1.data >> (gpio_num - 32)) & 0x1; + } +} +#endif +#include "ll_cam.h" +#include "xclk.h" +#include "cam_hal.h" + +static const char *TAG = "esp32 ll_cam"; + +#define I2S_ISR_ENABLE(i) {I2S0.int_clr.i = 1;I2S0.int_ena.i = 1;} +#define I2S_ISR_DISABLE(i) {I2S0.int_ena.i = 0;I2S0.int_clr.i = 1;} + +typedef union { + struct { + uint32_t sample2:8; + uint32_t unused2:8; + uint32_t sample1:8; + uint32_t unused1:8; + }; + 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; + +typedef size_t (*dma_filter_t)(uint8_t* dst, const uint8_t* src, size_t len); + +static i2s_sampling_mode_t sampling_mode = SM_0A00_0B00; + +static size_t ll_cam_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 size_t IRAM_ATTR ll_cam_dma_filter_jpeg(uint8_t* dst, const uint8_t* src, size_t len) +{ + const dma_elem_t* dma_el = (const dma_elem_t*)src; + size_t elements = len / sizeof(dma_elem_t); + size_t end = elements / 4; + // manually unrolling 4 iterations of the loop here + for (size_t i = 0; i < end; ++i) { + dst[0] = dma_el[0].sample1; + dst[1] = dma_el[1].sample1; + dst[2] = dma_el[2].sample1; + dst[3] = dma_el[3].sample1; + dma_el += 4; + dst += 4; + } + return elements; +} + +static size_t IRAM_ATTR ll_cam_dma_filter_grayscale(uint8_t* dst, const uint8_t* src, size_t len) +{ + const dma_elem_t* dma_el = (const dma_elem_t*)src; + size_t elements = len / sizeof(dma_elem_t); + size_t end = elements / 4; + for (size_t i = 0; i < end; ++i) { + // manually unrolling 4 iterations of the loop here + dst[0] = dma_el[0].sample1; + dst[1] = dma_el[1].sample1; + dst[2] = dma_el[2].sample1; + dst[3] = dma_el[3].sample1; + dma_el += 4; + dst += 4; + } + return elements; +} + +static size_t IRAM_ATTR ll_cam_dma_filter_grayscale_highspeed(uint8_t* dst, const uint8_t* src, size_t len) +{ + const dma_elem_t* dma_el = (const dma_elem_t*)src; + size_t elements = len / sizeof(dma_elem_t); + size_t end = elements / 8; + for (size_t i = 0; i < end; ++i) { + // manually unrolling 4 iterations of the loop here + dst[0] = dma_el[0].sample1; + dst[1] = dma_el[2].sample1; + dst[2] = dma_el[4].sample1; + dst[3] = dma_el[6].sample1; + dma_el += 8; + dst += 4; + } + // the final sample of a line in SM_0A0B_0B0C sampling mode needs special handling + if ((elements & 0x7) != 0) { + dst[0] = dma_el[0].sample1; + dst[1] = dma_el[2].sample1; + elements += 1; + } + return elements / 2; +} + +static size_t IRAM_ATTR ll_cam_dma_filter_yuyv(uint8_t* dst, const uint8_t* src, size_t len) +{ + const dma_elem_t* dma_el = (const dma_elem_t*)src; + size_t elements = len / sizeof(dma_elem_t); + size_t end = elements / 4; + for (size_t i = 0; i < end; ++i) { + dst[0] = dma_el[0].sample1;//y0 + dst[1] = dma_el[0].sample2;//u + dst[2] = dma_el[1].sample1;//y1 + dst[3] = dma_el[1].sample2;//v + + dst[4] = dma_el[2].sample1;//y0 + dst[5] = dma_el[2].sample2;//u + dst[6] = dma_el[3].sample1;//y1 + dst[7] = dma_el[3].sample2;//v + dma_el += 4; + dst += 8; + } + return elements * 2; +} + +static size_t IRAM_ATTR ll_cam_dma_filter_yuyv_highspeed(uint8_t* dst, const uint8_t* src, size_t len) +{ + const dma_elem_t* dma_el = (const dma_elem_t*)src; + size_t elements = len / sizeof(dma_elem_t); + size_t end = elements / 8; + for (size_t i = 0; i < end; ++i) { + dst[0] = dma_el[0].sample1;//y0 + dst[1] = dma_el[1].sample1;//u + dst[2] = dma_el[2].sample1;//y1 + dst[3] = dma_el[3].sample1;//v + + dst[4] = dma_el[4].sample1;//y0 + dst[5] = dma_el[5].sample1;//u + dst[6] = dma_el[6].sample1;//y1 + dst[7] = dma_el[7].sample1;//v + dma_el += 8; + dst += 8; + } + if ((elements & 0x7) != 0) { + dst[0] = dma_el[0].sample1;//y0 + dst[1] = dma_el[1].sample1;//u + dst[2] = dma_el[2].sample1;//y1 + dst[3] = dma_el[2].sample2;//v + elements += 4; + } + return elements; +} + +static void IRAM_ATTR ll_cam_vsync_isr(void *arg) +{ + //DBG_PIN_SET(1); + cam_obj_t *cam = (cam_obj_t *)arg; + BaseType_t HPTaskAwoken = pdFALSE; + // filter + esp_rom_delay_us(1); + if (gpio_ll_get_level(&GPIO, cam->vsync_pin) == !cam->vsync_invert) { + ll_cam_send_event(cam, CAM_VSYNC_EVENT, &HPTaskAwoken); + if (HPTaskAwoken == pdTRUE) { + portYIELD_FROM_ISR(); + } + } + //DBG_PIN_SET(0); +} + +static void IRAM_ATTR ll_cam_dma_isr(void *arg) +{ + //DBG_PIN_SET(1); + cam_obj_t *cam = (cam_obj_t *)arg; + BaseType_t HPTaskAwoken = pdFALSE; + + typeof(I2S0.int_st) status = I2S0.int_st; + if (status.val == 0) { + return; + } + + I2S0.int_clr.val = status.val; + + if (status.in_suc_eof) { + ll_cam_send_event(cam, CAM_IN_SUC_EOF_EVENT, &HPTaskAwoken); + } + if (HPTaskAwoken == pdTRUE) { + portYIELD_FROM_ISR(); + } + //DBG_PIN_SET(0); +} + +bool ll_cam_stop(cam_obj_t *cam) +{ + I2S0.conf.rx_start = 0; + I2S_ISR_DISABLE(in_suc_eof); + I2S0.in_link.stop = 1; + return true; +} + +bool ll_cam_start(cam_obj_t *cam, int frame_pos) +{ + I2S0.conf.rx_start = 0; + + I2S_ISR_ENABLE(in_suc_eof); + + I2S0.conf.rx_reset = 1; + I2S0.conf.rx_reset = 0; + I2S0.conf.rx_fifo_reset = 1; + I2S0.conf.rx_fifo_reset = 0; + I2S0.lc_conf.in_rst = 1; + I2S0.lc_conf.in_rst = 0; + I2S0.lc_conf.ahbm_fifo_rst = 1; + I2S0.lc_conf.ahbm_fifo_rst = 0; + I2S0.lc_conf.ahbm_rst = 1; + I2S0.lc_conf.ahbm_rst = 0; + + I2S0.rx_eof_num = cam->dma_half_buffer_size / sizeof(dma_elem_t); + I2S0.in_link.addr = ((uint32_t)&cam->dma[0]) & 0xfffff; + + I2S0.in_link.start = 1; + I2S0.conf.rx_start = 1; + return true; +} + +esp_err_t ll_cam_config(cam_obj_t *cam, const camera_config_t *config) +{ + // Enable and configure I2S peripheral + periph_module_enable(PERIPH_I2S0_MODULE); + + I2S0.conf.rx_reset = 1; + I2S0.conf.rx_reset = 0; + I2S0.conf.rx_fifo_reset = 1; + I2S0.conf.rx_fifo_reset = 0; + I2S0.lc_conf.in_rst = 1; + I2S0.lc_conf.in_rst = 0; + I2S0.lc_conf.ahbm_fifo_rst = 1; + I2S0.lc_conf.ahbm_fifo_rst = 0; + I2S0.lc_conf.ahbm_rst = 1; + I2S0.lc_conf.ahbm_rst = 0; + + I2S0.conf.rx_slave_mod = 1; + 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.conf2.lcd_en = 1; + I2S0.conf2.camera_en = 1; + + // Configure clock divider + I2S0.clkm_conf.clkm_div_a = 0; + I2S0.clkm_conf.clkm_div_b = 0; + I2S0.clkm_conf.clkm_div_num = 2; + + I2S0.fifo_conf.dscr_en = 1; + I2S0.fifo_conf.rx_fifo_mod = sampling_mode; + I2S0.fifo_conf.rx_fifo_mod_force_en = 1; + + I2S0.conf_chan.rx_chan_mod = 1; + I2S0.sample_rate_conf.rx_bits_mod = 0; + I2S0.timing.val = 0; + I2S0.timing.rx_dsync_sw = 1; + + return ESP_OK; +} + +void ll_cam_vsync_intr_enable(cam_obj_t *cam, bool en) +{ + if (en) { + gpio_intr_enable(cam->vsync_pin); + } else { + gpio_intr_disable(cam->vsync_pin); + } +} + +esp_err_t ll_cam_set_pin(cam_obj_t *cam, const camera_config_t *config) +{ + gpio_config_t io_conf = {0}; + io_conf.intr_type = cam->vsync_invert ? GPIO_PIN_INTR_NEGEDGE : GPIO_PIN_INTR_POSEDGE; + io_conf.pin_bit_mask = 1ULL << config->pin_vsync; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pull_up_en = 1; + io_conf.pull_down_en = 0; + gpio_config(&io_conf); + gpio_install_isr_service(ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM); + gpio_isr_handler_add(config->pin_vsync, ll_cam_vsync_isr, cam); + gpio_intr_disable(config->pin_vsync); + + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[config->pin_pclk], PIN_FUNC_GPIO); + gpio_set_direction(config->pin_pclk, GPIO_MODE_INPUT); + gpio_set_pull_mode(config->pin_pclk, GPIO_FLOATING); + gpio_matrix_in(config->pin_pclk, I2S0I_WS_IN_IDX, false); + + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[config->pin_vsync], PIN_FUNC_GPIO); + gpio_set_direction(config->pin_vsync, GPIO_MODE_INPUT); + gpio_set_pull_mode(config->pin_vsync, GPIO_FLOATING); + gpio_matrix_in(config->pin_vsync, I2S0I_V_SYNC_IDX, false); + + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[config->pin_href], PIN_FUNC_GPIO); + gpio_set_direction(config->pin_href, GPIO_MODE_INPUT); + gpio_set_pull_mode(config->pin_href, GPIO_FLOATING); + gpio_matrix_in(config->pin_href, I2S0I_H_SYNC_IDX, false); + + int data_pins[8] = { + config->pin_d0, config->pin_d1, config->pin_d2, config->pin_d3, config->pin_d4, config->pin_d5, config->pin_d6, config->pin_d7, + }; + for (int i = 0; i < 8; i++) { + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[data_pins[i]], PIN_FUNC_GPIO); + gpio_set_direction(data_pins[i], GPIO_MODE_INPUT); + gpio_set_pull_mode(data_pins[i], GPIO_FLOATING); + gpio_matrix_in(data_pins[i], I2S0I_DATA_IN0_IDX + i, false); + } + + gpio_matrix_in(0x38, I2S0I_H_ENABLE_IDX, false); + return ESP_OK; +} + +esp_err_t ll_cam_init_isr(cam_obj_t *cam) +{ + return esp_intr_alloc(ETS_I2S0_INTR_SOURCE, ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM, ll_cam_dma_isr, cam, &cam->cam_intr_handle); +} + +void ll_cam_do_vsync(cam_obj_t *cam) +{ +} + +uint8_t ll_cam_get_dma_align(cam_obj_t *cam) +{ + return 0; +} + +static bool ll_cam_calc_rgb_dma(cam_obj_t *cam){ + size_t dma_half_buffer_max = 16 * 1024 / cam->dma_bytes_per_item; + size_t dma_buffer_max = 2 * dma_half_buffer_max; + size_t node_max = LCD_CAM_DMA_NODE_BUFFER_MAX_SIZE / cam->dma_bytes_per_item; + + size_t line_width = cam->width * cam->in_bytes_per_pixel; + size_t image_size = cam->height * line_width; + if (image_size > (2 * 1024 * 1024) || (line_width > dma_half_buffer_max)) { + ESP_LOGE(TAG, "Resolution too high"); + return 0; + } + + size_t node_size = node_max; + size_t nodes_per_line = 1; + size_t lines_per_node = 1; + size_t lines_per_half_buffer = 1; + size_t dma_half_buffer_min = node_max; + size_t dma_half_buffer = dma_half_buffer_max; + size_t dma_buffer_size = dma_buffer_max; + + // Calculate DMA Node Size so that it's divisable by or divisor of the line width + if(line_width >= node_max){ + // One or more nodes will be requied for one line + for(size_t i = node_max; i > 0; i=i-1){ + if ((line_width % i) == 0) { + node_size = i; + nodes_per_line = line_width / node_size; + break; + } + } + } else { + // One or more lines can fit into one node + for(size_t i = node_max; i > 0; i=i-1){ + if ((i % line_width) == 0) { + node_size = i; + lines_per_node = node_size / line_width; + while((cam->height % lines_per_node) != 0){ + lines_per_node = lines_per_node - 1; + node_size = lines_per_node * line_width; + } + break; + } + } + } + // Calculate minimum EOF size = max(mode_size, line_size) + dma_half_buffer_min = node_size * nodes_per_line; + // Calculate max EOF size divisable by node size + dma_half_buffer = (dma_half_buffer_max / dma_half_buffer_min) * dma_half_buffer_min; + // Adjust EOF size so that height will be divisable by the number of lines in each EOF + lines_per_half_buffer = dma_half_buffer / line_width; + while((cam->height % lines_per_half_buffer) != 0){ + dma_half_buffer = dma_half_buffer - dma_half_buffer_min; + lines_per_half_buffer = dma_half_buffer / line_width; + } + // Calculate DMA size + dma_buffer_size =(dma_buffer_max / dma_half_buffer) * dma_half_buffer; + + ESP_LOGI(TAG, "node_size: %4u, nodes_per_line: %u, lines_per_node: %u, dma_half_buffer_min: %5u, dma_half_buffer: %5u, lines_per_half_buffer: %2u, dma_buffer_size: %5u, image_size: %u", + node_size * cam->dma_bytes_per_item, nodes_per_line, lines_per_node, dma_half_buffer_min * cam->dma_bytes_per_item, dma_half_buffer * cam->dma_bytes_per_item, lines_per_half_buffer, dma_buffer_size * cam->dma_bytes_per_item, image_size); + + cam->dma_buffer_size = dma_buffer_size * cam->dma_bytes_per_item; + cam->dma_half_buffer_size = dma_half_buffer * cam->dma_bytes_per_item; + cam->dma_node_buffer_size = node_size * cam->dma_bytes_per_item; + cam->dma_half_buffer_cnt = cam->dma_buffer_size / cam->dma_half_buffer_size; + return 1; +} + +bool ll_cam_dma_sizes(cam_obj_t *cam) +{ + cam->dma_bytes_per_item = ll_cam_bytes_per_sample(sampling_mode); + if (cam->jpeg_mode) { + cam->dma_half_buffer_cnt = 8; + cam->dma_node_buffer_size = 2048; + cam->dma_half_buffer_size = cam->dma_node_buffer_size * 2; + cam->dma_buffer_size = cam->dma_half_buffer_cnt * cam->dma_half_buffer_size; + } else { + return ll_cam_calc_rgb_dma(cam); + } + return 1; +} + +static dma_filter_t dma_filter = ll_cam_dma_filter_jpeg; + +size_t IRAM_ATTR ll_cam_memcpy(cam_obj_t *cam, uint8_t *out, const uint8_t *in, size_t len) +{ + //DBG_PIN_SET(1); + size_t r = dma_filter(out, in, len); + //DBG_PIN_SET(0); + return r; +} + +esp_err_t ll_cam_set_sample_mode(cam_obj_t *cam, pixformat_t pix_format, uint32_t xclk_freq_hz, uint8_t sensor_pid) +{ + if (pix_format == PIXFORMAT_GRAYSCALE) { + if (sensor_pid == OV3660_PID || sensor_pid == OV5640_PID || sensor_pid == NT99141_PID) { + if (xclk_freq_hz > 10000000) { + sampling_mode = SM_0A00_0B00; + dma_filter = ll_cam_dma_filter_yuyv_highspeed; + } else { + sampling_mode = SM_0A0B_0C0D; + dma_filter = ll_cam_dma_filter_yuyv; + } + cam->in_bytes_per_pixel = 1; // camera sends Y8 + } else { + if (xclk_freq_hz > 10000000 && sensor_pid != OV7725_PID) { + sampling_mode = SM_0A00_0B00; + dma_filter = ll_cam_dma_filter_grayscale_highspeed; + } else { + sampling_mode = SM_0A0B_0C0D; + dma_filter = ll_cam_dma_filter_grayscale; + } + cam->in_bytes_per_pixel = 2; // camera sends YU/YV + } + cam->fb_bytes_per_pixel = 1; // frame buffer stores Y8 + } else if (pix_format == PIXFORMAT_YUV422 || pix_format == PIXFORMAT_RGB565) { + if (xclk_freq_hz > 10000000 && sensor_pid != OV7725_PID) { + if (sensor_pid == OV7670_PID) { + sampling_mode = SM_0A0B_0B0C; + } else { + sampling_mode = SM_0A00_0B00; + } + dma_filter = ll_cam_dma_filter_yuyv_highspeed; + } else { + sampling_mode = SM_0A0B_0C0D; + dma_filter = ll_cam_dma_filter_yuyv; + } + cam->in_bytes_per_pixel = 2; // camera sends YU/YV + cam->fb_bytes_per_pixel = 2; // frame buffer stores YU/YV/RGB565 + } else if (pix_format == PIXFORMAT_JPEG) { + if (sensor_pid != OV2640_PID && sensor_pid != OV3660_PID && sensor_pid != OV5640_PID && sensor_pid != NT99141_PID) { + ESP_LOGE(TAG, "JPEG format is not supported on this sensor"); + return ESP_ERR_NOT_SUPPORTED; + } + cam->in_bytes_per_pixel = 1; + cam->fb_bytes_per_pixel = 1; + dma_filter = ll_cam_dma_filter_jpeg; + sampling_mode = SM_0A00_0B00; + } else { + ESP_LOGE(TAG, "Requested format is not supported"); + return ESP_ERR_NOT_SUPPORTED; + } + I2S0.fifo_conf.rx_fifo_mod = sampling_mode; + return ESP_OK; +} diff --git a/esp32-cam-rtos-allframes/ll_cam.h b/esp32-cam-rtos-allframes/ll_cam.h new file mode 100644 index 0000000..0df922d --- /dev/null +++ b/esp32-cam-rtos-allframes/ll_cam.h @@ -0,0 +1,136 @@ +// Copyright 2010-2020 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. + +#pragma once + +#include +#include "sdkconfig.h" +#if CONFIG_IDF_TARGET_ESP32 +#if ESP_IDF_VERSION_MAJOR >= 4 +#include "esp32/rom/lldesc.h" +#else +#include "rom/lldesc.h" +#endif +#elif CONFIG_IDF_TARGET_ESP32S2 +#include "esp32s2/rom/lldesc.h" +#elif CONFIG_IDF_TARGET_ESP32S3 +#include "esp32s3/rom/lldesc.h" +#endif +#include "esp_log.h" +#include "esp_camera.h" +#include "camera_common.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/task.h" +#include "freertos/semphr.h" + +#define CAMERA_DBG_PIN_ENABLE 0 +#if CAMERA_DBG_PIN_ENABLE + #if CONFIG_IDF_TARGET_ESP32 + #define DBG_PIN_NUM 26 + #else + #define DBG_PIN_NUM 7 + #endif + #include "hal/gpio_ll.h" + #define DBG_PIN_SET(v) gpio_ll_set_level(&GPIO, DBG_PIN_NUM, v) +#else + #define DBG_PIN_SET(v) +#endif + +#define CAM_CHECK(a, str, ret) if (!(a)) { \ + ESP_LOGE(TAG,"%s(%d): %s", __FUNCTION__, __LINE__, str); \ + return (ret); \ + } + +#define CAM_CHECK_GOTO(a, str, lab) if (!(a)) { \ + ESP_LOGE(TAG,"%s(%d): %s", __FUNCTION__, __LINE__, str); \ + goto lab; \ + } + +#define LCD_CAM_DMA_NODE_BUFFER_MAX_SIZE (4092) + +typedef enum { + CAM_IN_SUC_EOF_EVENT = 0, + CAM_VSYNC_EVENT +} cam_event_t; + +typedef enum { + CAM_STATE_IDLE = 0, + CAM_STATE_READ_BUF = 1, +} cam_state_t; + +typedef struct { + camera_fb_t fb; + uint8_t en; + //for RGB/YUV modes + lldesc_t *dma; + size_t fb_offset; +} cam_frame_t; + +typedef struct { + uint32_t dma_bytes_per_item; + uint32_t dma_buffer_size; + uint32_t dma_half_buffer_size; + uint32_t dma_half_buffer_cnt; + uint32_t dma_node_buffer_size; + uint32_t dma_node_cnt; + uint32_t frame_copy_cnt; + + //for JPEG mode + lldesc_t *dma; + uint8_t *dma_buffer; + + cam_frame_t *frames; + + QueueHandle_t event_queue; + QueueHandle_t frame_buffer_queue; + TaskHandle_t task_handle; + intr_handle_t cam_intr_handle; + + uint8_t dma_num;//ESP32-S3 + intr_handle_t dma_intr_handle;//ESP32-S3 + + uint8_t jpeg_mode; + uint8_t vsync_pin; + uint8_t vsync_invert; + uint32_t frame_cnt; + uint32_t recv_size; + bool swap_data; + bool psram_mode; + + //for RGB/YUV modes + uint16_t width; + uint16_t height; + uint8_t in_bytes_per_pixel; + uint8_t fb_bytes_per_pixel; + uint32_t fb_size; + + cam_state_t state; +} cam_obj_t; + + +bool ll_cam_stop(cam_obj_t *cam); +bool ll_cam_start(cam_obj_t *cam, int frame_pos); +esp_err_t ll_cam_config(cam_obj_t *cam, const camera_config_t *config); +void ll_cam_vsync_intr_enable(cam_obj_t *cam, bool en); +esp_err_t ll_cam_set_pin(cam_obj_t *cam, const camera_config_t *config); +esp_err_t ll_cam_init_isr(cam_obj_t *cam); +void ll_cam_do_vsync(cam_obj_t *cam); +uint8_t ll_cam_get_dma_align(cam_obj_t *cam); +bool ll_cam_dma_sizes(cam_obj_t *cam); +size_t ll_cam_memcpy(cam_obj_t *cam, uint8_t *out, const uint8_t *in, size_t len); +esp_err_t ll_cam_set_sample_mode(cam_obj_t *cam, pixformat_t pix_format, uint32_t xclk_freq_hz, uint8_t sensor_pid); + +// implemented in cam_hal +void ll_cam_send_event(cam_obj_t *cam, cam_event_t cam_event, BaseType_t * HPTaskAwoken); diff --git a/esp32-cam-rtos-allframes/nt99141.c b/esp32-cam-rtos-allframes/nt99141.c index 07a9cc4..450780b 100644 --- a/esp32-cam-rtos-allframes/nt99141.c +++ b/esp32-cam-rtos-allframes/nt99141.c @@ -144,28 +144,6 @@ static int write_addr_reg(uint8_t slv_addr, const uint16_t reg, uint16_t x_value #define write_reg_bits(slv_addr, reg, mask, enable) set_reg_bits(slv_addr, reg, 0, mask, enable?mask:0) -static int calc_sysclk(int xclk, bool pll_bypass, int pll_multiplier, int pll_sys_div, int pll_pre_div, bool pll_root_2x, int pll_seld5, bool pclk_manual, int pclk_div) -{ - const int pll_pre_div2x_map[] = { 2, 3, 4, 6 };//values are multiplied by two to avoid floats - const int pll_seld52x_map[] = { 2, 2, 4, 5 }; - - if (!pll_sys_div) { - pll_sys_div = 1; - } - - int pll_pre_div2x = pll_pre_div2x_map[pll_pre_div]; - int pll_root_div = pll_root_2x ? 2 : 1; - int pll_seld52x = pll_seld52x_map[pll_seld5]; - - int VCO = (xclk / 1000) * pll_multiplier * pll_root_div * 2 / pll_pre_div2x; - int PLLCLK = pll_bypass ? (xclk) : (VCO * 1000 * 2 / pll_sys_div / pll_seld52x); - int PCLK = PLLCLK / 2 / ((pclk_manual && pclk_div) ? pclk_div : 1); - int SYSCLK = PLLCLK / 4; - - ESP_LOGD(TAG, "Calculated VCO: %d Hz, PLLCLK: %d Hz, SYSCLK: %d Hz, PCLK: %d Hz", VCO * 1000, PLLCLK, SYSCLK, PCLK); - return SYSCLK; -} - static int set_pll(sensor_t *sensor, bool bypass, uint8_t multiplier, uint8_t sys_div, uint8_t pre_div, bool root_2x, uint8_t seld5, bool pclk_manual, uint8_t pclk_div) { return -1; @@ -309,7 +287,7 @@ static int set_framesize(sensor_t *sensor, framesize_t framesize) ret = write_regs(sensor->slv_addr, sensor_framesize_VGA); } - return 0; + return ret; } static int set_hmirror(sensor_t *sensor, int enable) @@ -682,7 +660,6 @@ static int set_brightness(sensor_t *sensor, int level) { int ret = 0; uint8_t value = 0; - bool negative = false; switch (level) { case 3: @@ -699,17 +676,14 @@ static int set_brightness(sensor_t *sensor, int level) case -1: value = 0x78; - negative = true; break; case -2: value = 0x70; - negative = true; break; case -3: value = 0x60; - negative = true; break; default: // 0 @@ -730,7 +704,6 @@ static int set_contrast(sensor_t *sensor, int level) { int ret = 0; uint8_t value1 = 0, value2 = 0 ; - bool negative = false; switch (level) { case 3: diff --git a/esp32-cam-rtos-allframes/ov2640.c b/esp32-cam-rtos-allframes/ov2640.c index 811023c..e96d4c2 100644 --- a/esp32-cam-rtos-allframes/ov2640.c +++ b/esp32-cam-rtos-allframes/ov2640.c @@ -157,26 +157,40 @@ static int set_window(sensor_t *sensor, ov2640_sensor_mode_t mode, int offset_x, {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; + if (sensor->pixformat == PIXFORMAT_JPEG) { + c.clk_2x = 0; + c.clk_div = 0; + c.pclk_auto = 0; + c.pclk_div = 8; + if(mode == OV2640_MODE_UXGA) { + c.pclk_div = 12; + } + // if (sensor->xclk_freq_hz == 16000000) { + // c.pclk_div = c.pclk_div / 2; + // } + } else { +#if CONFIG_IDF_TARGET_ESP32 + c.clk_2x = 0; +#else + c.clk_2x = 1; +#endif c.clk_div = 7; + c.pclk_auto = 1; + c.pclk_div = 8; + if (mode == OV2640_MODE_CIF) { + c.clk_div = 3; + } else if(mode == OV2640_MODE_UXGA) { + c.pclk_div = 12; + } } + ESP_LOGI(TAG, "Set PLL: clk_2x: %u, clk_div: %u, pclk_auto: %u, pclk_div: %u", c.clk_2x, c.clk_div, c.pclk_auto, c.pclk_div); 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); diff --git a/esp32-cam-rtos-allframes/ov3660.c b/esp32-cam-rtos-allframes/ov3660.c index 723ec5c..59393b7 100644 --- a/esp32-cam-rtos-allframes/ov3660.c +++ b/esp32-cam-rtos-allframes/ov3660.c @@ -142,7 +142,7 @@ static int calc_sysclk(int xclk, bool pll_bypass, int pll_multiplier, int pll_sy 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); + ESP_LOGI(TAG, "Calculated VCO: %d Hz, PLLCLK: %d Hz, SYSCLK: %d Hz, PCLK: %d Hz", VCO*1000, PLLCLK, SYSCLK, PCLK); return SYSCLK; } @@ -310,13 +310,13 @@ static int set_image_options(sensor_t *sensor) 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; + ESP_LOGW(TAG, "Invalid framesize: %u", framesize); + framesize = FRAMESIZE_QXGA; } + framesize_t old_framesize = sensor->status.framesize; + sensor->status.framesize = framesize; uint16_t w = resolution[framesize].width; uint16_t h = resolution[framesize].height; aspect_ratio_t ratio = resolution[sensor->status.framesize].aspect_ratio; @@ -355,7 +355,7 @@ static int set_framesize(sensor_t *sensor, framesize_t framesize) } if (sensor->pixformat == PIXFORMAT_JPEG) { - if (framesize == FRAMESIZE_QXGA) { + if (framesize == FRAMESIZE_QXGA || sensor->xclk_freq_hz == 16000000) { //40MHz SYSCLK and 10MHz PCLK ret = set_pll(sensor, false, 24, 1, 3, false, 0, true, 8); } else { @@ -363,12 +363,16 @@ static int set_framesize(sensor_t *sensor, framesize_t framesize) 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); + //tuned for 16MHz XCLK and 8MHz PCLK + if (framesize > FRAMESIZE_HVGA) { + //8MHz SYSCLK and 8MHz PCLK (4.44 FPS) + ret = set_pll(sensor, false, 4, 1, 0, false, 2, true, 2); + } else if (framesize >= FRAMESIZE_QVGA) { + //16MHz SYSCLK and 8MHz PCLK (10.25 FPS) + ret = set_pll(sensor, false, 8, 1, 0, false, 2, true, 4); } else { - //25MHz SYSCLK and 10MHz PCLK (15.45 FPS) - ret = set_pll(sensor, false, 5, 1, 0, false, 0, true, 5); + //32MHz SYSCLK and 8MHz PCLK (17.77 FPS) + ret = set_pll(sensor, false, 8, 1, 0, false, 0, true, 8); } } diff --git a/esp32-cam-rtos-allframes/ov5640.c b/esp32-cam-rtos-allframes/ov5640.c index e7adcf4..a9ab2a8 100644 --- a/esp32-cam-rtos-allframes/ov5640.c +++ b/esp32-cam-rtos-allframes/ov5640.c @@ -196,7 +196,7 @@ static int calc_sysclk(int xclk, bool pll_bypass, int pll_multiplier, int pll_sy 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); + ESP_LOGI(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; } @@ -209,6 +209,7 @@ static int set_pll(sensor_t *sensor, bool bypass, uint8_t multiplier, uint8_t sy if(multiplier > 127){ multiplier &= 0xFE;//only even integers above 127 } + ESP_LOGI(TAG, "Set PLL: bypass: %u, multiplier: %u, sys_div: %u, pre_div: %u, root_2x: %u, pclk_root_div: %u, pclk_manual: %u, pclk_div: %u", bypass, multiplier, sys_div, pre_div, root_2x, pclk_root_div, pclk_manual, pclk_div); calc_sysclk(sensor->xclk_freq_hz, bypass, multiplier, sys_div, pre_div, root_2x, pclk_root_div, pclk_manual, pclk_div); @@ -432,14 +433,22 @@ static int set_framesize(sensor_t *sensor, framesize_t framesize) if (sensor->pixformat == PIXFORMAT_JPEG) { //10MHz PCLK uint8_t sys_mul = 200; - if(framesize < FRAMESIZE_QVGA){ + if(framesize < FRAMESIZE_QVGA || sensor->xclk_freq_hz == 16000000){ sys_mul = 160; } else if(framesize < FRAMESIZE_XGA){ sys_mul = 180; } ret = set_pll(sensor, false, sys_mul, 4, 2, false, 2, true, 4); + //Set PLL: bypass: 0, multiplier: sys_mul, sys_div: 4, pre_div: 2, root_2x: 0, pclk_root_div: 2, pclk_manual: 1, pclk_div: 4 } else { - ret = set_pll(sensor, false, 10, 1, 1, false, 1, true, 4); + //ret = set_pll(sensor, false, 8, 1, 1, false, 1, true, 4); + if (framesize > FRAMESIZE_HVGA) { + ret = set_pll(sensor, false, 10, 1, 2, false, 1, true, 2); + } else if (framesize >= FRAMESIZE_QVGA) { + ret = set_pll(sensor, false, 8, 1, 1, false, 1, true, 4); + } else { + ret = set_pll(sensor, false, 20, 1, 1, false, 1, true, 8); + } } if (ret == 0) { diff --git a/esp32-cam-rtos-allframes/sccb.c b/esp32-cam-rtos-allframes/sccb.c index cb615bb..1a2c56e 100644 --- a/esp32-cam-rtos-allframes/sccb.c +++ b/esp32-cam-rtos-allframes/sccb.c @@ -11,6 +11,7 @@ #include #include #include "sccb.h" +#include "sensor.h" #include #include "sdkconfig.h" #if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) @@ -36,12 +37,10 @@ const int SCCB_I2C_PORT = 1; #else const int SCCB_I2C_PORT = 0; #endif -static uint8_t ESP_SLAVE_ADDR = 0x3c; int SCCB_Init(int pin_sda, int pin_scl) { - ESP_LOGI(TAG, "pin_sda %d pin_scl %d\n", pin_sda, pin_scl); - //log_i("SCCB_Init start"); + ESP_LOGI(TAG, "pin_sda %d pin_scl %d", pin_sda, pin_scl); i2c_config_t conf; memset(&conf, 0, sizeof(i2c_config_t)); conf.mode = I2C_MODE_MASTER; @@ -56,10 +55,30 @@ int SCCB_Init(int pin_sda, int pin_scl) return 0; } -uint8_t SCCB_Probe() +int SCCB_Deinit(void) +{ + return i2c_driver_delete(SCCB_I2C_PORT); +} + +uint8_t SCCB_Probe(void) { uint8_t slave_addr = 0x0; - while(slave_addr < 0x7f) { + // for (size_t i = 1; i < 0x80; i++) { + // i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + // i2c_master_start(cmd); + // i2c_master_write_byte(cmd, ( i << 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_LOGW(TAG, "Found I2C Device at 0x%02X", i); + // } + // } + for (size_t i = 0; i < CAMERA_MODEL_MAX; i++) { + if (slave_addr == camera_sensor[i].sccb_addr) { + continue; + } + slave_addr = camera_sensor[i].sccb_addr; 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); @@ -67,12 +86,10 @@ uint8_t SCCB_Probe() 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; + return slave_addr; } - slave_addr++; } - return ESP_SLAVE_ADDR; + return 0; } uint8_t SCCB_Read(uint8_t slv_addr, uint8_t reg) diff --git a/esp32-cam-rtos-allframes/sccb.h b/esp32-cam-rtos-allframes/sccb.h index 4d5b5b4..ace081a 100644 --- a/esp32-cam-rtos-allframes/sccb.h +++ b/esp32-cam-rtos-allframes/sccb.h @@ -10,6 +10,7 @@ #define __SCCB_H__ #include int SCCB_Init(int pin_sda, int pin_scl); +int SCCB_Deinit(void); 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); diff --git a/esp32-cam-rtos-allframes/sensor.c b/esp32-cam-rtos-allframes/sensor.c index 2e6d111..15714aa 100644 --- a/esp32-cam-rtos-allframes/sensor.c +++ b/esp32-cam-rtos-allframes/sensor.c @@ -1,5 +1,14 @@ #include "sensor.h" +const camera_sensor_info_t camera_sensor[CAMERA_MODEL_MAX] = { + {CAMERA_OV7725, OV7725_SCCB_ADDR, OV7725_PID, FRAMESIZE_VGA}, + {CAMERA_OV2640, OV2640_SCCB_ADDR, OV2640_PID, FRAMESIZE_UXGA}, + {CAMERA_OV3660, OV3660_SCCB_ADDR, OV3660_PID, FRAMESIZE_QXGA}, + {CAMERA_OV5640, OV5640_SCCB_ADDR, OV5640_PID, FRAMESIZE_QSXGA}, + {CAMERA_OV7670, OV7670_SCCB_ADDR, OV7670_PID, FRAMESIZE_VGA}, + {CAMERA_NT99141, NT99141_SCCB_ADDR, NT99141_PID, FRAMESIZE_HD}, +}; + const resolution_info_t resolution[FRAMESIZE_INVALID] = { { 96, 96, ASPECT_RATIO_1X1 }, /* 96x96 */ { 160, 120, ASPECT_RATIO_4X3 }, /* QQVGA */ diff --git a/esp32-cam-rtos-allframes/sensor.h b/esp32-cam-rtos-allframes/sensor.h index ad6cd89..ac54731 100644 --- a/esp32-cam-rtos-allframes/sensor.h +++ b/esp32-cam-rtos-allframes/sensor.h @@ -11,13 +11,45 @@ #include #include -#define NT99141_PID (0x14) -#define OV9650_PID (0x96) -#define OV7725_PID (0x77) -#define OV2640_PID (0x26) -#define OV3660_PID (0x36) -#define OV5640_PID (0x56) -#define OV7670_PID (0x76) +// Chip ID Registers +#define REG_PID 0x0A +#define REG_VER 0x0B +#define REG_MIDH 0x1C +#define REG_MIDL 0x1D + +#define REG16_CHIDH 0x300A +#define REG16_CHIDL 0x300B + +typedef enum { + OV9650_PID = 0x96, + OV7725_PID = 0x77, + OV2640_PID = 0x26, + OV3660_PID = 0x36, + OV5640_PID = 0x56, + OV7670_PID = 0x76, + NT99141_PID = 0x14 +} camera_pid_t; + +typedef enum { + CAMERA_OV7725, + CAMERA_OV2640, + CAMERA_OV3660, + CAMERA_OV5640, + CAMERA_OV7670, + CAMERA_NT99141, + CAMERA_MODEL_MAX, + CAMERA_NONE, + CAMERA_UNKNOWN +} camera_model_t; + +typedef enum { + OV2640_SCCB_ADDR = 0x30, + OV5640_SCCB_ADDR = 0x3C, + OV3660_SCCB_ADDR = 0x3C, + OV7725_SCCB_ADDR = 0x21, + OV7670_SCCB_ADDR = 0x21, + NT99141_SCCB_ADDR = 0x2A, +} camera_sccb_addr_t; typedef enum { PIXFORMAT_RGB565, // 2BPP/RGB565 @@ -58,6 +90,13 @@ typedef enum { FRAMESIZE_INVALID } framesize_t; +typedef struct { + const camera_model_t model; + const camera_sccb_addr_t sccb_addr; + const camera_pid_t pid; + const framesize_t max_size; +} camera_sensor_info_t; + typedef enum { ASPECT_RATIO_4X3, ASPECT_RATIO_3X2, @@ -101,6 +140,8 @@ typedef struct { // Resolution table (in sensor.c) extern const resolution_info_t resolution[]; +// camera sensor table (in sensor.c) +extern const camera_sensor_info_t camera_sensor[]; typedef struct { uint8_t MIDH; diff --git a/esp32-cam-rtos-allframes/to_bmp.c b/esp32-cam-rtos-allframes/to_bmp.c index 85f9c88..5a54bdb 100644 --- a/esp32-cam-rtos-allframes/to_bmp.c +++ b/esp32-cam-rtos-allframes/to_bmp.c @@ -24,6 +24,10 @@ #if ESP_IDF_VERSION_MAJOR >= 4 // IDF 4+ #if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 #include "esp32/spiram.h" +#elif CONFIG_IDF_TARGET_ESP32S2 +#include "esp32s2/spiram.h" +#elif CONFIG_IDF_TARGET_ESP32S3 +#include "esp32s3/spiram.h" #else #error Target CONFIG_IDF_TARGET is not supported #endif @@ -115,6 +119,54 @@ static bool _rgb_write(void * arg, uint16_t x, uint16_t y, uint16_t w, uint16_t return true; } +static bool _rgb565_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 jw2 = jpeg->width*2; + size_t t = y * jw; + size_t t2 = y * jw2; + size_t b = t + (h * jw); + size_t l = x * 2; + uint8_t *out = jpeg->output+jpeg->data_offset; + uint8_t *o = out; + size_t iy, iy2, ix, ix2; + + w = w * 3; + + for(iy=t, iy2=t2; iy> 3); + o[ix2+1] = c>>8; + o[ix2] = c&0xff; + } + data+=w; + } + return true; +} + //input buffer static uint32_t _jpg_read(void * arg, size_t index, uint8_t *buf, size_t len) { @@ -140,6 +192,21 @@ static bool jpg2rgb888(const uint8_t *src, size_t src_len, uint8_t * out, jpg_sc return true; } +bool jpg2rgb565(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, _rgb565_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) { diff --git a/esp32-cam-rtos-allframes/to_jpg.cpp b/esp32-cam-rtos-allframes/to_jpg.cpp index f8987a8..9b8905a 100644 --- a/esp32-cam-rtos-allframes/to_jpg.cpp +++ b/esp32-cam-rtos-allframes/to_jpg.cpp @@ -25,6 +25,10 @@ #if ESP_IDF_VERSION_MAJOR >= 4 // IDF 4+ #if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 #include "esp32/spiram.h" +#elif CONFIG_IDF_TARGET_ESP32S2 +#include "esp32s2/spiram.h" +#elif CONFIG_IDF_TARGET_ESP32S3 +#include "esp32s3/spiram.h" #else #error Target CONFIG_IDF_TARGET is not supported #endif @@ -195,7 +199,7 @@ public: return true; } if ((size_t)len > (max_len - index)) { - ESP_LOGW(TAG, "JPG output overflow: %d bytes", len - (max_len - index)); + //ESP_LOGW(TAG, "JPG output overflow: %d bytes (%d,%d,%d)", len - (max_len - index), len, index, max_len); len = max_len - index; } if (len) { @@ -215,7 +219,7 @@ bool fmt2jpg(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixf { //todo: allocate proper buffer for holding JPEG data //this should be enough for CIF frame size - int jpg_buf_len = 64*1024; + int jpg_buf_len = 128*1024; uint8_t * jpg_buf = (uint8_t *)_malloc(jpg_buf_len); diff --git a/esp32-cam-rtos-allframes/twi.c b/esp32-cam-rtos-allframes/twi.c deleted file mode 100644 index 25d71fc..0000000 --- a/esp32-cam-rtos-allframes/twi.c +++ /dev/null @@ -1,432 +0,0 @@ -/* - 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 @@ -41,11 +45,15 @@ esp_err_t camera_enable_out_clock(camera_config_t* config) ledc_channel_config_t ch_conf; ch_conf.gpio_num = config->pin_xclk; +#if CONFIG_IDF_TARGET_ESP32 ch_conf.speed_mode = LEDC_HIGH_SPEED_MODE; +#else + ch_conf.speed_mode = LEDC_LOW_SPEED_MODE; +#endif 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.duty = 1; ch_conf.hpoint = 0; err = ledc_channel_config(&ch_conf); if (err != ESP_OK) { diff --git a/esp32-cam-rtos/.gitignore b/esp32-cam-rtos/.gitignore new file mode 100644 index 0000000..5509140 --- /dev/null +++ b/esp32-cam-rtos/.gitignore @@ -0,0 +1 @@ +*.DS_Store diff --git a/esp32-cam-rtos/CMakeLists.txt b/esp32-cam-rtos/CMakeLists.txt new file mode 100644 index 0000000..536f6ba --- /dev/null +++ b/esp32-cam-rtos/CMakeLists.txt @@ -0,0 +1,61 @@ +if(IDF_TARGET STREQUAL "esp32" OR IDF_TARGET STREQUAL "esp32s2" OR IDF_TARGET STREQUAL "esp32s3") + set(COMPONENT_SRCS + driver/esp_camera.c + driver/cam_hal.c + driver/sccb.c + driver/sensor.c + sensors/ov2640.c + sensors/ov3660.c + sensors/ov5640.c + sensors/ov7725.c + sensors/ov7670.c + sensors/nt99141.c + conversions/yuv.c + conversions/to_jpg.cpp + conversions/to_bmp.c + conversions/jpge.cpp + conversions/esp_jpg_decode.c + ) + + set(COMPONENT_ADD_INCLUDEDIRS + driver/include + conversions/include + ) + + set(COMPONENT_PRIV_INCLUDEDIRS + driver/private_include + sensors/private_include + conversions/private_include + target/private_include + ) + + if(IDF_TARGET STREQUAL "esp32") + list(APPEND COMPONENT_SRCS + target/xclk.c + target/esp32/ll_cam.c + ) + endif() + + if(IDF_TARGET STREQUAL "esp32s2") + list(APPEND COMPONENT_SRCS + target/xclk.c + target/esp32s2/ll_cam.c + target/esp32s2/tjpgd.c + ) + + list(APPEND COMPONENT_PRIV_INCLUDEDIRS + target/esp32s2/private_include + ) + endif() + + if(IDF_TARGET STREQUAL "esp32s3") + list(APPEND COMPONENT_SRCS + target/esp32s3/ll_cam.c + ) + endif() + + set(COMPONENT_REQUIRES driver) + set(COMPONENT_PRIV_REQUIRES freertos nvs_flash) + + register_component() +endif() diff --git a/esp32-cam-rtos/Kconfig b/esp32-cam-rtos/Kconfig index 78fc607..49813f6 100644 --- a/esp32-cam-rtos/Kconfig +++ b/esp32-cam-rtos/Kconfig @@ -1,65 +1,71 @@ menu "Camera configuration" - -config OV2640_SUPPORT - bool "OV2640 Support" - default y - help - Enable this option if you want to use the OV2640. - Disable this option to save memory. -config OV7725_SUPPORT - bool "OV7725 Support" - default n - help - Enable this option if you want to use the OV7725. - Disable this option to save memory. - -config OV3660_SUPPORT - bool "OV3660 Support" - default y - help - Enable this option if you want to use the OV3360. - Disable this option to save memory. - -config OV5640_SUPPORT - bool "OV5640 Support" - default y - help - Enable this option if you want to use the OV5640. - Disable this option to save memory. - -config SCCB_HARDWARE_I2C - bool "Use hardware I2C for SCCB" - default y - help - Enable this option if you want to use hardware I2C to control the camera. - Disable this option to use software I2C. - -choice SCCB_HARDWARE_I2C_PORT - bool "I2C peripheral to use for SCCB" - depends on SCCB_HARDWARE_I2C - default SCCB_HARDWARE_I2C_PORT1 + config OV7670_SUPPORT + bool "Support OV7670 VGA" + default y + help + Enable this option if you want to use the OV7670. + Disable this option to save memory. - config SCCB_HARDWARE_I2C_PORT0 - bool "I2C0" - config SCCB_HARDWARE_I2C_PORT1 - bool "I2C1" + config OV7725_SUPPORT + bool "Support OV7725 SVGA" + default n + help + Enable this option if you want to use the OV7725. + Disable this option to save memory. -endchoice + config NT99141_SUPPORT + bool "Support NT99141 HD" + default y + help + Enable this option if you want to use the NT99141. + Disable this option to save memory. -choice CAMERA_TASK_PINNED_TO_CORE - bool "Camera task pinned to core" - default CAMERA_CORE0 - help - Pin the camera handle task to a certain core(0/1). It can also be done automatically choosing NO_AFFINITY. + config OV2640_SUPPORT + bool "Support OV2640 2MP" + default y + help + Enable this option if you want to use the OV2640. + Disable this option to save memory. - config CAMERA_CORE0 - bool "CORE0" - config CAMERA_CORE1 - bool "CORE1" - config CAMERA_NO_AFFINITY - bool "NO_AFFINITY" + config OV3660_SUPPORT + bool "Support OV3660 3MP" + default y + help + Enable this option if you want to use the OV3360. + Disable this option to save memory. + + config OV5640_SUPPORT + bool "Support OV5640 5MP" + default y + help + Enable this option if you want to use the OV5640. + Disable this option to save memory. + + choice SCCB_HARDWARE_I2C_PORT + bool "I2C peripheral to use for SCCB" + default SCCB_HARDWARE_I2C_PORT1 + + config SCCB_HARDWARE_I2C_PORT0 + bool "I2C0" + config SCCB_HARDWARE_I2C_PORT1 + bool "I2C1" + + endchoice + + choice CAMERA_TASK_PINNED_TO_CORE + bool "Camera task pinned to core" + default CAMERA_CORE0 + help + Pin the camera handle task to a certain core(0/1). It can also be done automatically choosing NO_AFFINITY. + + config CAMERA_CORE0 + bool "CORE0" + config CAMERA_CORE1 + bool "CORE1" + config CAMERA_NO_AFFINITY + bool "NO_AFFINITY" + + endchoice -endchoice - endmenu diff --git a/esp32-cam-rtos/README.md b/esp32-cam-rtos/README.md index d773b9f..96872e6 100644 --- a/esp32-cam-rtos/README.md +++ b/esp32-cam-rtos/README.md @@ -1,16 +1,358 @@ -# ESP32 MJPEG Multiclient Streaming Server +# ESP32 Camera Driver -This is a simple MJPEG streaming webserver implemented for AI-Thinker ESP32-CAM or ESP-EYE modules. +## General Information -This is tested to work with **VLC** and **Blynk** video widget. +This repository hosts ESP32, ESP32-S2 and ESP32-S3 compatible driver for OV2640, OV3660, OV5640, OV7670 and OV7725 image sensors. Additionally it provides a few tools, which allow converting the captured frame data to the more common BMP and JPEG formats. +## Important to Remember +- Except when using CIF or lower resolution with JPEG, the driver requires PSRAM to be installed and activated. +- Using YUV or RGB puts a lot of strain on the chip because writing to PSRAM is not particularly fast. The result is that image data might be missing. This is particularly true if WiFi is enabled. If you need RGB data, it is recommended that JPEG is captured and then turned into RGB using `fmt2rgb888` or `fmt2bmp`/`frame2bmp`. +- When 1 frame buffer is used, the driver will wait for the current frame to finish (VSYNC) and start I2S DMA. After the frame is acquired, I2S will be stopped and the frame buffer returned to the application. This approach gives more control over the system, but results in longer time to get the frame. +- When 2 or more frame bufers are used, I2S is running in continuous mode and each frame is pushed to a queue that the application can access. This approach puts more strain on the CPU/Memory, but allows for double the frame rate. Please use only with JPEG. -**This version uses FreeRTOS tasks to enable streaming to up to 10 connected clients** +## Installation Instructions +### Using esp-idf -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/) +- Clone or download and extract the repository to the components folder of your ESP-IDF project +- Enable PSRAM in `menuconfig` (also set Flash and PSRAM frequiencies to 80MHz) +- Include `esp_camera.h` in your code + +### Using PlatformIO + +The easy way -- on the `env` section of `platformio.ini`, add the following: + +```ini +[env] +lib_deps = + esp32-camera +``` + +Now the `esp_camera.h` is available to be included: + +```c +#include "esp_camera.h" +``` + +Enable PSRAM on `menuconfig` or type it direclty on `sdkconfig`. Check the [official doc](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/kconfig.html#config-esp32-spiram-support) for more info. + +``` +CONFIG_ESP32_SPIRAM_SUPPORT=y +``` + +***Arduino*** The easy-way (content above) only seems to work if you're using `framework=arduino` which seems to take a bunch of the guesswork out (thanks Arduino!) but also suck up a lot more memory and flash, almost crippling the performance. If you plan to use the `framework=espidf` then read the sections below carefully!! + +## Platform.io lib/submodule (for framework=espidf) + +It's probably easier to just skip the platform.io library registry version and link the git repo as a submodule. (i.e. using code outside the platform.io library management). In this example we will install this as a submodule inside the platform.io $project/lib folder: +``` +cd $project\lib +git submodule add -b master https://github.com/espressif/esp32-camera.git +``` + +Then in `platformio.ini` file +``` +build_flags = + -I../lib/esp32-camera +``` +After that `#include "esp_camera.h"` statement will be available. Now the module is included, and you're hopefully back to the same place as the easy-Arduino way. + +**Warning about platform.io/espidf and fresh (not initialized) git repos** +There is a sharp-edge on you'll discover in the platform.io build process (in espidf v3.3 & 4.0.1) where a project which has only had `git init` but nothing committed will crash platform.io build process with highly non-useful output. The cause is due to lack of a version (making you think you did something wrong, when you didn't at all) - the output is horribly non-descript. Solution: the devs want you to create a file called version.txt with a number in it, or simply commit any file to the projects git repo and use git. This happens because platform.io build process tries to be too clever and determine the build version number from the git repo - it's a sharp edge you'll only encounter if you're experimenting on a new project with no commits .. like wtf is my camera not working let's try a 'clean project'?! + +## Platform.io Kconfig +Kconfig is used by the platform.io menuconfig (accessed by running: `pio run -t menuconfig`) to interactively manage the various #ifdef statements throughout the espidf and supporting libraries (i.e. this repo: esp32-camera and arduino-esp32.git). The menuconfig process generates the `sdkconfig` file which is ultimately used behind the scenes by espidf compile+build process. + +**Make sure to append or symlink** [this `Kconfig`](./Kconfig) content into the `Kconfig` of your project. + +You symlink (or copy) the included Kconfig into your platform.io projects src directory. The file should be named `Kconfig.projbuild` in your projects src\ directory or you could also add the library path to a CMakefile.txt and hope the `Kconfig` (or `Kconfig.projbuild`) gets discovered by the menuconfig process, though this unpredictable for me. + +The unpredictable wonky behavior in platform.io build process around Kconfig naming (Kconfig vs. Kconfig.projbuild) occurs between espidf versions 3.3 and 4.0 - but if you don't see "Camera configuration" in your `pio run -t menuconfig` then there is no point trying to test camera code (it may compile, but it probably won't work!) and it seems the platform.io devs (when they built their wrapper around the espidf menuconfig) didn't implement it properly. You've probably already figured out you can't use the espidf build tools since the files are in totally different locations and also different versions with sometimes different syntax. This is one of those times you might consider changing the `platformio.ini` from `platform=espressif32` to `platform=https://github.com/platformio/platform-espressif32.git#develop` to get a more recent version of the espidf 4.0 tools. + +However with a bit of patience and experimenting you'll figure the Kconfig out. Once Kconfig (or Kconfig.projbuild) is working then you will be able to choose the configurations according to your setup or the camera libraries will be compiled. Although you might also need to delete your .pio/build directory before the options appear .. again, the `pio run -t menuconfig` doens't always notice the new Kconfig files! + +If you miss-skip-ignore this critical step the camera module will compile but camera logic inside the library will be 'empty' because the Kconfig sets the proper #ifdef statements during the build process to initialize the selected cameras. It's very not optional! + +### Kconfig options + +| config | description | default | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------ | +| CONFIG_OV2640_SUPPORT | Support for OV2640 camera | enabled | +| CONFIG_OV7725_SUPPORT | Support for OV7725 camera | disabled | +| CONFIG_OV3660_SUPPORT | Support for OV3660 camera | enabled | +| CONFIG_OV5640_SUPPORT | Support for OV5640 camera | enabled | +| CONFIG_SCCB_HARDWARE_I2C | Enable this option if you want to use hardware I2C to control the camera. Disable this option to use software I2C. | enabled | +| CONFIG_SCCB_HARDWARE_I2C_PORT | I2C peripheral to use for SCCB. Can be I2C0 and I2C1. | CONFIG_SCCB_HARDWARE_I2C_PORT1 | +| CONFIG_CAMERA_TASK_PINNED_TO_CORE | Pin the camera handle task to a certain core(0/1). It can also be done automatically choosing NO_AFFINITY. Can be CAMERA_CORE0, CAMERA_CORE1 or NO_AFFINITY. | CONFIG_CAMERA_CORE0 | + +## Examples + +### Initialization + +```c +#include "esp_camera.h" + +//WROVER-KIT PIN Map +#define CAM_PIN_PWDN -1 //power down is not used +#define CAM_PIN_RESET -1 //software reset will be performed +#define CAM_PIN_XCLK 21 +#define CAM_PIN_SIOD 26 +#define CAM_PIN_SIOC 27 + +#define CAM_PIN_D7 35 +#define CAM_PIN_D6 34 +#define CAM_PIN_D5 39 +#define CAM_PIN_D4 36 +#define CAM_PIN_D3 19 +#define CAM_PIN_D2 18 +#define CAM_PIN_D1 5 +#define CAM_PIN_D0 4 +#define CAM_PIN_VSYNC 25 +#define CAM_PIN_HREF 23 +#define CAM_PIN_PCLK 22 + +static camera_config_t camera_config = { + .pin_pwdn = CAM_PIN_PWDN, + .pin_reset = CAM_PIN_RESET, + .pin_xclk = CAM_PIN_XCLK, + .pin_sscb_sda = CAM_PIN_SIOD, + .pin_sscb_scl = CAM_PIN_SIOC, + + .pin_d7 = CAM_PIN_D7, + .pin_d6 = CAM_PIN_D6, + .pin_d5 = CAM_PIN_D5, + .pin_d4 = CAM_PIN_D4, + .pin_d3 = CAM_PIN_D3, + .pin_d2 = CAM_PIN_D2, + .pin_d1 = CAM_PIN_D1, + .pin_d0 = CAM_PIN_D0, + .pin_vsync = CAM_PIN_VSYNC, + .pin_href = CAM_PIN_HREF, + .pin_pclk = CAM_PIN_PCLK, + + .xclk_freq_hz = 20000000,//EXPERIMENTAL: Set to 16MHz on ESP32-S2 or ESP32-S3 to enable EDMA mode + .ledc_timer = LEDC_TIMER_0, + .ledc_channel = LEDC_CHANNEL_0, + + .pixel_format = PIXFORMAT_JPEG,//YUV422,GRAYSCALE,RGB565,JPEG + .frame_size = FRAMESIZE_UXGA,//QQVGA-QXGA Do not use sizes above QVGA when not JPEG + + .jpeg_quality = 12, //0-63 lower number means higher quality + .fb_count = 1, //if more than one, i2s runs in continuous mode. Use only with JPEG + .grab_mode = CAMERA_GRAB_WHEN_EMPTY//CAMERA_GRAB_LATEST. Sets when buffers should be filled +}; + +esp_err_t camera_init(){ + //power up the camera if PWDN pin is defined + if(CAM_PIN_PWDN != -1){ + pinMode(CAM_PIN_PWDN, OUTPUT); + digitalWrite(CAM_PIN_PWDN, LOW); + } + + //initialize the camera + esp_err_t err = esp_camera_init(&camera_config); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Camera Init Failed"); + return err; + } + + return ESP_OK; +} + +esp_err_t camera_capture(){ + //acquire a frame + camera_fb_t * fb = esp_camera_fb_get(); + if (!fb) { + ESP_LOGE(TAG, "Camera Capture Failed"); + return ESP_FAIL; + } + //replace this with your own function + process_image(fb->width, fb->height, fb->format, fb->buf, fb->len); + + //return the frame buffer back to the driver for reuse + esp_camera_fb_return(fb); + return ESP_OK; +} +``` + +### JPEG HTTP Capture + +```c +#include "esp_camera.h" +#include "esp_http_server.h" +#include "esp_timer.h" + +typedef struct { + httpd_req_t *req; + size_t len; +} jpg_chunking_t; + +static size_t jpg_encode_stream(void * arg, size_t index, const void* data, size_t len){ + jpg_chunking_t *j = (jpg_chunking_t *)arg; + if(!index){ + j->len = 0; + } + if(httpd_resp_send_chunk(j->req, (const char *)data, len) != ESP_OK){ + return 0; + } + j->len += len; + return len; +} + +esp_err_t jpg_httpd_handler(httpd_req_t *req){ + camera_fb_t * fb = NULL; + esp_err_t res = ESP_OK; + size_t fb_len = 0; + int64_t fr_start = esp_timer_get_time(); + + fb = esp_camera_fb_get(); + if (!fb) { + ESP_LOGE(TAG, "Camera capture failed"); + httpd_resp_send_500(req); + return ESP_FAIL; + } + res = httpd_resp_set_type(req, "image/jpeg"); + if(res == ESP_OK){ + res = httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg"); + } + + if(res == ESP_OK){ + if(fb->format == PIXFORMAT_JPEG){ + fb_len = fb->len; + res = httpd_resp_send(req, (const char *)fb->buf, fb->len); + } else { + jpg_chunking_t jchunk = {req, 0}; + res = frame2jpg_cb(fb, 80, jpg_encode_stream, &jchunk)?ESP_OK:ESP_FAIL; + httpd_resp_send_chunk(req, NULL, 0); + fb_len = jchunk.len; + } + } + esp_camera_fb_return(fb); + int64_t fr_end = esp_timer_get_time(); + ESP_LOGI(TAG, "JPG: %uKB %ums", (uint32_t)(fb_len/1024), (uint32_t)((fr_end - fr_start)/1000)); + return res; +} +``` + +### JPEG HTTP Stream + +```c +#include "esp_camera.h" +#include "esp_http_server.h" +#include "esp_timer.h" + +#define PART_BOUNDARY "123456789000000000000987654321" +static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY; +static const char* _STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n"; +static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n"; + +esp_err_t jpg_stream_httpd_handler(httpd_req_t *req){ + camera_fb_t * fb = NULL; + esp_err_t res = ESP_OK; + size_t _jpg_buf_len; + uint8_t * _jpg_buf; + char * part_buf[64]; + static int64_t last_frame = 0; + if(!last_frame) { + last_frame = esp_timer_get_time(); + } + + res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE); + if(res != ESP_OK){ + return res; + } + + while(true){ + fb = esp_camera_fb_get(); + if (!fb) { + ESP_LOGE(TAG, "Camera capture failed"); + res = ESP_FAIL; + break; + } + if(fb->format != PIXFORMAT_JPEG){ + bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len); + if(!jpeg_converted){ + ESP_LOGE(TAG, "JPEG compression failed"); + esp_camera_fb_return(fb); + res = ESP_FAIL; + } + } else { + _jpg_buf_len = fb->len; + _jpg_buf = fb->buf; + } + + if(res == ESP_OK){ + res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY)); + } + if(res == ESP_OK){ + size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, _jpg_buf_len); + + res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen); + } + if(res == ESP_OK){ + res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len); + } + if(fb->format != PIXFORMAT_JPEG){ + free(_jpg_buf); + } + esp_camera_fb_return(fb); + if(res != ESP_OK){ + break; + } + int64_t fr_end = esp_timer_get_time(); + int64_t frame_time = fr_end - last_frame; + last_frame = fr_end; + frame_time /= 1000; + ESP_LOGI(TAG, "MJPG: %uKB %ums (%.1ffps)", + (uint32_t)(_jpg_buf_len/1024), + (uint32_t)frame_time, 1000.0 / (uint32_t)frame_time); + } + + last_frame = 0; + return res; +} +``` + +### BMP HTTP Capture + +```c +#include "esp_camera.h" +#include "esp_http_server.h" +#include "esp_timer.h" + +esp_err_t bmp_httpd_handler(httpd_req_t *req){ + camera_fb_t * fb = NULL; + esp_err_t res = ESP_OK; + int64_t fr_start = esp_timer_get_time(); + + fb = esp_camera_fb_get(); + if (!fb) { + ESP_LOGE(TAG, "Camera capture failed"); + httpd_resp_send_500(req); + return ESP_FAIL; + } + + uint8_t * buf = NULL; + size_t buf_len = 0; + bool converted = frame2bmp(fb, &buf, &buf_len); + esp_camera_fb_return(fb); + if(!converted){ + ESP_LOGE(TAG, "BMP conversion failed"); + httpd_resp_send_500(req); + return ESP_FAIL; + } + + res = httpd_resp_set_type(req, "image/x-windows-bmp") + || httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.bmp") + || httpd_resp_send(req, (const char *)buf, buf_len); + free(buf); + int64_t fr_end = esp_timer_get_time(); + ESP_LOGI(TAG, "BMP: %uKB %ums", (uint32_t)(buf_len/1024), (uint32_t)((fr_end - fr_start)/1000)); + return res; +} +``` diff --git a/esp32-cam-rtos/cam_hal.c b/esp32-cam-rtos/cam_hal.c new file mode 100644 index 0000000..f2b65de --- /dev/null +++ b/esp32-cam-rtos/cam_hal.c @@ -0,0 +1,472 @@ +// Copyright 2010-2020 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_heap_caps.h" +#include "ll_cam.h" +#include "cam_hal.h" + +static const char *TAG = "cam_hal"; + +static cam_obj_t *cam_obj = NULL; + +static const uint32_t JPEG_SOI_MARKER = 0xFFD8FF; // written in little-endian for esp32 +static const uint16_t JPEG_EOI_MARKER = 0xD9FF; // written in little-endian for esp32 + +static int cam_verify_jpeg_soi(const uint8_t *inbuf, uint32_t length) +{ + uint32_t sig = *((uint32_t *)inbuf) & 0xFFFFFF; + if(sig != JPEG_SOI_MARKER) { + for (uint32_t i = 0; i < length; i++) { + sig = *((uint32_t *)(&inbuf[i])) & 0xFFFFFF; + if (sig == JPEG_SOI_MARKER) { + ESP_LOGW(TAG, "SOI: %d", i); + return i; + } + } + ESP_LOGW(TAG, "NO-SOI"); + return -1; + } + return 0; +} + +static int cam_verify_jpeg_eoi(const uint8_t *inbuf, uint32_t length) +{ + int offset = -1; + uint8_t *dptr = (uint8_t *)inbuf + length - 2; + while (dptr > inbuf) { + uint16_t sig = *((uint16_t *)dptr); + if (JPEG_EOI_MARKER == sig) { + offset = dptr - inbuf; + //ESP_LOGW(TAG, "EOI: %d", length - (offset + 2)); + return offset; + } + dptr--; + } + return -1; +} + +static bool cam_get_next_frame(int * frame_pos) +{ + if(!cam_obj->frames[*frame_pos].en){ + for (int x = 0; x < cam_obj->frame_cnt; x++) { + if (cam_obj->frames[x].en) { + *frame_pos = x; + return true; + } + } + } else { + return true; + } + return false; +} + +static bool cam_start_frame(int * frame_pos) +{ + if (cam_get_next_frame(frame_pos)) { + if(ll_cam_start(cam_obj, *frame_pos)){ + // Vsync the frame manually + ll_cam_do_vsync(cam_obj); + uint64_t us = (uint64_t)esp_timer_get_time(); + cam_obj->frames[*frame_pos].fb.timestamp.tv_sec = us / 1000000UL; + cam_obj->frames[*frame_pos].fb.timestamp.tv_usec = us % 1000000UL; + return true; + } + } + return false; +} + +void IRAM_ATTR ll_cam_send_event(cam_obj_t *cam, cam_event_t cam_event, BaseType_t * HPTaskAwoken) +{ + if (xQueueSendFromISR(cam->event_queue, (void *)&cam_event, HPTaskAwoken) != pdTRUE) { + ll_cam_stop(cam); + cam->state = CAM_STATE_IDLE; + ESP_EARLY_LOGE(TAG, "EV-OVF"); + } +} + +//Copy fram from DMA dma_buffer to fram dma_buffer +static void cam_task(void *arg) +{ + int cnt = 0; + int frame_pos = 0; + cam_obj->state = CAM_STATE_IDLE; + cam_event_t cam_event = 0; + + xQueueReset(cam_obj->event_queue); + + while (1) { + xQueueReceive(cam_obj->event_queue, (void *)&cam_event, portMAX_DELAY); + DBG_PIN_SET(1); + switch (cam_obj->state) { + + case CAM_STATE_IDLE: { + if (cam_event == CAM_VSYNC_EVENT) { + //DBG_PIN_SET(1); + if(cam_start_frame(&frame_pos)){ + cam_obj->frames[frame_pos].fb.len = 0; + cam_obj->state = CAM_STATE_READ_BUF; + } + cnt = 0; + } + } + break; + + case CAM_STATE_READ_BUF: { + camera_fb_t * frame_buffer_event = &cam_obj->frames[frame_pos].fb; + size_t pixels_per_dma = (cam_obj->dma_half_buffer_size * cam_obj->fb_bytes_per_pixel) / (cam_obj->dma_bytes_per_item * cam_obj->in_bytes_per_pixel); + + if (cam_event == CAM_IN_SUC_EOF_EVENT) { + if(!cam_obj->psram_mode){ + if (cam_obj->fb_size < (frame_buffer_event->len + pixels_per_dma)) { + ESP_LOGW(TAG, "FB-OVF"); + ll_cam_stop(cam_obj); + DBG_PIN_SET(0); + continue; + } + frame_buffer_event->len += ll_cam_memcpy(cam_obj, + &frame_buffer_event->buf[frame_buffer_event->len], + &cam_obj->dma_buffer[(cnt % cam_obj->dma_half_buffer_cnt) * cam_obj->dma_half_buffer_size], + cam_obj->dma_half_buffer_size); + } + //Check for JPEG SOI in the first buffer. stop if not found + if (cam_obj->jpeg_mode && cnt == 0 && cam_verify_jpeg_soi(frame_buffer_event->buf, frame_buffer_event->len) != 0) { + ll_cam_stop(cam_obj); + cam_obj->state = CAM_STATE_IDLE; + } + cnt++; + + } else if (cam_event == CAM_VSYNC_EVENT) { + //DBG_PIN_SET(1); + ll_cam_stop(cam_obj); + + if (cnt || !cam_obj->jpeg_mode || cam_obj->psram_mode) { + if (cam_obj->jpeg_mode) { + if (!cam_obj->psram_mode) { + if (cam_obj->fb_size < (frame_buffer_event->len + pixels_per_dma)) { + ESP_LOGW(TAG, "FB-OVF"); + cnt--; + } else { + frame_buffer_event->len += ll_cam_memcpy(cam_obj, + &frame_buffer_event->buf[frame_buffer_event->len], + &cam_obj->dma_buffer[(cnt % cam_obj->dma_half_buffer_cnt) * cam_obj->dma_half_buffer_size], + cam_obj->dma_half_buffer_size); + } + } + cnt++; + } + + cam_obj->frames[frame_pos].en = 0; + + if (cam_obj->psram_mode) { + if (cam_obj->jpeg_mode) { + frame_buffer_event->len = cnt * cam_obj->dma_half_buffer_size; + } else { + frame_buffer_event->len = cam_obj->recv_size; + } + } else if (!cam_obj->jpeg_mode) { + if (frame_buffer_event->len != cam_obj->fb_size) { + cam_obj->frames[frame_pos].en = 1; + ESP_LOGE(TAG, "FB-SIZE: %u != %u", frame_buffer_event->len, cam_obj->fb_size); + } + } + //send frame + if(!cam_obj->frames[frame_pos].en && xQueueSend(cam_obj->frame_buffer_queue, (void *)&frame_buffer_event, 0) != pdTRUE) { + //pop frame buffer from the queue + camera_fb_t * fb2 = NULL; + if(xQueueReceive(cam_obj->frame_buffer_queue, &fb2, 0) == pdTRUE) { + //push the new frame to the end of the queue + if (xQueueSend(cam_obj->frame_buffer_queue, (void *)&frame_buffer_event, 0) != pdTRUE) { + cam_obj->frames[frame_pos].en = 1; + ESP_LOGE(TAG, "FBQ-SND"); + } + //free the popped buffer + cam_give(fb2); + } else { + //queue is full and we could not pop a frame from it + cam_obj->frames[frame_pos].en = 1; + ESP_LOGE(TAG, "FBQ-RCV"); + } + } + } + + if(!cam_start_frame(&frame_pos)){ + cam_obj->state = CAM_STATE_IDLE; + } else { + cam_obj->frames[frame_pos].fb.len = 0; + } + cnt = 0; + } + } + break; + } + DBG_PIN_SET(0); + } +} + +static lldesc_t * allocate_dma_descriptors(uint32_t count, uint16_t size, uint8_t * buffer) +{ + lldesc_t *dma = (lldesc_t *)heap_caps_malloc(count * sizeof(lldesc_t), MALLOC_CAP_DMA); + if (dma == NULL) { + return dma; + } + + for (int x = 0; x < count; x++) { + dma[x].size = size; + dma[x].length = 0; + dma[x].sosf = 0; + dma[x].eof = 0; + dma[x].owner = 1; + dma[x].buf = (buffer + size * x); + dma[x].empty = (uint32_t)&dma[(x + 1) % count]; + } + return dma; +} + +static esp_err_t cam_dma_config() +{ + bool ret = ll_cam_dma_sizes(cam_obj); + if (0 == ret) { + return ESP_FAIL; + } + + cam_obj->dma_node_cnt = (cam_obj->dma_buffer_size) / cam_obj->dma_node_buffer_size; // Number of DMA nodes + cam_obj->frame_copy_cnt = cam_obj->recv_size / cam_obj->dma_half_buffer_size; // Number of interrupted copies, ping-pong copy + + ESP_LOGI(TAG, "buffer_size: %d, half_buffer_size: %d, node_buffer_size: %d, node_cnt: %d, total_cnt: %d", + cam_obj->dma_buffer_size, cam_obj->dma_half_buffer_size, cam_obj->dma_node_buffer_size, cam_obj->dma_node_cnt, cam_obj->frame_copy_cnt); + + cam_obj->dma_buffer = NULL; + cam_obj->dma = NULL; + + cam_obj->frames = (cam_frame_t *)heap_caps_calloc(1, cam_obj->frame_cnt * sizeof(cam_frame_t), MALLOC_CAP_DEFAULT); + CAM_CHECK(cam_obj->frames != NULL, "frames malloc failed", ESP_FAIL); + + uint8_t dma_align = 0; + size_t fb_size = cam_obj->fb_size; + if (cam_obj->psram_mode) { + dma_align = ll_cam_get_dma_align(cam_obj); + if (cam_obj->fb_size < cam_obj->recv_size) { + fb_size = cam_obj->recv_size; + } + } + for (int x = 0; x < cam_obj->frame_cnt; x++) { + cam_obj->frames[x].dma = NULL; + cam_obj->frames[x].fb_offset = 0; + cam_obj->frames[x].en = 0; + cam_obj->frames[x].fb.buf = (uint8_t *)heap_caps_malloc(fb_size * sizeof(uint8_t) + dma_align, MALLOC_CAP_SPIRAM); + CAM_CHECK(cam_obj->frames[x].fb.buf != NULL, "frame buffer malloc failed", ESP_FAIL); + if (cam_obj->psram_mode) { + //align PSRAM buffer. TODO: save the offset so proper address can be freed later + cam_obj->frames[x].fb_offset = dma_align - ((uint32_t)cam_obj->frames[x].fb.buf & (dma_align - 1)); + cam_obj->frames[x].fb.buf += cam_obj->frames[x].fb_offset; + ESP_LOGI(TAG, "Frame[%d]: Offset: %u, Addr: 0x%08X", x, cam_obj->frames[x].fb_offset, (uint32_t)cam_obj->frames[x].fb.buf); + cam_obj->frames[x].dma = allocate_dma_descriptors(cam_obj->dma_node_cnt, cam_obj->dma_node_buffer_size, cam_obj->frames[x].fb.buf); + CAM_CHECK(cam_obj->frames[x].dma != NULL, "frame dma malloc failed", ESP_FAIL); + } + cam_obj->frames[x].en = 1; + } + + if (!cam_obj->psram_mode) { + cam_obj->dma_buffer = (uint8_t *)heap_caps_malloc(cam_obj->dma_buffer_size * sizeof(uint8_t), MALLOC_CAP_DMA); + CAM_CHECK(cam_obj->dma_buffer != NULL, "dma_buffer malloc failed", ESP_FAIL); + + cam_obj->dma = allocate_dma_descriptors(cam_obj->dma_node_cnt, cam_obj->dma_node_buffer_size, cam_obj->dma_buffer); + CAM_CHECK(cam_obj->dma != NULL, "dma malloc failed", ESP_FAIL); + } + + return ESP_OK; +} + +esp_err_t cam_init(const camera_config_t *config) +{ + CAM_CHECK(NULL != config, "config pointer is invalid", ESP_ERR_INVALID_ARG); + + esp_err_t ret = ESP_OK; + cam_obj = (cam_obj_t *)heap_caps_calloc(1, sizeof(cam_obj_t), MALLOC_CAP_DMA); + CAM_CHECK(NULL != cam_obj, "lcd_cam object malloc error", ESP_ERR_NO_MEM); + + cam_obj->swap_data = 0; + cam_obj->vsync_pin = config->pin_vsync; + cam_obj->vsync_invert = true; + + ll_cam_set_pin(cam_obj, config); + ret = ll_cam_config(cam_obj, config); + CAM_CHECK_GOTO(ret == ESP_OK, "ll_cam initialize failed", err); + +#if CAMERA_DBG_PIN_ENABLE + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[DBG_PIN_NUM], PIN_FUNC_GPIO); + gpio_set_direction(DBG_PIN_NUM, GPIO_MODE_OUTPUT); + gpio_set_pull_mode(DBG_PIN_NUM, GPIO_FLOATING); +#endif + + ESP_LOGI(TAG, "cam init ok"); + return ESP_OK; + +err: + free(cam_obj); + cam_obj = NULL; + return ESP_FAIL; +} + +esp_err_t cam_config(const camera_config_t *config, framesize_t frame_size, uint8_t sensor_pid) +{ + CAM_CHECK(NULL != config, "config pointer is invalid", ESP_ERR_INVALID_ARG); + esp_err_t ret = ESP_OK; + + ret = ll_cam_set_sample_mode(cam_obj, (pixformat_t)config->pixel_format, config->xclk_freq_hz, sensor_pid); + + cam_obj->jpeg_mode = config->pixel_format == PIXFORMAT_JPEG; +#if CONFIG_IDF_TARGET_ESP32 + cam_obj->psram_mode = false; +#else + cam_obj->psram_mode = (config->xclk_freq_hz == 16000000); +#endif + cam_obj->frame_cnt = config->fb_count; + cam_obj->width = resolution[frame_size].width; + cam_obj->height = resolution[frame_size].height; + + if(cam_obj->jpeg_mode){ + cam_obj->recv_size = cam_obj->width * cam_obj->height / 5; + cam_obj->fb_size = cam_obj->recv_size; + } else { + cam_obj->recv_size = cam_obj->width * cam_obj->height * cam_obj->in_bytes_per_pixel; + cam_obj->fb_size = cam_obj->width * cam_obj->height * cam_obj->fb_bytes_per_pixel; + } + + ret = cam_dma_config(); + CAM_CHECK_GOTO(ret == ESP_OK, "cam_dma_config failed", err); + + cam_obj->event_queue = xQueueCreate(cam_obj->dma_half_buffer_cnt - 1, sizeof(cam_event_t)); + CAM_CHECK_GOTO(cam_obj->event_queue != NULL, "event_queue create failed", err); + + size_t frame_buffer_queue_len = cam_obj->frame_cnt; + if (config->grab_mode == CAMERA_GRAB_LATEST && cam_obj->frame_cnt > 1) { + frame_buffer_queue_len = cam_obj->frame_cnt - 1; + } + cam_obj->frame_buffer_queue = xQueueCreate(frame_buffer_queue_len, sizeof(camera_fb_t*)); + CAM_CHECK_GOTO(cam_obj->frame_buffer_queue != NULL, "frame_buffer_queue create failed", err); + + ret = ll_cam_init_isr(cam_obj); + CAM_CHECK_GOTO(ret == ESP_OK, "cam intr alloc failed", err); + + +#if CONFIG_CAMERA_CORE0 + xTaskCreatePinnedToCore(cam_task, "cam_task", 2048, NULL, configMAX_PRIORITIES - 2, &cam_obj->task_handle, 0); +#elif CONFIG_CAMERA_CORE1 + xTaskCreatePinnedToCore(cam_task, "cam_task", 2048, NULL, configMAX_PRIORITIES - 2, &cam_obj->task_handle, 1); +#else + xTaskCreate(cam_task, "cam_task", 2048, NULL, configMAX_PRIORITIES - 2, &cam_obj->task_handle); +#endif + + ESP_LOGI(TAG, "cam config ok"); + return ESP_OK; + +err: + cam_deinit(); + return ESP_FAIL; +} + +esp_err_t cam_deinit(void) +{ + if (!cam_obj) { + return ESP_FAIL; + } + + cam_stop(); + gpio_isr_handler_remove(cam_obj->vsync_pin); + if (cam_obj->task_handle) { + vTaskDelete(cam_obj->task_handle); + } + if (cam_obj->event_queue) { + vQueueDelete(cam_obj->event_queue); + } + if (cam_obj->frame_buffer_queue) { + vQueueDelete(cam_obj->frame_buffer_queue); + } + if (cam_obj->dma) { + free(cam_obj->dma); + } + if (cam_obj->dma_buffer) { + free(cam_obj->dma_buffer); + } + if (cam_obj->frames) { + for (int x = 0; x < cam_obj->frame_cnt; x++) { + free(cam_obj->frames[x].fb.buf - cam_obj->frames[x].fb_offset); + if (cam_obj->frames[x].dma) { + free(cam_obj->frames[x].dma); + } + } + free(cam_obj->frames); + } + + if (cam_obj->cam_intr_handle) { + esp_intr_free(cam_obj->cam_intr_handle); + } + + free(cam_obj); + cam_obj = NULL; + return ESP_OK; +} + +void cam_stop(void) +{ + ll_cam_vsync_intr_enable(cam_obj, false); + ll_cam_stop(cam_obj); +} + +void cam_start(void) +{ + ll_cam_vsync_intr_enable(cam_obj, true); +} + +camera_fb_t *cam_take(TickType_t timeout) +{ + camera_fb_t *dma_buffer = NULL; + TickType_t start = xTaskGetTickCount(); + xQueueReceive(cam_obj->frame_buffer_queue, (void *)&dma_buffer, timeout); + if (dma_buffer) { + if(cam_obj->jpeg_mode){ + // find the end marker for JPEG. Data after that can be discarded + int offset_e = cam_verify_jpeg_eoi(dma_buffer->buf, dma_buffer->len); + if (offset_e >= 0) { + // adjust buffer length + dma_buffer->len = offset_e + sizeof(JPEG_EOI_MARKER); + return dma_buffer; + } else { + ESP_LOGW(TAG, "NO-EOI"); + cam_give(dma_buffer); + return cam_take(timeout - (xTaskGetTickCount() - start));//recurse!!!! + } + } else if(cam_obj->psram_mode && cam_obj->in_bytes_per_pixel != cam_obj->fb_bytes_per_pixel){ + //currently this is used only for YUV to GRAYSCALE + dma_buffer->len = ll_cam_memcpy(cam_obj, dma_buffer->buf, dma_buffer->buf, dma_buffer->len); + } + return dma_buffer; + } else { + ESP_LOGI(TAG, "Failed to get the frame on time!"); + } + return NULL; +} + +void cam_give(camera_fb_t *dma_buffer) +{ + for (int x = 0; x < cam_obj->frame_cnt; x++) { + if (&cam_obj->frames[x].fb == dma_buffer) { + cam_obj->frames[x].en = 1; + break; + } + } +} diff --git a/esp32-cam-rtos/cam_hal.h b/esp32-cam-rtos/cam_hal.h new file mode 100644 index 0000000..a271865 --- /dev/null +++ b/esp32-cam-rtos/cam_hal.h @@ -0,0 +1,60 @@ +// Copyright 2010-2020 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. + +#pragma once + +#include "esp_camera.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Uninitialize the lcd_cam module + * + * @param handle Provide handle pointer to release resources + * + * @return + * - ESP_OK Success + * - ESP_FAIL Uninitialize fail + */ +esp_err_t cam_deinit(void); + +/** + * @brief Initialize the lcd_cam module + * + * @param config Configurations - see lcd_cam_config_t struct + * + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_ERR_NO_MEM No memory to initialize lcd_cam + * - ESP_FAIL Initialize fail + */ +esp_err_t cam_init(const camera_config_t *config); + +esp_err_t cam_config(const camera_config_t *config, framesize_t frame_size, uint8_t sensor_pid); + +void cam_stop(void); + +void cam_start(void); + +camera_fb_t *cam_take(TickType_t timeout); + +void cam_give(camera_fb_t *dma_buffer); + +#ifdef __cplusplus +} +#endif diff --git a/esp32-cam-rtos/camera.c b/esp32-cam-rtos/camera.c deleted file mode 100644 index f0f3a2e..0000000 --- a/esp32-cam-rtos/camera.c +++ /dev/null @@ -1,1592 +0,0 @@ -// 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 -#if CONFIG_NT99141_SUPPORT -#include "nt99141.h" -#endif -#if CONFIG_OV7670_SUPPORT -#include "ov7670.h" -#endif - -typedef enum { - CAMERA_NONE = 0, - CAMERA_UNKNOWN = 1, - CAMERA_OV7725 = 7725, - CAMERA_OV2640 = 2640, - CAMERA_OV3660 = 3660, - CAMERA_OV5640 = 5640, - CAMERA_OV7670 = 7670, - CAMERA_NT99141 = 9141, -} 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_gpio_init(const camera_config_t* config) -{ - // Configure input GPIOs - const 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, - .pin_bit_mask = 0LL - }; - for (int i = 0; i < sizeof(pins) / sizeof(gpio_num_t); ++i) { - if (rtc_gpio_is_valid_gpio(pins[i])) { - rtc_gpio_deinit(pins[i]); - } - conf.pin_bit_mask |= 1LL << pins[i]; - } - gpio_config(&conf); -} - -static void i2s_init() -{ - camera_config_t* config = &s_state->config; - - // Route input GPIOs to I2S peripheral using GPIO matrix - gpio_matrix_in(config->pin_d0, I2S0I_DATA_IN0_IDX, false); - 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) { - ESP_LOGD(TAG,"unexpected JPEG signature 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; - } - - if(config->pin_xclk >= 0) { - ESP_LOGD(TAG, "Enabling XCLK output"); - camera_enable_out_clock(config); - } - - if (config->pin_sscb_sda != -1) { - ESP_LOGD(TAG, "Initializing SSCB"); - SCCB_Init(config->pin_sscb_sda, config->pin_sscb_scl); - } - - if(config->pin_pwdn >= 0) { - ESP_LOGD(TAG, "Resetting camera by power down line"); - 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 -#if CONFIG_NT99141_SUPPORT - if (slv_addr == 0x2a) - { - ESP_LOGD(TAG, "Resetting NT99141"); - SCCB_Write16(0x2a, 0x3008, 0x01);//bank sensor - } -#endif - - s_state->sensor.slv_addr = slv_addr; - s_state->sensor.xclk_freq_hz = config->xclk_freq_hz; - -#if (CONFIG_OV3660_SUPPORT || CONFIG_OV5640_SUPPORT || CONFIG_NT99141_SUPPORT) - if(s_state->sensor.slv_addr == 0x3c){ - id->PID = SCCB_Read16(s_state->sensor.slv_addr, REG16_CHIDH); - id->VER = SCCB_Read16(s_state->sensor.slv_addr, REG16_CHIDL); - vTaskDelay(10 / portTICK_PERIOD_MS); - ESP_LOGD(TAG, "Camera PID=0x%02x VER=0x%02x", id->PID, id->VER); - } else if(s_state->sensor.slv_addr == 0x2a){ - id->PID = SCCB_Read16(s_state->sensor.slv_addr, 0x3000); - id->VER = SCCB_Read16(s_state->sensor.slv_addr, 0x3001); - vTaskDelay(10 / portTICK_PERIOD_MS); - ESP_LOGD(TAG, "Camera PID=0x%02x VER=0x%02x", id->PID, id->VER); - if(config->xclk_freq_hz > 10000000) - { - ESP_LOGE(TAG, "NT99141: only XCLK under 10MHz is supported, and XCLK is now set to 10M"); - s_state->sensor.xclk_freq_hz = 10000000; - } - } else { -#endif - id->PID = SCCB_Read(s_state->sensor.slv_addr, REG_PID); - 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 || CONFIG_NT99141_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 -#if CONFIG_OV7670_SUPPORT - case OV7670_PID: - *out_camera_model = CAMERA_OV7670; - ov7670_init(&s_state->sensor); - break; -#endif -#if CONFIG_NT99141_SUPPORT - case NT99141_PID: - *out_camera_model = CAMERA_NT99141; - NT99141_init(&s_state->sensor); - break; -#endif - default: - id->PID = 0; - *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 -#if CONFIG_OV7670_SUPPORT - case OV7670_PID: - if (frame_size > FRAMESIZE_VGA) { - frame_size = FRAMESIZE_VGA; - } - break; -#endif -#if CONFIG_NT99141_SUPPORT - case NT99141_PID: - if (frame_size > FRAMESIZE_HD) { - frame_size = FRAMESIZE_HD; - } - break; -#endif - default: - return ESP_ERR_CAMERA_NOT_SUPPORTED; - } - - 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 || s_state->sensor.id.PID == NT99141_PID) { - if (is_hs_mode()) { - s_state->sampling_mode = SM_0A00_0B00; - s_state->dma_filter = &dma_filter_yuyv_highspeed; - } 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) { - if(s_state->sensor.id.PID == OV7670_PID) { - s_state->sampling_mode = SM_0A0B_0B0C; - }else{ - s_state->sampling_mode = SM_0A00_0B00; - } - s_state->dma_filter = &dma_filter_yuyv_highspeed; - } else { - s_state->sampling_mode = SM_0A0B_0C0D; - s_state->dma_filter = &dma_filter_yuyv; - } - s_state->in_bytes_per_pixel = 2; // camera sends YU/YV - s_state->fb_bytes_per_pixel = 2; // frame buffer stores YU/YV/RGB565 - } else if (pix_format == PIXFORMAT_RGB888) { - s_state->fb_size = s_state->width * s_state->height * 3; - if (is_hs_mode()) { - if(s_state->sensor.id.PID == OV7670_PID) { - s_state->sampling_mode = SM_0A0B_0B0C; - }else{ - s_state->sampling_mode = SM_0A00_0B00; - } - s_state->dma_filter = &dma_filter_rgb888_highspeed; - } else { - s_state->sampling_mode = SM_0A0B_0C0D; - 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 && s_state->sensor.id.PID != NT99141_PID) { - ESP_LOGE(TAG, "JPEG format is only supported for ov2640, ov3660 and ov5640"); - err = ESP_ERR_NOT_SUPPORTED; - goto fail; - } - 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) { - if (err != ESP_ERR_INVALID_STATE) { - ESP_LOGE(TAG, "gpio_install_isr_service failed (%x)", err); - goto fail; - } - else { - ESP_LOGW(TAG, "gpio_install_isr_service already installed"); - } - } - err = gpio_isr_handler_add(s_state->config.pin_vsync, &vsync_isr, NULL); - if (err != ESP_OK) { - 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; - i2s_gpio_init(config); - esp_err_t err = camera_probe(config, &camera_model); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Camera probe failed with error 0x%x", err); - goto fail; - } - if (camera_model == CAMERA_OV7725) { - ESP_LOGI(TAG, "Detected OV7725 camera"); - if(config->pixel_format == PIXFORMAT_JPEG) { - ESP_LOGE(TAG, "Camera does not support JPEG"); - err = ESP_ERR_CAMERA_NOT_SUPPORTED; - goto fail; - } - } else if (camera_model == CAMERA_OV2640) { - ESP_LOGI(TAG, "Detected OV2640 camera"); - } else if (camera_model == CAMERA_OV3660) { - ESP_LOGI(TAG, "Detected OV3660 camera"); - } else if (camera_model == CAMERA_OV5640) { - ESP_LOGI(TAG, "Detected OV5640 camera"); - } else if (camera_model == CAMERA_OV7670) { - ESP_LOGI(TAG, "Detected OV7670 camera"); - } else if (camera_model == CAMERA_NT99141) { - ESP_LOGI(TAG, "Detected NT99141 camera"); - } else { - ESP_LOGI(TAG, "Camera not supported"); - err = ESP_ERR_CAMERA_NOT_SUPPORTED; - 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(); - - if(s_state->config.pin_xclk >= 0) { - camera_disable_out_clock(); - } - free(s_state); - s_state = NULL; - 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) -{ -//#if ESP_IDF_VERSION_MAJOR > 3 -// 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) -{ -//#if ESP_IDF_VERSION_MAJOR > 3 -// 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-rtos/camera_common.h b/esp32-cam-rtos/camera_common.h index 8a2db5c..bdc24d2 100644 --- a/esp32-cam-rtos/camera_common.h +++ b/esp32-cam-rtos/camera_common.h @@ -15,6 +15,10 @@ #if ESP_IDF_VERSION_MAJOR >= 4 // IDF 4+ #if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 #include "esp32/rom/lldesc.h" +#elif CONFIG_IDF_TARGET_ESP32S2 // ESP32-S2 +#include "esp32s2/rom/lldesc.h" +#elif CONFIG_IDF_TARGET_ESP32S3 // ESP32-S3 +#include "esp32s3/rom/lldesc.h" #else #error Target CONFIG_IDF_TARGET is not supported #endif @@ -22,28 +26,3 @@ #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-rtos/component.mk b/esp32-cam-rtos/component.mk new file mode 100644 index 0000000..8db15eb --- /dev/null +++ b/esp32-cam-rtos/component.mk @@ -0,0 +1,4 @@ +COMPONENT_ADD_INCLUDEDIRS := driver/include conversions/include +COMPONENT_PRIV_INCLUDEDIRS := driver/private_include conversions/private_include sensors/private_include target/private_include +COMPONENT_SRCDIRS := driver conversions sensors target target/esp32 +CXXFLAGS += -fno-rtti diff --git a/esp32-cam-rtos/esp32-cam-rtos.ino b/esp32-cam-rtos/esp32-cam-rtos.ino index 4479e21..57f03a5 100644 --- a/esp32-cam-rtos/esp32-cam-rtos.ino +++ b/esp32-cam-rtos/esp32-cam-rtos.ino @@ -463,8 +463,9 @@ void setup() // .frame_size = FRAMESIZE_QVGA, // .frame_size = FRAMESIZE_UXGA, // .frame_size = FRAMESIZE_SVGA, + .frame_size = FRAMESIZE_XGA, // .frame_size = FRAMESIZE_VGA, - .frame_size = FRAMESIZE_UXGA, + // .frame_size = FRAMESIZE_UXGA, .jpeg_quality = 16, .fb_count = 2 }; @@ -482,7 +483,7 @@ void setup() sensor_t* s = esp_camera_sensor_get(); s->set_vflip(s, true); - + // Configure and connect to WiFi IPAddress ip; @@ -506,7 +507,7 @@ void setup() xTaskCreatePinnedToCore( mjpegCB, "mjpeg", - 2*1024, + 2 * 1024, NULL, 2, &tMjpeg, diff --git a/esp32-cam-rtos/esp32-camera-master.zip b/esp32-cam-rtos/esp32-camera-master.zip new file mode 100644 index 0000000000000000000000000000000000000000..ce89eab405a80ff153968ab0d81cd844c25eb26c GIT binary patch literal 122571 zcmb4q1CS`qvgX*fZQEyT+qP}nwmoyko;hRNwr!hx-rIfm?cI$(Vz;BaBBLYfi>k`( zPQ;h;QotZk0RM3vAj~WNr|`c96aYv76DNCSCR!r{8xuzZS{nl=XA?(y6=hHWKyJfH zeffWzYU3r70sVLCe>`~qFqO{C!r8*i*3Qx7Um#R;!b*Bd&VOG){TCj@e=g+;E4I=L z5CDJ@6aaweAMk`^46IC~Eu5U4=$zf1|AkqtEMphQkI?m^X3cXZa*jS+gt#8BUhLmx zYmb?N<~DY1x$d_UQYhfIl>R8}Zg!QXt0aUthGDP2>O-Tq5u0n%fj3hYRlYZQbpxJpw(Gnx~L>qcmHb)hfUU z(G_Qv7~)BSWUsW}N{77C#CpfHhaE3kA(^_>xht!ZZYJ3BTVVGcj!Aoe>@e$4?+H&% zy~OCq!=sqICK)u7>gEG;u%`q;GZtF4!bu#P&~6oruYk;a{XJ*_9xuE5y1nu4!HWjB zYc}Q_d(n?;sp)qGEBJ0Aq%b^C?Srs2wG13~3L*!YwHgDIGF5HS3H`t8aL#q5Z1%4X z)4>4%u>V1aQbu;RrWR)ZQeJb4f?N;-!pP1GB^}S%MDSGoB_bvjBOFlKUHMsllBhW( z%2apPRj4uYq_n-ydd<&|j-Olc6lA49ql2s@45JzLyK0)c{B+YC#Nql#uUBrC6K;rH z_YLTrtyned38uM$4oW9V3`Fh?O|=S%anOZyZ%a%u3H3UkB_~WRGZ0Y;>aE}sSbG3% z?H)2SV4#prxy?KZ27X&w)A?#iNk2Y_W8#A&_qPO7-lS}jIAU-Y#butnafks$rFlI= zO$;(k4Aga+S*E0t{;4|I@zWW+bcFu>Y2FpE5@=|mw9Erb1XAB)G|56aXS(roS_TE; zwHbYYKLOQ?s2K(tYR4TpdE`gh25grj{Xkc=a_>oDv55B>`L{68~O5P$ybAM^f z7uOd=*{^__uPNKdjhN$gGMcp_{N}zEyqsRa1XNyv9i8fI(nua-uYiXl)Ttl>`zJV1 zlW39(Tt<3{Kd>^2DIz~U+q@zfuv$8918R>(~Rsxl$m=q zh74o~_M!o5Kxd9VP!4D|sAzN*$m|5+HU9j$9*@x-$@zZ4yY*KY;@06~cxv`U|Cw^O z&&0dvvvpIyH>1ltoJ0S2i)d=v54C{@0MMoRXN!=Q5E7A968V=!Y-wsaZ49IOzSOQd zmMU3*mFBQmV4pwi2Qo`mDUwdZ0z=~DpSfTak?=Lhw_@wWtaF?&)G>ho{Y)9p)=b zt@iojOM9Np$>>il*7kUpX8`m~?TxQy)X=$PXP1T!?M)hcWwX1R6FF~HEwqE1kC&U% z(;ls_&a9H!#M9T-eHi>JEaa;tU5#=z_b-1(+wXoW<_Gdj$UjLk#G0hsjJ*=zCR*gi zY7z6!sI^GV^@h$B2&zxZ%0>}-=(;GpS{Cf;U6^-XggIsQQYsNs0#zzwg55xG%h4M- zud?Y_yyouH$wrm!d+PK?awya?es!K5*uiUV<52RDXy{U z_67Cs6&uxB0A4h1k*Er|zugGIh$SIqM@~9Xz9VS#MX0&kR8Dy|S7MH)sa3hJ1P(=A^n(?HsLAJ(yihW*Tv!M9^6q@sFwy91shHZ8%S z0(NO#;ehfCVUh#39g!?9*J(jgLYyt(iD}xYR9A%@JzB}2{RC?@mCoC(tD~9?P+F||Fqbb&Th-)+c%1?7}mKM*_ zH4?;BoW^&8P5?0TuO$bco`aTs?>)VehdZ&Yq@o?NKYcLrV|iYHq`2IZnoqZfAf_$x zzwT!=1gbmxtk={4jOP*G8kIr5=Nl*innMAuOXf{B9t`of)dzFu=*QNA!do_0<%Ch! z8Wj}KqTdIC%f~a>7?`d@$j9x^M&D10>Q<|JASO{VdQKaIQX4-9uiFnoGDl$Njs@5} z=!E?|>mq3pkoP9W<*F@GuCj-Z_6^4yrrM0$3~8&|Ide=40OYXp#{$T(SPCLjGeilp z@Xmz*MPp<0214AP@?i5YP(Mlz5+}TnTAgG`MyZpwa{=$je*Z*?1D0J%dZZt$f9rf= z$n3fi@2tgAZB`ulRPJg*p)^P560NMVgo_8?B4^qJD+E0#PO5H)kzD~0Zb>CYjUcK_ zm@6ZfEO1FKPg>a_O8&s|TRs@!L{MVepvRge*DfnjfJb4wgB-Fy$`Fq?US(^7^7WH^ zBa(^)RM_cVmhrb?w*}zlaR=EJm=noN^5jJ|C+@#P)fLRU4Bx z|J_$&O!(2Uzov*%PrU>DEyV>$bPk5j&QB?zsoFq<>IlwWwtR#D$Lgl6E+|=%@D1t8 zr^;(eonUn&O3#FmAuM2O(}Zom>FBI{WHFL@JHId*7wlfnQa+*|e5N79)32aiJ24jx zm-r{;8Cft-=8{65?&z_F_%cjh7(c;aMwBRU$r|)Im1t(^?UE%V?$w3Ib+mUzF?Z8H zJnm|))DV2!8f6e!UQ|RJPV(fVn8f+~590psJ{u|E=VQm1V0I}!u)^At{aW!#`;9+$ zuK^knPoqOiBj;(4sE1P}!T#$p#r%{^!^-vH`>8*snnhaZ84l*O^R#PGmJ`db3{zWD(vMB}b9 zB7*ZMJXgi*lV!um^zuS=jCb4BZL#2T>hkUAo9> z%B*Q<@#oFXQQ%tY<-Vd{BdzN%Q0gH9e)}f~tIAEpq6Y&zgk4n9md%jZRb=lN#??SH z+tQ`7zVkf`!o@=wadDZRe-RU z2e)PJD>75ZYXh+$wq^qwRlp%E3ix5KCz9JRrQ4Gu&`8iEWDfibb%{T0~zu@CDwi*b9fyfJPO{Ln$Ev5^9dHB#RF_j$r!K38wrtEgieaEM8z-B@}{I z8Iy-BGl_W`uH61kLRRW&(R_(CQ1!9L?sImjacS!7{^|)P@y3C`%V_=WSG=Ry$KhIv zKyQXLXFbguH8QHE93*ct>Qg%9>7qs>5rstdgQ+1;86wlIz$wH~+A1ru({{hvm1G|n z6Ba3Mi_%;2Hoe3e)F1?#5AtHNz=-jMae;(#o0-ciFZhE*{E^2mTz*I$^6b9EHW}1g zH4TDURrw?ctotVqw=NaubV`JT+*fw_12>m{eD`PZujAn#UM*vA>Aa%P%YiS zVuxmhzEC6BDkbff*1$G7!qNa}*FS;Waf~rAIH4H&p^an03sk2W>ENq(9N()9a$8#2 ztP1B;;VtuZxsbhcyVOpgPeYY zJadtU3vKC=B=CQxOFbk4k~S(NP-CmG&BlaF7-PwdGFHoRT!4_nyrrgMdmwjpS?l@P z(eb&;3S+ZN@eGO-jXtE18le+8b~tbvN3GLSV(N2obVy6%xbff})6Z@0&mR01GCeXy zEKLCih|=3t_ns40YA5}p_OD=<(qi^XVHRAau5eQk6{lk4;j$}G^gs--jOp&M89R0X zDb*QUgtbo3C~o0-h02JP;wmNya{bw{1}mzYylb5|yrShig7 zAyb_lDN%eE_MccRfmOk_75V|Ys0|kalGc;ntVc_C+4W$jlL1;5^G^09xh->U=UdS5 z1N;JY;20qbE3$yu>lQtXqEhtzJH@A@G6PY^40vC36rX@xh6?%|qB8kn4kjX;=pAZ6 zc_zH{wntsIYA`}!Gw{gHjyrJz+ z1?~>mJ$3!`_GR#t9S4Uq37o)^!TyK}Zp&cHs6Ik22?J60busvg!lI1o*%;uMX>e8b-IFf(ngH79R;upzI6;jpTWG{(x#EDf;12D?`(Y_LOrLU zms@yxZMa>#Xz3N4$YJCGEs;1mY{)UK>D{mtaR-tY!y2zq_72Y-$;bPGLvgFP zx?M_SK`?4$6L2$Ni^&s2wDSn53z|%;)!gMtr|<5W2NW<0x+_{UA|I?aX2DovM$VoV zvukpeam7aB{L_P0Kv?sG6~NL*aV8~AzEwyfESFSB1M?}3;YFsy%xl!|)SYB=Zk{E% zG+T2Er9ZrsG1t~aOQ=AwG8ajiBWAT=laYr5OeUljJ4FSr5Vg>Dcf30$$~6l}8*hy@ z_Q@MH9L*-G!{J}6rlzho(*-8hLvL%hmvF?llMij72@vjiTT6dfd%b5@JkGmK+PCwC zPDk-+g|c8Fn7;(pFf5488(c1$!1bnCS?UM`1wz6d^bKZ5i7I@2nWkrk_-f9ySGh$9 zf?QB>^WGrqNk>;B{Sx-t&7WE%@OncB>TgxAeII_~Pq9@VxF=YJA+ zN0glDsS02ZgvJI(U-oLjM-EFNJL9;?pG;_U)DmTGiXZ9Fs<~D6I1aTH4<>^{mmT29 z(kcp*YfzoO?fssnCocoRgL`w(b=2Qwg)>u37}j(IXII6EF>kU@5B~b0@+s&Ulq>e9 zs%QRySG=OiEHhLtp$CDA?l$0c`snC|g6NSL0z|v}^Oa}Ted~=aIiFEfxtN2<<;zrP zfr%4yErdT+2;ya?%kdqr^~2KV3rV?@RIh(FZy)bFhZ!X)W>I#>zF#WnGvGJI>yLO3 z@EuuWbxLL#LSVKz>>vzI^+qg$JwIeVjq`}z^P7dl{>Am9_5=D4;J>HqXd`gdfT#cf zExZ5#xc^YVQ4|pnmJy+|G5+s_UF*$lLp1R_$4^9RJ3;~;K7#9VwK79YDS5IUF0}B{ zJ&6Z_B$I>&f!{vdaQJ)Yr1E@tNXV6J%-Z$s-VkX^N9ROMWos6BuI&t$MPws;q8H_N zfDe6;;@KFo1Dy_!gYZg@d*5cX^$cwa7&A9Yz+=~JY^Oe@sV+uHq_E1DQilc9LUSTM zxZ&lLPZb`gqPnz>e{QSq3J>pQ<^E34fzkJi_>Zser*|CX?#07#Lfy%+a-@8F0eY~@ zTmwy(J0q?c<)!#*fSa8b6H00NXh=BE3}mGtW8(;O0OvLkOzSFZ!&Z5?sbaJYUnH0kf>fP)nG|3(Ji}u}n!-)20(F!NJB(Oma>O#(kE<%Q_@Wi_#-3hUxhKw`b z@O)g|A7>GqdEcw+4F9@aAVYAb$Lw@>bZG8O#v`hm$|t1JJR|Hz;hWN(c9L^qN^ACL z0`ON-+eruGovehvwWyoamsk6b*yq!{9yDK^1Uwviq;26~1^xA!URRpu$!7sO>)`^F zs9x3g+eh8HiYS$C$X_u=BQW`!5tyPbjJ=w-DBOrLk*21_wEdA;8w(o3*3(zAd z8=$~6S7V9M$X1EbO{<3m2=*W?DP8kcYjCH9-?pa0DqTZ3gTDgBowL~Y9~(fybA=UY zRio+>I86tPm@|hu_)829-+akT61w;;Gny|lyKh#FVij$Gc_4X^6&OHo4E8r{QQTv1 zuUVn@n4%9mp(+wun4(YmgElFr>QdyDFimiZp{x>RGpxrmVCv}cD5eq^j7L^o#&Ghb zJ~nJGhX&(FHCQBgN-J&;>`RN8hGy!6H|oyRgoB}5XV=w3_bQrjfG1Vvem1%`hD!!} z6(uHC5gG_->B*eOopA{I&kIk)nT<90fn~G7szU)$(@ z5qO#kR4CnhLN44(8>+kkvW!R#;MAKnAbIM?W5Sh20%dq&)BSIomw){t{`{aALF|JE zS3#Ps!60%qH1{@?3oEqdDgZ}`8O$*17qGBmb?UDp-l~VC?`xrdrrY^c1njEeb~VJX zfWVAqJPCWZ7EjIyV1!9eYXVn6kaex4lo4NL4W6DXDO*CqhAHvl13|7LPc%Ng5NS~p z+l8s@Z)$4vGRB|v(V&726M{+g?LE(vjQzz#a~8H=Od{SP&J@X>sY;~-c)O%3Qo2X@ zQhHSvillFHN7LNb`Y6nY(D7E%5os;(XFJjs5<0b;WVHcd*oa*7K6|!(LFv5kSJDCZ z@g=w&Q~d}VQ{_>NAJt6ZD7+pU)+^$2J#!&L6S)gUcYzExTN5&C@rHl5F$SypZRpuX zGe6O}{;##!tR=I#O^?FvmE=Idolw1%{?*@;Dh9t#5b6^)E@=G9eSBP&f3b7eV6%78 zfw0=zdUaiV?;4jU-~KHOs_GB5TjHQxqu2l~BE zDT#uIOruz_I2%$`2<5aaxFmRl)rqyXyFiXfC{9YJxHPH>WaaeXkWgO>o3RN?+zj!y=QE~G(+_yS-u#P8;WvG z+`6BrY=ufg;gC>CnlM^Uu9l|;!*)d6cAX|fHA42M&IzcRv%akGxKVAeSp$O1EdUsZ=>H`Sb4TA*s2 zZ0U~m!TZS4&=~2oOc_bhGziB?LNhM$bSXhdTn8QH;AYByft3ieAemW6$fFPnzYJCd zZYg?J41}f8FF}>`spNKzSy4BC92;?ZvbR>kO0qn8E!a90H?|F-ev37#QFCmKa%cci zr+~eOuM9ujZqUG|wp*`2k((fRmM;I!2<#2R-xOkquwIx44%7%(J(n^pcjgKrrAhIT zBfqLO2UPZ(S-FGY!qXO(0RUA=IDZ#F5~j70z}~-Cby8xO@CvwPlI42b=^>VloS%^G z`Zdd>Nc{0wk+B6r-djZly+Ca1cP|T_236s3yW+>ZE;BvEWx_F(4}t(DqjQm0Oj?D1 z%1eBJC))OUP$yEXs0=a>X0}sU!CI9Vd;-!cimmh(Y{qD8Me>;-_FU)Uc@RAfD(IE5 z#Q1msRAXeyX*kINf@`6chB2O)rQj=tPjI0wI2Mr= zZ6TzF8JtY{bf#d)ZFU();wk|@+mI4O$YkN!udk0-gr6-4{qmlOW}Z7$6Bh-^?o%?6 z=u|7jWK@c>uU19X2WQrAr1VgYS5iv_+xt4f_ZA7rO)E+A0GI8(Z5Zo{?YvcFDOsi_ zB7VqDcP`>dee1D0!8BYN4ss}_ig8*6$L!k%`SEg9HNYe)1BgUwu1Uv4(paXW#z=80 z?Fm%faZXg4_y(LNmUQrfYpotQdHrc#qA`*(BnQ(9*si< z_1Q{_A~uh}&}@sQSl`Mu?2j*hkw|S-Gq(O%5SCkcUBNW1fd^NXQjg=QY<)NMPavJj zbp=9JJaO`g6EndKSMZd43sxpJBlnxboe8$icBkAc{Vt*LQGGlq|4L6FK7&*rmX4UH zEsmG71l|$Ox7S)Bdzzb$W;V8zRHv>W&|qg!EYM|PA+$92}u6>sRnB(M>0TN`Hvs9i%C4@eS47d@?byZhX?5hVzA}eqdVrnp!X}7NHAvx$ItXkEkRZcQY=X_1cAnNI17>T`-RD9%DD{f_b1EI0#P8Dt71|aiA(3U z*jkDle8Rr`TO}O|QzbHNg+gaIJT9$QKdoBuupq8jFjU>>Rb$^`ZZ;7|4{=Oz8|L18 z%p|ydE?(vxAm9@xp2NsOgptcQ7^}Ps3=IDavrJ~n(|q#0?)xJNtpj|f5R(I2Ljm5j zd5NACCs1&|oW2+f%Qpij29t^%D9NZqjnGxa~BT_#hx z?UsIA{v`)Si_xP$^D~wF>|_;%s{PD{Bv|*Q&m234q+^5%t4f?NMMn!wER!2t$qMO< z>A|QMq~WOG63SJJ3A!~>b5m`=U4l=Mjq&f^6=S<;Qglnnu`*_2Wz02;2i`uzFB04l z?uQQ&B%qujgL&6Nmom~^YddU!ip$-PhQd&+3vs?Ft$v&J-+xzOA^)}ksD=168H|ZJf_ezQd5^iHk>Jg3^Ut*jii0)4V>D&dkX}A zbtYdeoBZBk0;|sYBUwj%n`z5|$%%L~0|8!5O#cnNS#*UY@`j^kgKJC@& zrXF_tnQq?w>|H4#+~V=W{QBydQ%OTm(NlrZFXvWX=i;z#9el1up$qS9yt0BX*vd@L z)1mj-?v`^fIONJ&r_q;hl85&S_OEHo$^-qa!U*525+u~7vcwc6jEYf~!hJgjqHl>b z*hQ+=(4IPZom`a9qD)w7;ye@`LiTS4*K5ZC-JlFQw=@#k4#f*Yt(Sa@<<~VJzf#t8UBf@!P zf5X%`Ct_Z))9OEgPDzk>#K#tFNB0-&zI-%Ze4AM5-+IZ!42NyFg-w}0b?&^|FWBlh zSA0FJh>eJ;V5y?K#x6jVlXD}Q`S_p9y!deqU%i~rR{dOexSdq5yS)<1c7d#dOu;SD zHs(W~=LU;r)DV5nr70bpV3c9g3V8NtfSz70#lk;kslNy6)5$o`IhP0Fd$u228{f(R z(3Uorksnulu%3k=X`s3$=~+XvcP=TS|FNo#rf68`#ligCpO8qj$j@CF{$S6cE^=DK z_BLm^pP|R_vi|vG8<78DrAF_cN=P%8`=3g94zq5aMo<3Ac@iI{dT|ISP_bj=pk|bD!^` zD-)+osTakZ6l>$^WLWl6ZGHnZ@?KB$UGeA0bXm33B`&P^397h-Za|;uFk184h%^@{#~S$ zOayGw++gK$VC7vo?QSC24lGEwu#`U8bm+v{)iRg~fbSwGx=MR`&Pw1sK2jHM0yBD9 z3v7?)GRBW-0ikz~a1q#bNX!q4AXbN~jipe{!X@8qjzLFL(|?X4{T0Bq9IK?45POn))hhXlt-B&VJzjd;rRvJXU?%V4qX@bu@LTy{l8nz?8^A z+|!9xq#^n&zI*%zer^f4o#irrmAKn6;bTVShW0Z5d_@V%HC&8f413=EJgmE znDnx|{t}6pmzN3Bccr}`qz(b#_uh>O+bLa-0(POiB^!q0*Xt1Mpwdni+UA?A=I8M_juNS3nEYGwZ^7^ik+ zXWsz3V8OdL+PuJ*{H7}@1mC^eIZ*oZ_KA#jnOnBU5d@Ly5Hw4xRk#A`4uxe(5A{+f zjiy&tW7+G`6(w1(@X?Z~4CVb(U<2<7&zS?r%n*!Dqg_;b_K#ch&rgxtx8f(M?+4aT zR+xRdBa9)q3~@y=s#DBY*ReqfgZ%Qx;ygjM{_?hNNxFP~8133&X!E-mNm8-JW%E1m z!PC>#l11f@z~wbNJX~CCUeBv5{ma|)(8EE|K+y5G?Ydm)^@}tHdyp@9iTcO8rGIEY zhWDvU{@NC27rp@fH9MZAQ}T3T8V(i1DRh`+~Ws=ycfVR$=#vT&wm;LT65(a_fzv63;k+ycE96 z#TTc99ib%kNO%*zEK!h!3{Hz!Qh(UUgB~Dl4c?=6pV%sZ`QU}^)vD-}R>pYH&~LYp zWKPZD$cCs8X{b+$JZVtB55Lzt|453!S9Prf zm2i5J-Ii|0S!ZHBC>qyZd=WM}uxG5a9#_|jq~THh3dw7cfkI1x!U<&- zL)2eZB-URh9Kk;nQH|_u?Coq#Y@O+Bto~I;b$Wh*y?t?df{t)=c8GX+bb5w#ad5eV zfl{q&P!Xpbr;(zapP7`Jrj(IT5ucs`c8p?vc!)GG)SkBg0u;zK;DDh3P>digzgvY7 zOrw^aqMoMjN1>OXk(8qqKo@P`;~2$U(loZx9~O)W$Vk=H*0&x!!a>|SJ~+jz%D1V^ z&q__J)lybW90LXTuO0p0G|_+Fk*%wVqmzZ5t<(Qa8U3FW&&SJcO8_tc0MfsNFou6T znE!_P%SP3+v^Uc;HZig@HlZ{6*Flx2eb`}fB7Eoc7)maM%b@=a-Ev})BA`hKj|hO~ zD=I2d)>wC4^-i8r?SFsXup7~T_!p~{L-g%uynRkBlx~GiP;5)qYf^J?bADAv;M*C* z4YsMDfjWcaW_Tu!qnkDOX@Mg~tG=@S)e(-J2 zn;m_(g8Dd|xBF$k`6s|=&>sIgzfqC}R3yoZ%b9ta8YMNMAbD^TRcf!$YLCi<8m{t` zJB`p2gfT5ZoT3sE=@DWWJwRUitOU46vpJ2(`D=0D@PWj&dzgCU!1nNNxm|YynXLU- z{1%DYo}t4j^o#mrK%Yx!CA-J-&*`}c`ZmCIB$E4sCUxP9+JIFx_l-;~xD&X{%#64c zU(-!!T9WCHj0?MVn!>E==;(r$CNl z-Z?pnS+v+?0Xrd(Ia6EfW14kk((Zju^2X5GV7nvK^V*DbDZ^ASad>e z{2E=VD@jRhBb}I6zqYt0(9bHqOPt#wV+J=G&Bu+3Xbf|?^nk9HN2O%3Ueq!d>g+|_ z-5w@A*N_(RXO@l^9)20bwO8o(GlU9waS>OukQ?G_<;f|OIIo{TPYm~y#(6u3Y^&Oz zseT4wMW&dP3_gX5edRtTSRKpG9MwF(krrSjh1cy-2rpHInb3A23x>#eX-4mCP*Cqg zy80tfcvKLc@I$nHiu;EQ0-$VQIZNz(7?*$x?d!+ubYnm@mVj`D=|oD=UO*+4KIJ^o z43HE3=36(-gL|UQZP=Ow;AtYVF3+p_a7QXZ7vE4yhkFoq+m9eAj%h_hvo;+F)VkHs zjk8@xxRJz;@rn##gyA!-LHFLx_NXrFS8-2P2BobrC7_=apsO8vL5?JI;zz-T?*oB*`*cf zJ-45;5G}kG)kfWGGCydtx2nKFNp##sn{om*K_dDMFHe>@@j|ydPp%5=kNLevn>@Pt$;&HJT_U zb5pIw)NI#?ViuNyVNFh+fu|KnZOzWZ_mugFt}Jnqtj)r3E0=(|In#IL%N$Va>`};Z zhuc0h?38DVy@wmq8d6I7xrJSX~)Le6G10tbRoN`F4TRtJbJ#yvW-sT=)MaT za2e=AzojZD9D}Fs|4^(*t%8EMs$j>tf-2@Zo~%Ez4*qSvZ$j5JdBescySLTh-L4Ts zq@23sfn+iDBu|^)5b`rHuYcw08f|zdUXs?3P?~ik+%u7Ub>6}jeIr&BtpQg`C@uO> zjMzc2J(IEA1RYBn>6PyO#F_shWXLzrp6<0Y?~lJMs@GBYL$dF(zY;m{8vfI}X(t@~ z*3HYRKZT@jk9QuWh&8*DGXGk9rfr$(R>uwyRa6|#-c}W6#+DsZtx`UZG&bh-Yy2Dd zpS=^s|ASkxur;!FF*f-pFI5v()t&|f0D%6t5rO0X1MdIuR_6cmRyIlscI*5wzB9FT zr_dsl7M0Z%%QlK&YpF3I6$MrwD7Xi^Z&iv3o(=Th^pKA_0@S+X6 zl)A}a({eN0xwAW_r>2MGK%WH{d~9WqlPDXS z=di&-M>TO-rRZv=uY?eF7W-+p)+w0ipe+X3V#yfdrg@CQNZogHq?Mtzt}S^9Z&gT# zylk=Vk~3Cg$=s^Q98G!bIvN!{?o%ROc!9!RPyghFK7qY9enM>&{s)iuQiMb9wvrAh zrvVDtsad7)4Ie0<@AMN2Q-)LGWaLY1j2Qz!$#rE}AiK27xuBq%{){V~{moA=ByhXa zOqDytOtzyQoP%~^4b+6r?Q$u0>w5J_b}1TDv?O~<8?qs_I@y4V`O*%xtwTo0t^LEN zf8FsVXC4@ z(HqH|;QE>0O^r!dItY}SB=^b#zqQmysgEoE#ICzBJ zCNMvXs&s7gH|A7XGwJL8A!C5wNe7~TqWtWs+QehR3FL?i`ldl1nTGF_r`_#In-Zu( zx~8?v6ZjjmEw6pW$Wp1oIJE>TXbwg5VJ!$!BLEyGL`(%rdzW~mNTk~QLz)`<z!CrV2Km49wc~E6m`(<6*Yp$?|6F$?mqZe1&fSsX!?yvWX}cPoo#2307%~lObylCmzhtTTGV%m7XWfQr18) z6`af?RDc0Tk4h7r%rj}h*i=<6tR^tR4I{wJ$O6GUS~BFq4Ex>^nqTg=N)NSADpEHA z<&w4){DihPV}P=J$h>yd6VHnelce2~#DRF*SqEd~tieAqW~|55%a<3kt)h@Yb7VnJ zaWx)y-RU)0AVAg-2{^%aOoD`%9@-Jr%~}FOmTiLZY})uJG6k|R$eeORMqKnf)iEd; z1src6FixmGM2OepV{N&11Z47sY8F0qOER96@sI=b^TbltL;1F2Waq0BW-v}2>$8Bn zy;7}slSaF6@N%y6t-hRkyStzEO&v|4s`QPux*fcD{RZy$j@?pdMAPRtzfvw%vgTkx zrmJR9k;`x)w~ByYwvti5vUHCs(-GY26YG;;~a?{F1HPe#N2P&Dq=b`pcbnPQ&+VK zc2qGUt-_nV>;T50yw&V*<8V**O4Gn`LYEDfPL+6Aro#KXL7h21?a`VJf?*sU-5Cdn zC(rG|x1YWgG#$bVB*a0&f+!D9O#K$dNuA6dq$w*z*#4F-f8|v2Xg1JFScE8|7ycf* z1=30(Rd%>_${d|{vS{jT5f+b6*8c^|RUbh%PKATci~06lg0 zLhq0#YkU%X^uuiMOP9=J$5Q;&1hD;A2J^2UID49%SmsAf9Yty<=?1aH2B5#)_O^Z) z0(ctpo>*XJMR5mVxF(Uw%x@og zf4?fAn9P;nk(IC7W_@kgnf8JM1STi8bZkj!W&xOjJ@f+r`%eypU0OO};(=ztUeqrP z2%!!7W;VQW8Tfg9Txy|JrDc&wFPL|6wZGgFdUfOsEA*OPl;BsxJkvp;U2ZVsk`nU% zlHENq!H662>jLqAg_w;)yh?I0)&%2*4k9*XImX3 z<20lL)C72?)2`tQVK&`ax6YaD2gx;4Xn{aRt6qiv5Q_qh0!cSKbIp98kin^@TVt9; zyTFWE`JT)gzSt$7aqLQ9KX;B(KN%o09`o$TBs|=NXF_-zmcw-RZG=#x8CP#tw>!Yp zdd(ALgDsfmGMsU*W&h3zAn>n$pNXdJcL#QkZ4ZHvr3+LUvuDZzTsd=X)F8*SaPJx` zG|UKdk@5pQ&kdn@`0bF6CHQb@dqgjAB_%K9IP!@Re8*e|iLq}w)WE7$yU%TU zhsLBlke9@v)5vKKmD{iNe3{fEiGNhp_3?Zr(U;f9A5@S>p#!v#vxLRc-VQ=q)sJx3 z3q<1hQWpQW4`AgRSDcUR>`LtNjjK)DY}5kF)cVXOSIl9QO(Rfc=j-+yRT1)7{;Wg; z>3u>hF3>$E)Vu;7+V4pz(%;Uu7sBu5;A)1Kk^wS7jYYpDT#;={w`J5nw&X_o&$&_kbP=&a7Ed4hoQRyF8Ctx$lkxY$bV<-v@4S+zYlZ^tZPV z_$Z@kJCN~lCZk>qqxs}~5R?=Rde>V*UdP*r)%L=5au?>qaCKV;*{+Hhg}TG4H+ydA z@lbrb@z&$HbvTJZ(vsoka9^qrujG)8HVuXgP^S;SN~)EE$rD2m-R{qqvXpKf5366k zuO{5cYu9QGw@%L{;5~NovJQ#rk0mf%IUca%!xTIgasaUQ0uFvo$>%(??kS11ZPF;C zvo!EpV5o!ek z3q77{5C>zX|7cUh$L8;{>j(f5yVn{DOX5G@a~59SsNoJl<;$fGZvs z!@CQlvtSRHZ}%85-D__%-~#0%H6LJ~3O(P4@v?t4-?9B(S(S`T0wN(F$@|5kVCwC9 zdIB*toXH3U4-{B@3oyCw=Ys-j4CE^SWG>=MdkEbs^hSe((t3d{w1KfPY#%&xJ&FMf z>lQ5ozz`@qRnOQ^=gPw{JX;yTb+cfHU;`mdAeJu@)d~V5e}jQf3xpNt28FNBuR4UE z7)^{cP81PJQWOP$P`}qcO86Ok!nfn6nE~yV3u=60kz8`JTxW0PY;CW#S4;W56}C^# z$BRDV$ZBX`wp+&k8^3Q>z^y@3;vH5x&?F!gxW`};Py~bHHm%Wa8_w(lL4;S1gg3z( zvvEob*`hBI&6neKU=V+HqFMwN*t=g9I|}amR4FJ5Hk`4yP|8IpSZJ3Jjkb7S-ywL2 z@%t9iHrPIVV`Q0f88kSc3%1TSbg0b&B6?jIu3q~*5CVC}=yQ*lmoY?JeKjNm7L9FH ze05kg%bf(QZfRIGjDk2khyX5-17e@xui&$fw)YOPBG$^=|AYuf(44y(D4wwDy28=*hiSFeE`hd73u8aHM1-Y?S2p*AL zN+3Bz_sCu)kX)i$lmK85I0O#_wx)&TwCmzOOJPv0Ri{><(6eB8f63+SXMqR)YeqC?n zJ;kM@qnxvlEA+5OT7mN3^z-qzG+ZGI^of*b=u2UQvYHcmDb5O`%rGLAS1?X2LXA@} zM}tyUiQeCX+@sKh9lsS0hzuhq;h(FBR6xm~uvIXw5L87iqF9%|EmRktlY~=78LGe# zDX4;y9t`>iIk0%AI;Bo!p6iUS?yI+0k2&tlv zRMo>TJ9TT*G{)aC=x4<9H(eXCW3<9}3X8|{@rIfi;Dy0=eP0yH&o#-|#Zwn<&9#A* z(e;)b8%Dqh%IPLvtr!#t^D&ba0l9+3@#0ZnT8C*!DMA0R2$fpq7{6vH&ph=vm%#p# z?vTZkdz`+4);<^){(a#Bvm15geJ$ec71vUiTi1sFyFeIwKp8M9i~h{xXCH^DCJJj& z9OQxFU>i8qkdj}3!v?IsS6y-ba0rJZ{OhPR=u*@$5`RjA+%PVKQdLo?MC={_E%1`_ zm60Sa$QBpRQmest8ujqY;yK-&>sr`^i~e3F{EBNkgRYFfv9Wah$82=@}$Q&U8SFeEc=*VCG7QWE^+R|)OJlUTLlQ@6? zr6Prqv$^mC6pJQG78l_wu)-Uv|GYILe78;3`FiWZ!o0Maw;0+|pdSuh>7tXhJj2Sd zP;VX5%<$`eq0|l3E1-UiJ;XGU1> z>&l!7{2!dXbC4%bwi^X6JHgfVtAc#KS*Z>K=8#(rI6J%3W4R5S z-<V=d)K-aAOmN!dTVj19-T(| zWBshXlUh3Gw+Yc?I2(Qrth+_{bR239wQwHOZ=GP)pW&M=!C9uO?#Ftr@N`di#4b7d z&wrwYGcfX&QqHW9YUPOg@rY>xAKzmZXnrdS?GQJ?AnR#)nyJv+;6b!stAkEprN}Mf ziGg>)qKSkJ0XO?aG|NDT7Wu3d>wR(~uI}WoH|I9jQPInCQo9GJbnJu@O+V@^2cFZo z8bwnvt6B|%Hu|$PBH5C&`jyda;Kv$6OUCpjdI*|^ZN~W-gO(4ojvVj@pNi$@o@*@# z_iM_7B-Na#83JI@akuw=K=&n^KMzUJIl_&lXF+oaze6|WG;hk1P_GQ2t$Unv%X17L z9V+?ryx{}X2Ku=(+h*SgFVEAh{qI;%FQ>D)p%Ee;_++tm zRjWAp>ih7qK`51=UTFJN&4w^IAM&O#JGw%Hd-68(KkG2^=l?70R zL-j$J0cI=@n6x0~aS{tpfsk-05(MvCCS0|Kih<}e7m~}82;8iJW|;jAx9;Y^0%e2K zFBeF^NfXqL%BfAqwn1G;V$C;7C~xfFqfwI&ls%Y%awf>xU-A&A-EUwn%VUbYbX|@Z$PvUWCa5x{X&9`uoqXj>c6Z$i+zc%7zM~#>#&Dagc|3vO5JJllVS^ z7lZ~IOi^T75>*F3$d~+YWryC6ZxFq6QK)%LO#dTKnG%F?tKKkJeBGrRuZ_V2>4u-n(Vcc0RHO%3nzP z3#B<(4A@L_N(S)*(ZQc!b$kcuLc8CC4(1bNd%F45z%)I4uus#Gx*yt>rP>7g?Ra|B z7ku`CcAh?DYj}=uxT4{Rbor`1k7-$$>Jz?&3?TmC=+v`Smy6C!#G`~uMM7+ya9hQx z6I(jr9~#&rdLSI6S8}c?xw|Rs7Aig%=%;SqLsbtk*W zD|^BA=-2{(ipRHs!E#T+C`H!=c~Xr6!%<)oi~*@JcxR}g$iP0e^(se&5gJGGU zn_G|0lznHK1Ti41lh6A#!8>r@X8<1mynUV#$m*6d_j$c1fa6#(Z5Pgv2ML;6;|hcg z{HDP!c|UL11>8hFPdu0f?ab0LsYi2Q-k~G2`h(nLZoUV0gC(~$%q=}ZH^b67dCghH z5xJXuU~lM`8FyIkO~6JC#1FzF?d_i z1`7num47gZc7)xKa4QJXChrHj`(|Y0mmg(2dr!OBrZQa=@L@t!)rnR1%YqBrc9x41 zYE_E{x>EMiQQ>|a<%yJ+iv}?6?U@R3NGJiH!VqR;%xY4sT;h8s5%RfKn#j? z`YPmw@;_1#G$3?A%T6c|>iZ+GKaJchv^3j2(5NE0qxXWJeMD}a8KM7*-L||*CCl6d zZnz7Du#7wYl~0U56995)hHb{DRi#h&_pr^d zu|N&|=Hc8$kLi!J1TqNJW?gj%RAglr7VPTrfnH3lgESuaZDTh9p=yJf0ym)H+cB@b z*1lQmwcdHq6Tmk-DXVsZE+7C&-nT27gHgKmIj3U>`FV8!J*ri}wul2BTgnEbL&Vzb zA(9obi}xtfCwLwmkZSh+4I|%Z8jG8 zW$^cmGXe)KlfoMom6NE1`zGbU z??P(|)@D>3&4}oVr!2-+1-^(bnxX9!gTLk4R0DGPRjaECR#p_O)$1EZy$tEWlHAp| zznIfXP_QS}UYCf@e*`QNplABrPd2%}tg#i|Ttw*N(;9Ww`~Yw7e#ScPc-uCGx&U0< zuFpMl@=7i|bM%*HpLGrhca&gFy-BE?oP@G@RJhF-P`wKaaM>u^Fh}?~%z~UM0upf*p(^%Qn!oT~f<0N@Sr{`P;jw zHe~=YrNHfeo_5-@?>95RJwDWb;m~{=DRQEU+C0`l(eVvJtA6RRtkdj&P!+97rdhuB z1AVU@?l^(ac{H`$E-X&iEPDdb;RbDtYW3Rg_N|Po5r1*^wG3}{UA77$N%Myaf<0O1 zH-MU`*cK_T@W%`#whd>sRdRpRb@>>~chZk-RB3cfZCEyr|3GCFkf)KGs!*B_7K8ED z-w=36{|v5kfimddfp3W^fwdYJZI@mQK_p176 ziR7&a2-SPgl)o`YQ*Kp!1T9TdXI^UeLAC3b+bk_|rPPb1SjVm2TeYvL&!sxa>m&fL-IRgsSOPi+*IZKapXhHbs<+i`x4tq^x@DvFe2)1pA#7I}){*~UYX zIQpbB*2l$mMsQ?vd2JQvB^~``meK6yDJ~Sv#?bBXnk|FMQ?RO>I9~Co6{7(;Y|@oE zMS002qY<-i@>{{QE;h(0AjUc!rg}a@x;m}UcTF@p#_eb_69K%8c#bnJ+3wsl2$ zv1!x4%0OI*IZc=>k{Y~nNAzf{?J{u^WUclr`GLR`uQd*sbT z(k!O|A_eucWbvm~#Y?k5BEnn~)tK%J{!QDtaP3V8BA768lCH&db<@%ElI7!zbyP?; z?)ZlO7@Kt{e@s9jp2#PdK=-Zv7Gw1}=O5ijhJN z4e=N|H*zRHoj42}*V`qQqRm(#skgfjOwzupo*M1Wt&=;qTM5jn(tC{lh!xA=8U&~f ztv9&6i*QD4R-b*kg#zQ(*W?kaT%!Q97k~5gOQMPaHgj)hnu4HZpQ`=tShnR@be;Y6Czy4|Ldsb=CjQo?t*3sSE3z7ELE*TlhbJSGc&A=b>OR%(T zT_An%wb+RNDXy!=r_pirL4zFDnu5Nv{$pi%fBlD|}+Cr+=d0u+XZ&7$G(#5u2D++fj%i>>TI-r1tzl4-#3o5lP{39+<|Z z71^V4qo4EpG>m17Gmi@{uOnerJ9<_dCC{L&I845Sxk8u}Kbw5Mi6qjTrGkWyS}70L zS**hJQ#GA19EH!I`p#XBcC@|GFgE8@^}h0B!l>VM%Sd!#Glj0y0zG{kT=A%1WJW=? ztyK%H=$f3MzT;b{;YOafL*@NRdjsaj_bGb78>r74dBg|L<@L&Zy@W>pNzo%9Kf<2R z=Y=Or9ZSy1faG<60Cdc+*2fm$!oR!&<<_G$3KKU=9U%LnS#W1RuFe^|xAU?TZ=NsY zfJ@+IyGsSX_WjfIvNQGaUhE0?^EQqqNlw@glz9svX3GhC!(G{{a3_j<+ zcrrK2v%t|6pnWmXwh|5LE)4k^9+ygFBxBg>l+(FE(V0`xJjhflh|-0W+dY&k!^NGC zk9*Ntf93``T^EnJMDirqv4Bq9m(}mxR58c;{B5k?HhkmlqbuFfLxSLA5bG9nX_9{J zIciunX${ZtO(=e8akQvV0c@;EYt&JgGd)s~FJL%^rGHAu5wFh~7eb?Vs6HKsxg ztYrUDvLT77m|A{vcNCb5yTz)3`}Sztu((`4Y9=^J|Eww>P2_A@O1Y#qj%CgNElTx8 zJ=l~i@SDj1n$i`77W%qm4s_>Fd$`G303U7tsYCn{TV=$QXt$j)0(sN?XL$uLnZ^x>+f%6_5R67TA7kwvV-Tw<{h-*)Ii1VMUmw@C6tp5gR z=zk^qug{DB13b~BW^KCx08d=&+W+>SlEGt>S@@F7mgfq*8kx<=u8};ymhzJD_ih52 z-C&Nq5_@U`kNeB_5~K2ZrS%w!#Ngd@hj^+k(rjbOeU-DZ-E64{HPUTNm)rA?yU<9s z%hz4}r{4K3d{l0X;=}kG8Mtm(D1r{^;6!;ek?9|&F$$;o66jC2=8^flY!p%btWd=x zU8wB#9oO%tH;uZIaSD%6>hWL+J%y;%s%yxnjV6)=os!X@kWB?iXd7NOR?ENYs+D5fx3q^bIByY%nP+ zfjWETqUiuqmfZALsoT#hhnJT*v#1d`%h2frB{=9gwq~69eBXu9Rkr3HcJ$bfH|??LH>s((waR%ySj9w-MxgCe%`xEQ{a#V2Qo7INnOvL=Q^PPV%4~-P8Jo4x>63ZkB@iB=gxd) z)LYHu+Fn$3^d|oN0JOUv^c-4#d@szQlsOBrSD2+*8h%yArzn8Loc^;<4j?_YNy>QN-hP47& z3F(F&BY5_7kDlP0=u=}oiBUc?>+|7l4ZOqaJI4F_OZ4e!$QEPPDi~`4mT{{n5eB~_ zhVW`d9m1$dC~cF+Y@b68aI}a&32RcfKIUwn^wn(vYDUxqetlYlzA)PozMvzq^9!3m zFh`9X>+hVURTNLk-u5WVpbv00V6k?ZkTX=iQaQ?nl$gZVA+I??f$kc)eacL5msw`qO|FM~PMZwN>msLEppt;1?8KFb@CO=Lk zf1R&@g?%EuqIc^KZ~m@oW)!*$R69$7<(=G5BNuVnvy@$}UBUva?EI_-2^}mHyDZ7!=~l z+E)RF`IVodP9M=*IVkIvYe*guwncC`RB-l+ApE1(v*74+Z4UI~ZFtt=T?%MN$r(YJ z#@p$te|5V_v(;97YFD7(SG3BMQ4yiMKm_Isuf|?|C$*|FSkRl^6knUSufpFD1g@#a z7|1lBRJA+RrVi}rFxky#wb)E;c>V|zHNrWGhDZOV{Y_m^D{$wrA0A&6|ZiuC6U?o#(84 z=8(Oe4GBusQX6_iAH!<^ZY0{}dyE37!Zo_Nj zdegvN&T7;;vbBgfWW%_T&=m=~5kizDRdIoL(&y2l_B3Qnm-!0vNze1u#pjZpRaS_y z&WgF|*Hm}A_p;|ARWibRO)PFi8ebtjkN<9erGYLA>v*RUg1w=H$;xZhSDm`ucgbjH zMNm^zCq?R#0R$e`@aKUve6|bO&MJ-iEt-Zwl)W+A#u*Na53q&$B#mLlua z4WhG!Y0-hf$wM+%s^gZPn|cKy|I)zGeztKN;%7xv_%1Fda|{pAv( zowtwJHNGE?3HEw|w=DZqN2lG+X*g&97i(3xSN6en62joW$8O8kiP!~4gL>QZ{TuiuvO4nU?IEn%+gv7m`wmv^Q8Ulfe%Pm-rf`e}dLSvsl-oYFd&6z_O!!!(U z_4E+Y1dfAqxn8=m2Npbz!GU{jgrFq?DHa)p;hKM^DG~4OU1Ig2HxZHPU-I?#fIUOqVD3Kk^Des2^@3yRQ)JJg%YWq&%t>UeKg{+U=3_CZWw+s1 zf=v!flb4+HnddCWb1& z|40+kXLcZ-*?Y5ZQy;iDYD%YG-s289f+?ODqSgwX0H5*cH>wNN-7{bXIgGjGbvu_? z(oeYfS*(3QJm?Db$3vJA?PrpFf6-VcI)^}YOevWv7}_}~mTP$ku;MSpdH>4Vt$APP zSDhS2ok_i!xQ$mzhotu{lMdfaPj8pF!3^Hi)uXG#^2+FUZf^BszwJgyIvoOIi9eBv z;_4+(J5kc&8a*%9?xMmJjgR(~_il3c=OrgfoB@99(fs6lwgScM)es_))F(B2ybYm;?96ahZdt>0?;o0%R zF3|~Pn$ZQHcI)T2yd=2ZAK^L;@#*cKt<$fC;m8P9V?jtS^$6HV6#EsF|1(76{M=m_%5UEw!$uSv))`}nMJ~|tU z5^yDjRa-RV1t0Sxh3&p1W={ha9X=<_zA%;N?pFNmM#?6)-5-LNcdeWlQwu_x4`uqZ zXXzqx$Y2H$_^*3kD{r*Yz?<^lN>8TKYlGSO)9ow+8A849G%jG7RW1KLL%9@vgHlC9 zU>%@gc0KX~eDE$P-0Ri@?4ax^43XZ^sZnV6-89GSadlkcX)Wd6N-SDbF3?lB2)lKx z2M892J4xG@uO}a<9;dg@>8aWkyk2X9X&c*V&w=eLI9~1FC&wrsn;jfAzyncx>{b*i z$^A~`8CIkUYw`(&UvVD=s6g5j!u_v0@QK%!;V>Q3iX@D5ylknL4w*s29>L7JVn?O6|*@0T}`$ z|KL9|3wvRh{+Mh`r2Z}JvfOMW_9kIeIK{2VHc-ZxHeXS8ZPKUHrYUTX0kdqyio@>9 z_me+3@^fa*(eQy&rDN~%?n}@~S!Bt?%Y0ZADf&itL-1)PHL%ej&T#W2hU`DxhgBXt z9cjU05u>ER6X_pX2l2U0!O*-z79J~+kC5TcDzw7#5ioNgX-U&c%a?cC9i5#$i*uZ> zANsUwcG^OAmbwcTwh?8|MnD9-P+`*?H$H@B&fFDeDKt1g)hJ*P5a}&0 zlBkoPB+TiOo(h{~K#S=A$Z#T^zDLdsx8_N3h+~N+A6cbtL;bn{AF3v6+?XYNQ43EY zE+Qa>WCpJUC)uTdj&AiNK8^;Z)4&Om=g4)MA7>Y)Py9pLm?>M*6n48y7a%0@r$1&> z82t~>N`ZK2S`ew}kz1Bzn|+( zhB&r(&^?o+13jYoRO)-WADQ*_n8_xRXo>KtU5`F7!-Q!F-v zTnt!Lehk{nPZL5YwBMSPpI^)73(!snVs!57MHg3v#4;w-8xJO${Uex zT@Dj3YNPr?dmRG=cDJCk^Rhd5wB6K=QCgnRnRYa1E8X~}jkaM-RMEX5U**GJ zG|}Aoso3i#u=96>g-!RrNkRN9G$)7^>fHmQ;}R6GZ2O8>Ah$ql!xMy^bz$@%u_q!6 z7;yzx+I(d+b&R^v0;LylVF2ogGWZi>IWWUp4S&s&4(r*B6 z2QH?m=!aFq)9p=%#x6tetY<-5L zN+x*e>XLeb4TTi0n6sNd zd!YU$l<|&X+JmABgq zpBI`vibb+R$UKL0c&(k2Sx9j(OJXcJnAo@2B0@Y^XVHJU5F&uKKM8?u-5pm4aE2ko zQ*;S!J%0JvZ}eQlJ|ehoNhYg~#-T~n#!$kG!l!|!q}d43A<9Or^l%H8;G{|IGjnZA zZte5uw-+(9zjkzmhcAw5Jy-lm02Scz;mIZ7uLW=6m2tYw)0mn+ho0MYgR59#=E<@_ zQ90bKP!?cRBIE7!MY`RyE>9F|x=obhAw8z9>WHq-oz<)?x(ya6B+!jJoA5bFRJ+F@ z_4LfvjX%IJo%=YbPbEEA_#6SHc?@{8;t5w;(lepPQ&O)eN!kai#%58wIxwl}6V$<8 z1k&Z3jCLpe#!+mwuo~O*inMC#5q^n_FQPd?0Q*Un4mVW@!G-w-KiqAYqT42SKvTb4 zcKh73@nZjd;7FP*14>*rv#{`(!@u~>W`D!yC}6eMh`O?p1B+^hYKohSN%)kJA99DQ zjiPY`B&~zZD5@fay~KA_32ib-D`^=BvxTk1c2~8CgGw`dywS31>+X$2@wfy#%4g{! zIWTPu7cq}{M(@x&hx2mLsTLUZi_G4AnL2c|cBl0@d6JFQm*!&Y_i8u8$d-idH_9LD z^1CmbHtQI!@uHGt$9J&+ua9$Vj>?rIUo50lt43<1nT-%S-9E6yAl3Ap6u2)!PhXg| zcY=LKzSH$@D(%EU;r;#MRNtf0&B-ee!{K}?F)OeTn+y;E>-(ca>YoS+Ki7Za8-j%| zB2GqPC!i@X{R~=+3D*nB(+zhY32jO^ z+MN3zQZV$SpcLtTw?v~vvS=~)ESi%Pyq8n-5n`GJAGnjo&Uy*_q5kx3H5;U%GXY-* zQ@HBts+rGQFeZKPbDzOUEvH>DdNDEoM8UDJv3C4Tv2Al#uZ0{9a*s`9t-CxQw$tdC zYMJq6J05s-p(LL3D-~w*D#_uRRG#fP9@+_mtX)-9{VFMO<`OT(%^4VPz%^x;>rKl-&18@4g&QaGd=RS5^_3a1SkinfW@9$XZg2-9~ z?Sg%JaH4EW&O0&agIsqY%F+_(#alR~X0j`U=F@7Z9ns#yu^g*EknzOw;Fa9;^skiG zv5cnPFzF%TE6Hv>*@ z75Hu6d5NadP$k!B)?adM$9}Hfb)Ci1h7;A1v&%Qsv@p?!devoA3Ld$XB{Kq(y-kx6 zDql};FWDa*^!kD7&EdyhFAcTUK)jpaK~H3AZ{W2w&BqW*SI>5=i7C1>l4fezFNKY- z1YEg5PlQOIQxYF6tnM!Zc@6R1i6719T(UgX`c8l59QtX{o8n|1N+qZtX2qTaftV~p z+~d@$`+NBe{_!AVVqsw+-v%M^=aKxDJ(p&R38xrOT(bj+1f8DktKdBsEco5TMW)yD*B0$BO}+!83r`uJU~e;S-JENuLGoIb zFP-3fN4HQGt3sr>AfEyl42&UetEGf`wwj{s1A-qj1D_{}Dr0$#ld5XD6+ctDpkuG# zEQ~`-%q!B-&*)8 zuA_V!6B&2J|6J8?e*B;=sc5Gb^34SIPi+!x9t5Va4XQ_oXgPlGoHG9VgYx*wmTi0o zd7q)1kw1#a)Rez}AV`z_mwbH?C|F5OHkuNJRR#|qJLr%OR3Yt* za-4AdbXuhaBob=_VYAnVJCW?;jyh?p&P%Z^iLF>YlooVB87L?zClU4)e`L73S~KSj zC=xcSc1UodNEeRrN%-&Ty2lOpy2AYjMNw%mJQ`KbW8Ff z>oM6PKW#8IDTiTEM#9vfzf@mJDu=5xqbv$BQ&@i&MYq5FakJyRltF#x2l$&0@`~Iu ztUjWsS${YMSGVYAdD0jP8BIhTe=m|4Gn8T^bbK^vS?&pk1kvg2?&maeER@2&8dF*F zDG((Mowc_>yW}>qz993_0Yev%aC30l*WiJJXvhP9n~t1&AWM+nF7=Y(Yf$Rw%9~vT z*+8u>d-ld!>8^ogu{rr-EK&+rqARiM6H|5}4mCn87gu|)0l(M_VIUkn4F1$}B^ZcK`OKAJV1|>h_e*GpZH>O$wX?<|tf4lWOSY1$Y(E zlMWM&e$D}NlzDl|&2@jy%9Tl0&%VPz2D91?QeDyIEoW^8sHvUQQGI%Pt?)AIIv(_}hD$-H=5wu7d>ChUMeJjTtBr8fSf=7 z#-it+)7Jlyoz0IECjS*e5~vH@znKXch`X6^YPQQIS(zLbrx#g(}Lu|w$1hOfCq5zt%S*jIoh%=X%7kpB{m$pbW!3xHp)Y7t~P>Y%O`VdLC4V2 zL(j{U|2umn+ricNSVezVCNSN;D&3RrOsZRZm#Z$itc`Pr^K*uN`*5p0+rdd6T#pW% zt0r6Z=F(Ma$IO9&GfV4;{k!;Il$tw>?qT=N;j1GThCYl1gzMrS8;4v?tApGVUta4L z*Bv~3US8gAjZ;6}t?Ja!9N;b7FQ<>=tFbdpJ~+;=(}DAQRFoF{FGs%WU;U}FHuh|A z=N9%HaF6%~o))`2|8943z23fD zzHE&>tn6&gjkUdx+>XUU+N2S)ZkfI)D`$mrOwZv~vpAIu)G`HSXu_I1vS~%L9V`i1 zH+AY#L$-8!V=h;O$9Lc?>kdyZ@(*P_t^ecwOkaTG+DV*=(hNcvjVX-pG#pj;gRWqiw$4`*^&c;jR8Y9XKNJ zdf7wZ>-6e+UXljwSe-V|sk9}~{d;9?!l|nGvx;?Ni|%!$r^v>~erLpfy3Cok=e2ct z<>=!LxF(gBxLf|zcKUS|uhqO?8FFFAnV~D>wJWvVQ5V6)jNhz6?oXa_&8$M6bP$99 zPH+e;B}^5u5>!N@7)lOSw>mwA_hjFfj;cl+J)EnFe0oJ&VAKsT`bcve{!0E6f$+DK0Ms zN|hX9h5r>|0Ovrumj9|mopZ<%H91Nh;b1uoUO=w=1q#dM++hHg*JRB3M+wsg^K9kNsz&!}IPzHr;jQkQic5EN8oC37%K49`iy zDIGHVi`v-{dq;(_(nQc~!7JBB#@P}HGFq$T4Dmdu$VTQq?<_|jg)PrUW}(uYsq$D* zY%{2xV`>}1em34I9YJ{i=OSEUeipyD6b|~5U(KyZn<8sef zta{)QYUd%~CCi%>k4*!OtTv0%D|K>ggUx9x;hN%GPVTTx!+DG3E!elb_EcpV)$Mt- z0M;!D?ciO8c#Kw&O=S9I8RRJ1BidyJHgyK1He}8y(RAn|VM7rB)){7Ab^A%_Xx3T` z$SLjNK7q=l1_u%GwTsdZF=^&pY6l`)LVxt0lCY1^8&cv zXi;b)kA#gx8)%hpQRqRMB${d#Su&5pai)5VndcqAW;<7*MB%?a6-$R{V9_trEJI45 z9568XWy9iJq4XmjwnDxVr6s*xxf~E@(^m_f<`3XX*=2ajL~WwHucFu@@ds=Q?<}ke z`JX}9d9#R`R2$;}UJ^}E${|OPx9l@ zj|O0X3YK)?RDH|Iq(wpb77b;}bl6C0=}qi&l?wFWX`(q*FNL2raLxnD5sU){DrEl@ zPiM!E)x-fqLd?*^K;Mjv6736YG?cw|jMa?F{wY>*x2yAG_+PTwELu;OLu;CKF}t_LgNEoy5|s ztN^w}_;p1#>s@XSjYyHO;yebDTi8qag18Z|86JZv?dFh2Tw}GP3YM2;niaRgvN)CZ z=?xSBhj8)gzGAZSw*vT9aaN(IEY9kf)LK&QVwEzDMl`5OPMGv2=Xvo~Y@iZUTjq~FH6uyXib81e4UI4JTw5oZm zw)rm1skt{Qs!XbHY%yy96844^MltSr#E6d-q*ce60Mv3#ZJ&J?jmkD`T-gUwC5e#k zMJvc_gbu%Ot-=UvuXD4~O9Yr@0881;*s)-(QVStzzwHi{CXFMU=U}rB3VxOKqn0daQ<}O1O9w__PM6VK}!wWB{YHO;0 z8R0r-tXUAO71&t;m{G7TBmK28n0etKO+pQ0?d}OgaloiiEIaB)_%D9SFzYA;EHzyo zXobG|7ar;^>@=9+vbW3e7}eqc<7(}AxUpd=F0w(i0^-So6Lh#Y3j5UPHHK#~ejqF3qV1ucNZ;>V_%1L==AWO&~G`8VH~fcC9t3hf||g-UY4jWh|91fMpc=IAdlH!z8gRVXl(pOhtsj3e-vY zDKWG9;{5jx5(&|wQb>YhELo_i@lxbWyEoxiq2>*sSvKClVnh)D>S!xCud5vq!~<3A z+bjpibnll5p_Mc{4SrW!WM;gKH){|YP;2nO0L&Co+f&(C?=#37FFM7@o-HM_E@lNF zQ-SNYiQcEXV1QDse`}YqgVir=fW=m?7KuPFW~yA_nKeP0<(s%c?{kA-_Hpy7sjm!w z7;X9FZXws+R$Rcs25v&l+lK!lU^xjB6|esk&)q|gRkC=3s2i~a=&_lhe5Mz`Wdl<= z!@F+D3eaTWx|M{5`HKzET4x=4<$nxwUq`}E3B{Ld&Qx1pt8JNkg^Vz$XL9KmP3WEK zHouI{T4R`M^M3jJfUNi0FAJ%UU4c2kNoU`kfI9+e7`POv@DUjSS)V(XXs~`3ps~x# z%>G{NoNTZ%J=0LYX`}c*E|`_IN@^F3V)IWC4+o2#JPqKtTJ-ENs{!UyMY^bFy}c&D^P2F?hC&XE+x44q@JWSb*04vK~j*m;mSJU7ghk7s~C0vRZyXi@)&x(iQ!Gach{0T~R z6ajgoL{GTH70}duqa=DdCKk5l85f}ewqYMXn;4JSzy@mRb%`qh==l94;`_TV9ft*4 zfuCPM1xqbZ9iVu~uX|h6hkCwP2T(*83csRs>W|&FX@MmY&w3ZgW*m$HHRJJNmT&#`#5p z4SWeB-=yzIiFI|@!0*m6z_fbiq;>M2?6S0@=iB_PAsoFE@iXimwcfUJmw)XW2L_sIrS%+r(;m(<|vG{|ME8pQjLAQe~CaI&kiM&Z=spSb1o<=B1b%jENWK-@HRmC zaTeLIpoGWh%MKY$Xvy*oc9R@%&nF@&bv3&|`~+}X4^rfkqP4EBKWPL|w?NU@hq^}n z=LT0Bw%()IJkBC8#sMg{$+Y7 zs^S0Du7rr2DGYGHHEMv$HTE_Ft#4c_4|KKL|UeC6Mu%rY$StcPf}KdnPbl3SMBkzRRg=QV+_2I+i=iKesM5y<(gU735-M8TF*2L?TZn5s=H+`2E+h<)*%d;Jq zPRGHs=yxYOCzmH59}dmM(b2@aebW_SOLEp*)bi~0-rKTM-S9kd`FZq(pIsq?U{Nyy z+LHOzfm?Drty?Mj?EOUlv_MybKfRSxFN)=31u}``)_PGI63ydo=;L>P+kUY`xR{=XU#lhc@H%rA@q+syxw|XNu8FX=g@&Z z^gCQ%Idg*rWY=8f-5@=@zT4WVrd~^+!AZE-T5!@Jk*xTv(l*}`32fy4W~jxO2+PdM z%wC+4nU)DddIN?N^pp{a)fZAFDvL@<=}^;fN!E&mo5yK+#)f+l^ms~YC(4R%;wk_R zPf23-wuJeHN0VFH-+gEb~7?InZGzCFSx(Yh?Tn%CHM_3aS zd1oZI&>K-L$%gi#*y&UOBvmeyiAkuup6qgbKa)SP94!J+wL0yZZH$;}qbx78=$1U3 z|HnMuH*+giqHg+tegoe%HVGqU!gB7;bxcPf@5gzZ$v4yM>Q#$n5XgugUc0`6FthdH z&n$-JXmVw_G*1SJJlT?`&1?|!YXfTgboun9JrJwgsh!1Xwx>I4eccPdpJ!g;8Om@r7INq)h2VaiX zTImJ+E1eebeAt4RKJ{l#>$Y53jskuti9Qj2q3k2 zAU5CcSbE@EK%=K1_G|Z|_o_Lxld}y}XCAikIvZJ)CrwH8q3w}o>Ra!I2ymol@K@lu z4r*njJfX_0O%m{!XRFg|xSl$Fb2YT}QsoJlpDYKfvrix(nKd8@5gw-& zbiOD+oB2vX551vDA(i=}JNEx{-XD7XEmNw0XiE?#-1fqshjOD_mO7sPAou%{m`D1N zfoo4*C^NlJfgtHLkH)&kvqu@v$TDXs>{fYF8<9u~k(=gl?(5IgU?72LIN{|G!1GgOf~u? z*q<%E^8=-9lB%W^vb@LCc7QH)th8oivC6~>3fS7>Hy)jg~B9+qP}nwr$(CZ9BQKZQd9+c5-9e&Sa+Nb-$io-97!|`&;W< zr_QNUwX4qFvW2iB16gU=1IUGNHgx_bs(iT>4o-Fh7PQQ}?)9n3jG>slW&0*lftR;# zNWy5<6_O?DRsJRq(VJoMS1FecKPuyRSL;qW;-)y^-1k%7ICKK$i?FeK;n%V;z9DP7 zf2gkf6MKpPi-Gg0GBZurneuV=Q3@;%@x`CbB+3AA(<~AekeBVe=)+s(`g;=zMH3#}f?c*$>xu=rHfGRCZ7GfU-L9|WV$-xkX|;=G=E*@U`J zgI~j;=*-7Y|FP~YB?j1r>{fMW5jlB$W^Dg^qZ_M&Ar`&_4A~9@&(7t~hPRE;C`NjA z@Cu=VgryCHngw*PC2(S~iGyv{w#7Uzc+wSfRR4_0n?cC4YU}H;fv7!#+*mc&Kbw1s z|8ZC&@m?uuMm-7Q&oO#48U6Q{?qC{*W`q$)i33rM0iiwokNB#wB$o+)e;0BnWw8Wx zF$&l_b=q)3P?s!kG6`gX*0#NzsabS~vejSZOPd}XU;2pJto*6gIcvW(k;N|uOT8aN za|~lh$kHFea@2*=Q%;P4|DATBo%PK)mwIH`0%f;M zsLq7ld z7qasm#+(OJurern?TkG5YC`>FlPxv5Y#0%lgsE#MIR*|Qb z0#l^s4ROT*se*sJmq*I3c?$&U92279aqxwFwAM(nHQTjK^7OsQq(;OV@y0ZmoYTSv zXsb}K4n&E%3+p3(vO_6KGB7TOObt}ptJWlS43NC4EZe6v=;bRc<)W8|ZtA0&IqC)) zU9JuR9B;CryYBocE_5>^BVj^=;n}nhA9sKWb(oKg0=I(y?Tgh_&6Z}Mqy*H=JX%Ysa6PMau zlAurDlcp1wq*uyz!|zK^ZJX>U@_v$wR7A^LFwXAK-kH3{)blgYt?)L9rX3(|tw~5Y zzs~@3x33#b8oHIzYaI)_ZrUIw)zL}LSKSOh*l0PoYPUC+Fe|FCUB97Ux3XD@9?g36 z+sRk%DEcxQLhZ3&d8i{5DHLBmD2pecAcW_K5~m~!3uv-6j>l^aA&PkM=8CU`5m51e z-R?uXl?2YAXjA2=y|1<6nzu>Y1|UmGqDDs521Ln~6j1D3V$s)<1fX2ygouiLiIA!0 z1Z&!(;~BAHHDYVU19Y4Mc=4LSuO%$DMjM!qZBG#SZ+tZVW=(5s?JPQtg9NKqJGc<6 zDiEL3CP}5X%gr;@S~0T2X{01faZab5qWt0Mq!1MYO?KSF#rZy*zBuP*GAL&3sY@ig z*}>&p>*n-)hZLzFe%5|!^MU3gg^I>FX+`~XSx_mj4f~`5 zN$ti7vSKf2il*BfdL-LT5J78@7$Pu%U*Wvrm-Oq%KE*$3Ni9i5t5q@+S}qC|9A7i1 z60E${TD;pX5r60RE*15)p*)eDY@!g!DjKzHxJ^&?VY9^MIP8!phkdD-dA3*mFTkUAF+-Mr&z$W54&BMrodZDO<<;`4i z(kD6*T(Ghp^M&}C4ujn|G2p0diuHYDP+gFfVX#Ud-ZCjL96<+tVEzCCF@_5$rww!V zekYhPJYN@^584S#&oRsGPM%x1^^EH5`BR0CucA|@e|3CoehoR|j5_T8C?vX@3GW0y zUZ7ek#WEzQ2e%r+*^R8Q?_aR^!-^CH&&&KYe5W1T;;mk=qT7zwd4wDSJlx+YgH5&9 zfz7(3gXvden0NIn`z%RPCpVmyDN5AL;y*mJL0V~=mE#eG&cg82GN-=KHfWi*x`~Qd zgscy_S<=Ld5>fwd3oLdv5^d%qFRUtz#&lUn4%n2=;Fh2~usgqc%aYMsN1k5R+t^Y{ zsNA1lmCYDg84rEbGD(I0j9wyS1q@(GV2} zGID$q)~U~=GW26c%VYru43zcREQ0f`H@_eNEfW5tSzO^GnYwq*=$)I{SaP9UJji+b z?%eQrPPfLg;g@xyZ_>a$RkKC#J#Px4eV?~!xi^_cUo*81+kh)@EIo2?Pdu*HC@#v~ zVd<(gBToGyotmK`G;>9-1sEDo+71BM0#D>;8hj{PqRJb#emGw)dX?Ka+#*4pC)=yQ z$h=Z)sg-?nv#Ev_$hynsLFiUFX?P#JW*rwhr=jv{Vspu>SgDmC2*@OrW;@=ZkoNti zYEm0*-7;irvqFcl`gm5@{x$pE-u(yA#9poamUo+4_crjLJw@BM*yu@ z(@r0d(RO$l!0HUOYkZoy2T{=}pE85;YJ&;DdQ4lyFy&kS(imT}c<4;7`Z#Y~G8HK^ z5U!0GXRFP5SZL8nkLKF6^q@CD=C-6*ps2^f{UimNnOnI1%*$Q1OAa%hPCG?rx_|T@ zDdA#hc(xb5VX4-?XVsh4tEQq0z>ns=4V9rbYEV+c`gOt4cx7Q~BY*u8F8$Jy+7<_5 zW9l=&8_kkU7~aMEa{#e+tI)u124#7RZ%8XhB-3k1PbkqlvCcQp28^v)#x~u|C1s~3 zpswn_!qN5cliBj@HSQyPmFz_KaJ~_n<`QhH ziNw4mG6ad|^lMQ)`s?O+PRzU@3r&gvDHpgPPN^+KdnHg1&gj@uD7f;)Oszqh{`Q%m zA~--ATs!0Ke|rOcRSQ4$(F?$z?H*@qSFZ;#eBGBX9hl1w6D_4`*Uqg5$YH&{6+75b$&SgiMISbt&UDv$YplZc6vZ;Hf z7gCq5MHE?3TQgvt{{C0_qkkFRwC#LFMT7+a(EOQWApfsnk^e0M@dJ&RIvM^mGNSeE zyw{5Ko!cjLIBiNG9B`2JAnld8+b_9EoB-&w(%ZVv*b49%bGc4NvU23_-<4!D`4?im zhdtiXIqhMMeqFalt&tmd{`q4xfL0xm^As;1Zy=w!DAZ42D1mnDfidQUizN1R!X=$L znRWb9hInYLVEMG)-(-yDENqbsd!-aGQZ41dXCcV{KqA_wL~x8JJ*T;=Bru>8r;`0o z;&5>rq4j-YyPgOeAt)XV3G|VI5I)VBcu2?ZyXD>?nUx}w$s*Kr;*RFT(YmB?_IR0q zY=IqN>q4MNOvzujZBazuqjKh5TdlIS4ja&ExZ(msGYZ$%#T9NKQqo1-D}dPv0bebK zfHEFm5;FKHjZ>WEXBpaipucs%P)3O0Q@6orjIJ6*h3pY+rvr$vvqKg8kFqI@n>0i- zI-mfmGU+OwOGWaR$KSVA@>>(e$$i~;d$SYed&W!HvrgAjV#rJQ@n9b^`S1kKlODwS zlHlJgW{FT}z?@EipQ$RxXR{WMO0$)+6b*MFQ-$)iYzAmDvbr`iq@~ifd0}-j5N|E( z%?Q0%K|pOy_Ul=rry|@@kCtjbqdU@?8Yo{WV{DFE)X~@I!l&Soh6iv0C;chZsy0ag{U|1&c|x+{P>XPN3%mUd+Ugpbdun-rbcV!Ya2^o(K&(sJe%33KnRk)Wx=P^3B)GeMZd3q4%$ zlV^_8>w)ryv_67OP2{WAa0HpqLga2Y$9)1qYpT$nk-v_2n3p0JYtFNV;yK~Ln|-~+ zT!v6Ij(p@~fAOW@Gb#&Wxj>S2uI6j<(8FmVyTXOfJD|IXjX0F07@QMkn7BaPeQ4KH z*0qeUoyDwYJe3VuF0+@ko&6CU41(19&$d8R*j4~M-oQy3C!D4dIthTZ4s|4PN!EJ# zvtK!8f@$oR!{+yFyZsR%uy587i}5$_By}F1tK&OEx2%aqoVl-E|0-XQk=H7%6HJyQ ziiEHc#2f%Fz2!%L_ClD8Y>wb2sCH52urf6sz=k6Ddk4U;AJPs*RSW=d$rkbbM6x0n zo~4M-=U4{=Wiiq(AS_!T^d4i2no8 zMeT!;Y+9#H8P``aru9M)nmg=EGGbiHP|r9zpCc>`wu3#~(@L9zEPNIN+g1r(;qSe) zS84y3z<`<$S(WsxojNL^kenmAS3`^G*jcu@67f;)lv!O05t(pbLDABMFK^Xg%f9bz z0TIe;G|DHf8M~g3noBek?d?b!G%n!+<8T0?{;P7c97*GP!{m%hkPWKd>e&g;5_H z9Ct}@7&H;)p6`M7C9*dUgH4%xvW?-6cdQvyI&@_3!av{Qa7UdV>b%`UDLSjvwjKEA z>YxDa6>58dl&rwkp?LjN`n5a@t+zS|-p2HpI30#|54_9BGd+Pa{rxAT>ids>A)SZi= zH7lu`qVOjJ+hH*rN?#^5d;uLUgj%$-ikv+t%v>eN7Mo28#U27LF3Vl9njE!_+3I=5 zqD|aF9ruy&w`Q?n$PV)TCERpW?r+=AptoNkXR28YAp<`hJR|+P5W;e2HeL9oKarTs z@O3xOf>&QT7(@MvbOndm6*ePZ;@3^U@x+{8uzeXe1p|geqt3TbR&JFDH5yYj?jA6u zqlo1)Nu_0+eA1S7UZzg;%cN8=N`tK0wI2^;nl~mfkG8_wwyvJ(pIj2fpFx@TK&7CFsbjWCdMq=<8k5Z4%N$N}=;(V8*eCMRp z#P3*a&=!=H80R@8Gt&-1+3au)3Zs6vm?pLm!BQggs`|bMfTTgrQNIW7jEX$shC>0$ z!_r>S!GwX@@X1rj<$y1K_#;Zp3GQOu3_q<0|dXO+zD$G#e?! zvKuPPRHtgKFzXbEFv>m>91iq`=O(Q zYw-1l2msNeEE4_U#l`^GULZBm%;`*qFi6^|!EUE#A(#+LqBe#l^*tF}0=h__(}*gF zJ}KHWPu5?V_FLTBmd_!>>NaNi9l`u4-%P=i8@Vh$#kMNpHFHZ2KDTa6QfQ<%vvt!k z^c}>)cF=8)HA8h<(=+L}Xn<@kd3~rdlY5)B#DI>b%~{>rRDzT}*R6iZF^} zmNwfax($SiyuzHcr7%IWk0Ef+_=GDqp{p#9a3%GhKYglnE6l8L(jwoifk$Uf8Z?RM zNS{w8f07; zOX^9WpE-g-DonJckbMTK#y1ugEh>)y^>TubZr?{IpTT;Sb5Gf^{OsmmS~qIi`^%RW z*~h~tLJEx~7a*(Zfc~oXLQPR;8un&L3yU6^uaBLr#xxQ4O=>D;niPyZSRycDm3pu9 zG)YdInt?1k*xls7`c2$zI7@!SvypTCxk1Ey{1pSDnx>reN$n1{Br)&y!RlAzcFmY= z@<{*?o5QGokeQ<#L)uklAw|dZIw_g@<7s3IP2Qs!}XJaKPs|!R7bxdxL8tkNAw{(-#;mgsU$9>pr zsp?y*_&EW{TP&t{idl|Pm()XUbBwz0{v?pODm`BTC_OXV zjZx_JHj??mU`NXuwX%Ufrc5BHS$Fh_pdHq0`-RgU77(PfP%S=a)?HvaH58;r^^}-->Y*Yx;X^NZ|B$qWoY4nUXrjWdq1_`rApH}7JnXu28DP_(Q z^E!tv{BW+5Nx84^-fd|oOPs2vuZlS}?~?YA`%22KKZ?q3N9>R`8*aovSCLlR$F3jQ zcdsA4pjVq6)N6L&ydV7kwY>U|eQVtShz3@Ot-6kSQ-3q008L!|1AAe!S-+T zTf>^R|A(C6N}o}>lCBj!I99(A)vitraP254P_z;EFZvQetO1*l5(}}h(~SS$t2yW- z1j+KwN@dbkJ+Hje)6?{_(Mz^d&|R**`%seHg{Dd57e`SSL@^nND;6`NNQINVR}Q4h-!UEP#z!i4U!@kuJf6<9x`$@Jwv>H zXDw?MA*JyYM&sfM`Swi3(0X~bd0Sn@RqrrCEwd`HFY}xu4&IuT%yrezP8T{0M2Z;QN2sUqGe6V7l zB!&jeks-a6x}t2{vPm3M?tq3QD7%CT5@A!vq^(94fBb1qqUX&N5x$y;5E^Yqzb_e$X<@5c?lLyAKc%H5%2`@59 zak3^cf*t$RMxLUWdZJ?P>AYo3->xf>)Kw#^Du0`*?ln?5DyFuVOXaAO&Qu}gR*IBS zIjW^{R7vHik;+jc{V}@S5RC-iw^U(WCo-~wJaVhlS2r0vBa>Ss`%(2sr$rZ9)$&ml z%3V010`Qz_pdBChR4ORkzScb2k;~W8c48|L&G*Fa*sfv2Iu)+!9zj3SL{OKeQeNWI zjW2W0vi$Te`inxc1+lq}^g%6s+?!|R-pTk9Z-)ubt|fR^moQyJ&-mD`Lp|QK7jp$e zMD}EA7qizoP0ZDl7C6)x&zoUfsmZQ<8T{x`6;JbNXT(`r0+?NhM zo=p*x5mUT3NuD(IH^0?B=3QUb0l4!j3y6YRPU)7MzEGvQ*koUc@cSQ+Qu{Q?ZcQfHXVVvV%`Ce%88dGE1!^98a}8`*cKlyBsHzB!6GiSC&&ozY))9AhAz-+t6YMrOEdcY}PSM4&i~$Boz?R ztmj4|hTCq|T?te*(r~>R`oF<1M1^*VV)cOCUSaQXukZv!l}JzHHGd0sm*FtQselF- zp$xY=(N~9!2SIW%g$KM#CEEtFj=PRIFddCfE(V?jS7g46zQ3gVCs|*461A{hya!dt z0tf*zH=0tc&@#!4g#ozNV@^ozh$JQ)*rILPC(9z6RGeVjr~%kPYN;+^Se4pkl(1GtL7{!5~7ghqUN$0O8(stfJlub)Wcf*rqX1tR4$NyI46o_6tNUz0ah z=60NL$s{&Qx{;;j1Uu(pdWs!VfV4RHY3mz9+$FC5T}IxXcM|1!kkXQp3lDCiRNM}^u-P^UDK9u7jF^?2pOGns!FOofSmq)DgL zOnzfJysgp>nKi$X2cSC5SkP={z-C{iRc9bvG@q&){Vq)ErN6PgqxS6dZ$EL-D`eus zI{ zdJ-))?Sl4MEjecI5N2c$M^uSm{vp?6Xu0I^kkD!wxAx1c}*{z1rrdC40G;Bm!}NJPT10mwL;n@vKF*SQ${n z6`;lM`I~-&Kq{X~YMy7Y=19%=6;XzfXEN|Lcy{VhLMCSIDAVh#En)WK5Zm6&Uci(G zBnMOwE`8QW#;L+@&AX*gw5-1r0cQ{Pg6hb^M)BcvQL5t(e0p~LG@1)(m6RDPTTP$+ zF|XoTdYP*l_&qfT_flxERN-3tXqCxjK?!nbFs1>z;yzho-&;U=+zx&IRgOd_*O5On zrY!aFQlOKgQf!65_k`$9Yj{sW< zA`D0wt_#cy0AyRm2*5%(AEk;hF4;vyI8%fO^->$UIO6@UHo7`6mER_ib>`z-h1xj^s zDnh^6T(X4@`=&i*9$CN*LaWWzjg#=tRFvuV`FGJG={%}xleKE1{?6iq7lYe!oM?wZ z^3vrU68Hp3qp7^uH_(S=H7NEIT9dig{V^PmTwW#`8P`V}l=Y%dk5L;gHSB=1QB4<; z;;Q8@tfXMra@0`fDI;`&lTz~;%q^TI!gdm3tJi)o#HiC~59A5)GAb(kY&S zDeB)q*}m4M#{BglWw*2ldO6?rn8tMCd}WMKI7W`@dJFu0z6&2-NLV-+B9qmfvI}XYTe8M;R1^jEull>6!f>Bar`s)SEC+K->M$urB;4 zJ5vAuC4kPRcFy)r|HSh4OY_^gNDAqDtIw$NDV_B&Iw(Tx$~IS#{Z{v*F>AWf-Bpw@ zWn$v zp&{nrGQTU>kb9-n{Urz1^DZdgL}HY!0ow=~SGP!9EKe3+sXb6A#+bqF){iS|Y6ZFZ z5y)g0A|~uNqmc`&i$JU_251=OT3$5HJ|?)zcC~MP8^a*l=^K^}joMpo?Ft`&QCB;_ zdhIC=Z46&%qG{LM;}>(hEzci9gPJvw;-1UmQ;eFM88~8`kj&|&12z4i5I?Qs<%DEj zIklLJu;l5|{Vo}@*r>*L9k$vAA;~3i_X@l7O}krTrCVRR-ioCnbNu9fgJ%zc?<&N( zy@onBt%m1s06l$Mh4zh9Zp_Wg()wyDPVJdhTh-}ZH!yb%H}bX`H|KWg-B;m0dP=1* zX{k$k6D)8Xj81!PFBSwj1C|_FHDO-I_5tDFYd|76v;w(I(Hvufi=1%Tv?IyhzHEyg zM@D?R0rvO))98PXUrpff4&;_xEJ9X_=VJMA;>UXBsa;)^xs@84<^|!WN7UhBi5_ImSoz`RufUZZ|#6{CM z^?*=4+NyTrA1yriEfMlWJTO%XzIt;u*^;_}s64IX(3%dd8ujEyOxeAA^tgPoJ0TSk z*+1lDk?^`xFDar1R}+ne10eV2Pg5cS1T%c!VNh=kL{x7-8-sM)^1i%_o>gHRGqX;s z64pTnSL)*1hEfPB5*(eXBrs+BSmrq;by1U)C>5?Ws2hRQI)?)RmUhY#6?e)w6s$<{ znrMnlY7wI5futbOTmLFX^TdUXeDYj0;yK3yO>Q~ zfJ=hecaqvKV==2t5spBW*yBLv5JIPF5DjF}oEc&4`H*17C>rnxf+151 z(<+LlnkYF&9-EWE4U8pi!`<3dwJm+%93qCY-ewXh-->z(ZnTD%45v!~Lws`e==|$I zlVG5hbdjJv;@Y;25Di{Pl&EGb8b-W4MIvw9O->C}Mg*bi=FosNGEtK6wRj0W-E zJJLJ0XQ1#HXPlMz4~^5HIYU$o?4}0aI-L~L&3g694W=Cg9d4mXkl@$-UqHt65(ZX> zwIYgr&xBRf@Ys-Ia2Kb043tD~yg;)Y@Et1QproI0UI(XL-FPh6v?kOi=om+N;0CHX z>u^Kh|97<(`g{id{oI1X?LlT~$&IF@QPxM%)e$4;g&p&Ni8?G`fR`~QWNr+_Im8MAD<0OF*?4Mo8w4ZySi z&CXzJK;8l3XENIv@Z~Le7pI9-(mQHur%OI{L~z*w5N07D)F5z1;vB$G4fSiWU?{~st)__bOA^M%(6d*Bc0 zzu%C`N8uZITL?#wG0rQ-~lelBds-HbSFSIL`|Wh?IJXv9+a z`aJdAVC+ndU_V-d_F(i=eYUSQM*2$jSp?*$>KJ+y1D__oY=cV3H%h-TmR0BG<)Ej< zTctZCp2MFl&5+;ST}fw!M^~XIgUpcY4p>iWRG?&#_m=cAKForB=)aY#+jw%!RTH=9}_wo054-vDNh(NfbFVGTq`T8Rz+zFt;{eL>i5& zDj4gRlk{F&PB~XP#(Wr;BK8H4^ii_3YX=p0JLISjo8Dhw8%hW}yPUY>Q6hQhrSQB0 zmNH#v;6@4Jhi-jzT!5Z7>orLeVRN;LE$1v{cA;8I@e7b#(pu>`ezy{R@4sRH>i;Ut{11!&k`a*Ex!*ki0sIgt0syf5*BQb8Yw;gX9R3X#%2k%N-w;FS zxmSlti8dN5I#Zt08(fu=5-EgHluJNv(1NQF9NW6|(@=~4dY(~gHYOF+3P}*Sy2{Rc z-kvI@)UkGMUcBPfq4m?$(W9+Jt9KbGa$XehbiwlmUvQ#V=|X=%vTKw6A%9O|ImEA) zD00#eR$$e-@eY}S7_YlE0mGl=nun@Ag3b`@H{r?Z{@^X;I0NY62%5(W5}px+!#YE_ z5iGhDfGW9?6&I2W0e<~dFrQ+I=j4m!e3*d_gN9@1w#ow*d`WcidD_nYJOALoF*PjU zQEQv+MqVzh{`%NRk)AH&1=FdF>kWkV4P&Bta|JQTKuPH)(}D~>izZ^7_@_Ir1U`+Y z!VOH0{fmHRgoTyU-V*&I(>xwU^VJ!kiws4k5$VnesIG~L-kfoK)aQNl%?=vBX_}0> z^|dkN5v+D)Shjb11+;EkK+Pw}-P4g7*krUFjt@pRmWA>Md@#_B^bjkYBbS;fCSj=D?lF)=@)VVFD?HK^FsGvQ zYBuW4C%G-DXb7xKDut%|LiV}QK_^>5e&O~EtSlvyoR5wMq1#!SK^nTwK+C4(dOI&E zO?H)m?x__HkHP5|)7aiutnDyuQ~QgojFB#ue3#gIgy&AN3e_qNPCdsvqWv&X`GT1* zt&!HY^oE-v$TXIF-8X)>#91e%Fq**^weK$#i^;{Zcn&%!oJex9?j0!F=nQL#I_F<{rv3nju!>X z>!O7>&u%oiI&s~7YVv!bbAV&{AdK|SEP5b%S>4Hr=al95s3NmP5yj{)Pv@7~JtMHd z@Pm~T{P>pERL)15Cp(F&ZZ`Q`pH}LNbg+2V>@9`(_@0-T1={gH8^w{2JdfqU{L*vt zymEht&h=EfW?3wVZ@5>E@<+Sw{`HN~zZ|Ep#l$=KA428ApCiTfKR!-BV=?;1_O`b6 zcK?)brzXf+2MZvCU4KOpa;8~C;%x|IS=7&Bs5qt8#3^=mGBt5N{B^IQjg^Aqy}a|| zp`U6bcC-X3XA_^)TDGOD106&@FS2XVwH?t4Je2WPmx}uh>Xc5Hs7p&$B&bAIqVl>* z4Vl<%M9z@U5u3!#X1t*4GP)SVf0W!|k?>o?+(z>;Lo$P3)0jC#`X*T!?JD@)kTn3TzGnx3fAY6OB4%Q*&@30qv9KG z5IQZ&fg}IZgP9Naa&j5{--oYoI*WL=pY`_pS#QSw*?K!08yo%8GKVT`$PEf0_+G2a z3`!G*Kw%jX5n7QpM3y5K#b?`^ONmd|HR&>Z^>mGiD&CrsA5V42uvvl523ZcU9_S#o zYf2gm9veUhg-0#EU{V$9Q#Rq&#kxM)d8lbXG?6{Xb(ST1&k#t)(s%%>Qz~($OMroG zCQ?qwyKrht&~az#!MLZ|uTS}!j$>A2A*4;b``mM6+MTj3idZ}O?&%`MUy&1!wE&Ww z3t9VTz~}zHWet&}cU$IU1%#~|vuRbF-Pt@mCI~@~(k2WF8>&9R9;TvBmtZKTZEVan z!f0=RLyt7V_EKp;Vx3^S5Kv>Ie;UL#qSv()6Q{@Zr(>%UFII+vUn5V#u*>Y!fsC|R zuQ!F6hQp-~qs48z0F-9#xz2k^@+x4#RYL%R*gBDu|2AO{{d=0JM$Vgd%MC^ZffiUBQ1DDgU|h zQZNAk2>Q*_7;|~xJye}bL#l*%MhquJ0>_mX+rQQB=-m1M(=X&r>%m&B|;P^M=(PA8|>OB}9 za?iJ0vpt5sy|CnBmvMMP{7F*Zqj`cNyeXgxa`oQ&_sLx*k(80(hDh5kQ{5*Tj4&J? zDg>6KFv(fHDp!~+-&vt0#-)LM8@Z0Gnl!wo{zr4XP%)BjL5#Q5IYqmKrX4bVwu2NW zM6o61?$amk9`Djn0oA|;=_x){s%Qb$N(LPDg%WAb? zfqVS3zISq>up*s#m~^AE7D%lwlW2l6Zn^RXd8a;|pl(*s`CSgkw2n-UG=?2BQpb$d zj&E9xy6U=erG0?0meQvXtCP{wyF}VBhP4Vl0}=|Ow8A8R1u#J*ndsWY;o41;u;wMI zb&Texm99x(tT;(rm-*WV-&k;qk>iE`U>AhwZ{`B`fc%9X+~y0=4FQTfodU~u>>6FC z;Sdy(_0>xg&eS0<$Fn!#*uH@o#l&a`r}&;&BS0uY+&TLj{SdD$zRDkvyhb3dMz&v|#rZWgS7<*`o$_ zaxlqZ07Pc&kco#7IU?$M9u8%2AZgO4*ic~_>I!OzPpyp5RJ)nb@=W9}jBLdI7D#AG zhU|h2rcwsp+}AG~DT@Fkb9J|HogTY3eSN~%>HXGJRJTu{v~%>*J`CzY^Wn!)k}RbQ z#A(&Q@^mG9!xbkot8*>7Fw=;hlv{HpcZi_`?q2;Axh>Rp)h|j_x#1Y_B#1UlxEuvC zO$PYPwwQLLDVk8)M5ru%$#_RI2p0*IWmI@@7VwswxTXY-yirnAbTK2v7A$u`Sup%i zjVX%{tAeq@KzI4PlN(FQta;15#6)C|-o*j%MJ>2@8VqD~Xf&*%`R7 zF*+dCj4+)CTb&N;B3AUC$3tcR@|_xck8;vz_I;e|ZmJiq;b!RZ51dQcjcJh0{kPbB^mr>zb9XA+9sMa>?7qJ)HIVOwKzNS z+EHF2 zrO`rTKzb(%C7zyt)aL#KUQ5rd5l;M4yFB-<_?th8pQ>T{~JZSm`20 zp16H~3HCa^{`c`7^U*6D@h3+(00RK{Z+hte7x4VIh##7wYh5gW5_Wx09d@OgID+Y_ zlGfwEY8hG@$tW96JBiS!iDiMx5-K$Ae?3+1$R~p}3u*4N?Yzw_?tc+|dH&vq+g|A| zs;kCMuUfGBH_7&F0v0S(DV zl~hC|c`S^iyA|qB`5<;V(PTJ_&jmDWuT{@ZqP_QRJ(Tc?5OlM@Ni1DS{E!TpWE*ReW06|f4ufDbvJ+7J zNkQj-wim&Fl1kWE8aWv{dD2-q+uQwXAs(tYX$8cHvh|KiD9v3L=L&+TZd+J|2DKHD z!jX&_%$)3yY>9aL*ZW>U8=%cJ*lL1TfC^{IevF*S& zi8NE(-RGOOaQ-+i;-xgg07}PNv8ycqD4VKQ8et;DQU2OKIVYu2c`8D7QW6xXEl~@| z^K&dZoMw=N5(>SHFdwxkroOlFk0u{!kufZJ*0WZS+y~K)l$9aLh2tMU#NShToT0G{ zwGf4XTG2MKie7o8V>aQkLEyj4VNdae>n~5x76SmV>bWrqD%b1OXqhProPVzxXA3qj z9VT^}#{N>L6D9Dwolu@jzf`z&$TyZ+3&)>bIhieIU>M^6#&jsA!SJa1&0x{qp{V%G z1ZE84I>yzGbA|`1A)S=`iZww!+hhUTb`mubeVZf}vQ67Pmhdr;oZ#@5RD1ay6IYD? z55N3JtOVyj@yoxQb?5(#@4+}^VsR1)03gfsKPPDXhY33uPEJM^#(#R&aLHrsz0R@x z_nvz3irH5v3k6s*i%j62eIxBdgzd4E6MQz*E<_u0J8{e{}amRgFU<=4| z2f7G;!DPPnCo@FuA!y9~!D;mCzIxrUL@T7BA;b4>#hz={MUDtXiP*+PhzwvN^zZiD zDsI#kC|+%LV^WjY{`Y3W{)|p@8x(k+O)pE+lATk}U{;zZ(_|Hm$8!!5g?ZAXO&Kg@ zw9UfqNT=3B>~V~)zua&-?J&RGP}g&F@N&&51uq7vTv_aq<@QL3Tz!^`mX!9`^xK2G#SncjyNMM$WU~Surut$NkpGJHwO8FsGIXJcV~>9_%aKE zED$ds+U%TX5yUT+74VO)Mk+$tji)T_h&)^q`_nc3MoB9Fb zsw7UY2+Cg2>Q^O}+7VMN*>8XGog=LNzZ4|=;4`3YkNY zblKVfY9D^Rsj4-T4{|KASG0EpK3LsjMh?>lRGdUVKSn>IzEMAw-n-S=KQ~a1SC(L* zC5(=;2;};!4PD#fQy(rq=7Iz2zeY`1;DtE$ZM_WlTDfiHD!&)0Pax*UdYQzK{7J22 z;lHmKe@JLwU+Z87wjSoP8b3w!? zo?XYEiYYv+W}Hogal2*1Dg*TeOSXdTKN4?;D2tY}N-qTsW4WBb_{_;r_E06MU|*Pe za5YL~JTt!j%!ABn1iz)$isUye5J32_c&2hGl7Myx<9j-;I}DZ3ctxJZx@4h_3ZhUd5PNjQdJJ&>auVjj0bCJcz0u|IbM(SzJr%9O5+6*7rLauBmNFdd>%)_I8-rKH zcKVmVSA+!TCjSoT`P1WfKWO*RmFsLHe!g8v5j>O>$TCffEtbzKAAi!BC<5h%I;n+r zhyqCz3n4Qg;{&A1MRW$Ug=>E_oVQ;V-nDOR>(|em5ytE)9Da^0-v`Vih|l|ngbJ&z zA1kH*F1mwhEa_{1YhUtnar|wO4as$9_8v|U>?CoSGI-K2txsb%t`ecXjxbm}N z=~Qp={pt7|zH8~MkO@SVsEAH->o~ZqBn)i?vbFkX8Navp>akXlvz!_qVU~~AY`PD0 zLIDJ{%d?E*g`6JmuTg>UZ4f_1&d-<9_;BhGr)l<$;3#lbb%}ELLbHwbKcrKKRi!(wkA+mu* zoW0{w%g*qMB$)n6%cJdcI*r>u&E!lw`96i~qzJgWFSgb3t^?bXHD9YCE) zWi=&=uvnulVE(H0XTIg0UT|OghSW#kMF9cW}d_D z_tHnHe|mnos2idG6DcewrUB-VK&gM5bd6R(e{2x6`hOJ6etZM7H@|lB1u~?!w0i&U zE&_@0mP8@TDK_2$3H-KTxI|36gzHFQJa1G(vjwHAC}!mXR)YbzZgy_*~DB z(`DxB*0(VWKY-V8%<#$^Z7~2M7|23*z zv!C+mTff}wvFJnWa0j_3Q+6`Div-Aj>;Lqph-5$O;J#hEpVPHb**J>74uG zlp2>;B0${v-tpAiuz|o@z@yl*UC2emu(($K?HOhENA65if~UphZcNpA^2?nFf+EfG zyILSgEtQWaVLiZCIq-`E@x60*DrVO}9>+AJ%GtDP2FV`<>@{PLWw<^~KQKKMNiy9- zT7q)OkNKei7SS}p{g(A7HE$gnoIow%Iq7LKT{)?U;2~y6(ihr=z@gOT%L1zQ3u{8pNj-6=3@ftf5C=pJE2ulme`Q zyz(5Li^Do-Nv8A_j3A|&D_cu_L&X8Kb~!dYsH7QOLx1xM4f5`8iNn~4d> zqG}zPsW7j)*(!cj+UcsVt#sh>ALi9fVW{EB7oBk}4@@f4vtu{eacZv2DwDHg-`R0X z2B*emoPwl~mn=;(q>}>bED9qDr{(Vz6EMZliL?Mvc`FqW&P}u!UOelb{?zGMIJ!Bj z)D5$e%dYT_`D-O*sB&)E!M-L{#?#N4Dz$L}G+0G}BYeY#F&2V);W~-7mb)+05QXGM z`ot(XZberA@z!OltUT&>9}A4^LbkNL!WO01&=xs)TZK7X{MVecCnpwjRl8=sK|!ws zZ!lhj<-A+QOhvVXmqQ&M7a>8KbXnJR$k-0 zYc+();~f(YU#W1jJvj?_4}P00-=Vgu(z*5gPDd5RLXiMO0%7BydA{z5-LGfw20%K! z@8@jNMA{_0pjI3K$G2YXEq}xysx^Ljp<>~4x=g)sX&WQ%^W>sM{d~6_Ti^FML0|8d zV%f(?7h&;-e$B_qn|rye+U}1l`p-LK!K=n)U!B+Qtjx*h%K%I9L47mnD1>xrfOrJ@ zV-XUlAcd2-3uBzLKvhkv*&ytm6RNG@tQN>bKb7*6Ylsh3#QPDiEsrshj8>Igv4xkc zY+r@5Kbp>Q8J4YC@B9|NPl99DVI%L7y(`5#W%?v5Da?>7DZnEHplK=TCwh#rDNLIX zXqS$~YfVwi`1DfE$E}^E-%4`@mhjBJOui_o`&7{d*Q$c&o>-_6Tal+% z$l+T$9gCEHh4TXQ$Lx%)^@?w5YZlL;Z)!kgZ44)L&tkrn2aB| z;-K6@EWBxG*)-TC{%t_T6yquKiw04D_Qe8Ko-@tndf705>`XGyIqWu*)Dy!i`8%~d zuDiqSkembJw`b=5BG`F_HvX@T@t2-hW5MX*XgC_oqcYGYBT{{Ka&Ymt1c9-Td4fc^Ryl|zE`AJLYh9uDg3x~JK0 zkzXG5rw5va$@N_l4f%_6=FCo5fhoTQAI-n!Des3TtRkMI1B}_7k$Ar;&$CSF4&FqJ zk}_wn5O7?4Qg%H>5j7e4#Yx;q9Y`i<&QkRS`L8#6dcOj6r6fuh>hJ2xmb>}DP1NuU zV%VYFb!rV#@5tE3TG}8Q1Ii5r+s>s&>)yAv7ET=`&=uO?%P`v-jyl$Ehr|$_)R!rOONhzk$o#k=$q$ z<;853h)o>FrilpyP)gYpH{)QT$Tz7Oz#xl+M>1AsdT;^$%A@q7LB8cAj(?)le z;Ek2tx}_gS=aVv&6QHNK-1;^-`%L`Ey@YCoQ(@pAF@>&pfP%D-7x51bii}PK3l!rY z4cE4N(Jv3a>YYw>wwjy%4YQIEwR>S~CEH6n0~I{{mRLAoKDZ2GOLH|$GanHG^MD;n z>9h4-msuu4)seJ1#z%B|OOfvicHTT{a(DVtRLw!Ia<3;A`v+MD4T|WDo>ElAlUAa|RM^)C5)ayjX zg6FIn3pn5-)}w9n`~vr2v;mgG)EwptvGx&=-iv%bbv@znfd9SRCYnfm>`W0`SEt?O zBjOnjt^ORA;>rOYr4G+9qtK34V0J+_?tD*PNaHKeGmKDvNm}r3txuSga!vB?W8Ef+ zAZ)AmgKny}x5Q@YAE&RUCtST*={Q`4aira`7*$6&z%+V`61?wl%q8jQ046Kpmq4wP ze*0<~UFedT1xK^%oaQqdA|qP%nGG%n-*+BD=VX&Bk7*TDiu}*k2A4++k&(vWJI7zh zQGg_=8n1spIp;nuaenysh|pGGh9GUW=&pI(PZHC28ic|$<|zaGkTk?gAykSQ3nFEw zDNQSLFHr|*eo^HX$F_Q&T(?4bnrNMka>vasicBV88f3vvoQJjc-jl^eiqf534?2k# zH3|)EnLi9?k8EVTUc29nNd=+8LU>(?H2-C(LbRlMA;-lZM;Fwxkj;&KJ6=Qb#HQ($ zM5yTKU|cQtaUj6&BD>bI8TGtTXJxye}pbZ00Jr%{eQ=x{vRyn=)uCl z#`J$tu~s}=*8{Ex-+*wBKMpXnOanp&xFDWkK>Hlf+(s-}@}+tZU|ypGHTEJ+yz9FG zJ~~bG;G!0fz+~>#%iA`sI`qkdk@$DOa25(c6bZ1v?*?lDP~Ff_mPl@xP66Ja7`-pN zc;Q`GZTs}U{VjQ5g&oX-QncwX=SB(6;QW10_=4V@;I#dQdIV<0+{tft^ zo=5ykMZ}uFf8Y_%c}Tes_psG~H$gXCc?rHakagNZM~ zNxtl=>4$wbB@hs-h$uG@u6yDqQAVm3r9#d#0Q=q?La#AWhuHtk zvTwX=^?DyeCfwb(KbV7Cy-wbr@Wz=-Oss*3b;j72~fNkgedmjDn=o7CMcoy8BI^?@q@mdBncs+%CrmNQIklEKh@$T z*nq@G0#Ptut4V;F(gTW}ZT#=^kXSebmO)9tadZ;H+c2lUFAxe z9-ByGTL*`uys5y7b@WNPNtbdzg68A87JBG@tFk{jV{5Y3Y~xAvy58JQ`DzQ*009Fp z+YJASBPUa+z(*A4#k_?t%)<<#7n9yoPlr};3qy^Av}OpmK+h8h*mA;Q$@*u`qb`Z6 z5!ib|AC_TOsZnJ`vW|a+H5^c2St#lLjxR%Fl#7YfCMvrQqv1US=_bkH%pxKw`VQio z)fvwccBx4gzk4u3&}`9KUq>dXpI6n9Pi_zaw}YzdCukY9`C{%wrmT=m@C-owo-uyw zABRreWa{@!nsjD@z(HwgY91dJH;oC%wthtZ*u*wx!%WT9sg;mPEgMW*p;cy@lFa`m z{WAh?5_gPJHO}hui(0ArKHAu|RR7x1D1`w?4u0mk5Ed1VMk9CFlz^yQo_cQQXC&P; zmFCH^Q$3WiXQsgVIs3YI`MU6S;B*Jwc?Y7ob?HR7r$TxE@|bb_kHLZ)c@v857>6oo zg;>9rt;7W?yQ(Kca_DFJF{V1v8E9-xFK@L9v30DJ2aBuI%7kq7l8vF$w2>_tQVi*ZM2-M{SBP zh=4@)g5u)h!!dYsl?Xw65+9WyCb5K|aBJvyL&4pvbeW05&7SwwfZ~ApEvKi`V^WeV z>!%+B&P`mEAAjI-b9U#J+^bStf=K~f`HAu(ln-%C9W#!p%se=VHkT7q$I{&#ti1tT z(JRJz%;!wEV;#lD4c1!zHATFj*cx~7K8uJEBX?((a25{9CW@jnE= z6l*i!dXe`m!T-&x+vp#Ww$d>5jxedQIn4C!oG6Ehjtb#gA>ZbmTqjGD=2y^yTr1BJ zgDTf4e+bxfCNAhFV4ck+LE^K0fU;SM6%3E2B?oj}1qe&g9{Q(Pf^kbGWx=>45O-+T zdIS}o{J!VU)LpibEYV1C4)!oft^0*(0r^@{co)1p{E(r!VuhKbc`m(HVujC}IDP`| z(3V+P4mO7%S%gn--tET(%T{cx?=hRba=Pik?@bVsOqHP)StLSCv5z{E|9XdL3!o$@ z^fF2j%woAnAz@I55yFAmXK{ZJC00;qs1ZG_F^9kC;fR5ivJfd2%63xfk5T3kESkn}Y}VOC=rkV)$@HR9+3cl~AnpMbk=8reLGoWdUAp(|Vv=tu#MX;Du|cd3y$K`_&r4)s_5pD0dRLx}RPziuY5Bb&hhaJ|2*G9mS zXJ(;ZFL%i(fwsI8W6|?xW!m6zNCKie$q!!)z|?d3&u6pHPgEI7fITI5%4sG%oY?UN zViGCef8ngk5FpbZENk&gVLoE!VKjUh{alqH8ubC|!_I08*3=N}HaN{M{Gt zP(^U{Y$isG2}F-ippibCV>YJ0xIhZwxPcnSkB@e zmTGh<$8JYr0N5n2ee_MRZZ7~oTAk3XQ&X2l>+Zc6osMc0E!S>GX29yl&2BX$6?#L8 zU8YPZK7%^RkklA|F+*y+W}?M!+s^H-)TVmb4vu5hIZnUCQfJS9r{)ant(}|JxTUFZ zMpLvY-!?5?7)M<)G6c0sTcynB%SFbcE*^ctOE`Pj5KTEX6~l(b5jWD?DG5@%Q~j$; z3gt0Pe6Vmz!bN4GQd(P(Id1wjZWz~DIU9=DIfJ^fP+VhH@W%XNn!X42)G5zV+Sc>> zJUTVOmUF%{-u2DYx=Z1;Mwup52Gh9)Jg1Eg$T4AtkR-8RnB7T9!;_n;uShL;M5a-T z#=nge?ji6DZ46uqXUWY>-d?iJ-Z+VjtB#_S!X74B+7~`eea!rk9oB@7nC~-PT+M;_ z+UFET8;sN_T=WG2^CrE>bPS4zfoHbqFjfSbMTKg$T?1uG7Ar1L0~PXuF67@UzHd&k zddH#QL|B1Db~=HMaIc}D5so2Q`%9Z3vES-cPhY7OUuNwF)oj^5_2r^dqhpf^jnQRUu} zA-tnGUGnf-(EjKXrft4Q$al=T2eVO+Z{-a24d(j5uzvacO#YK#Ua@H;NAof(+|dN% zo5&(3>sKB>S8t-$)^B48W8boM8%o0!@5>@rdv}ISTkgQz_8Ak@-qx2JIV2G#`iM_D z(rPr$9t+yqFR!T3-1!qXR@>1w&6THtKO+MsInw6aZ2)ocrbP7BdUP~*$8nO2cMQOU z_Btu^1c2{>$l6vyRvXKG->-!}wf$BXi_XH>iBHFVlJKIF-M2K(gGh>g>hJFlr~z~*|%6~f%NAQ>W%w2?}R zw12vmy`%4uFWKQc$dS13J{YG;bQic`U8_k2t^QA&HGB?cEq_PBX|PCALXL|K=W_ z{4NX|VPhsYr-jQll7F8hum{S6n9?`fluWs9L0Sn_+iGWdLj)*JfkXtNYt z?uZKmpvNPnE?6#zpkVWZH|q4qF7N5(6keT^1?z}*++ zREwji&HeJKX@LAn2K=Sft*FO(3GPh8EOu^P=~TyPV=tAJp_d>C&xF{LwhvEa}q}$R^)wUNB zXH4bdvdpRRrziKIo0KT4ASYd5&C<*^kmM6j$x1U&ep1U6?p&~yNqI=lx;kT0Nq0#+ zpu}wYLW6Nd42Pd*mLvz`FB}H32W&DN)k{A~l5%!C+6F=nMW+}}&W&30HJlJx?~ZNon}u*V={(bShSi}x$bciTuG#`G6udle+-5;^~jVnyjU z{^gFB6RD$t8o}>1G2>7p zzOacv&=KW52rk=RAj`5m6(<)#a~i&g8JCyvJ0EeQME;RxN5@rer`AMIQ57UDEkMJN zP-#FftZ}1S5Xh6mr%4jOqtrzIjD;spUXe#N=Z(lkGy^kqH+Pwcm_?~CheT#j ztRjE4sMF#)X2XU-crtSFNmP=O-@nwe1O9x`TtYxvT|_z-_`ClzpdwnRMYMkEzf#0_nr|cv>X;H zuPl>CyJ?P|oB`*kq-0S42GE1lVB-4tH$eAGt!)Ve+V|up@)A+_qOR&cS3pIz>h`v9 zmzCFHxYfn9K3HFy0upgxOc5^Tv*Y&hUrRUK*B`ghd4Y)Q*S5r9&aP-BYdwh!C0{n@Q^DM5+=8~9Ue;``r)$FI@Wbhs(eDE-Ht$VV5IJ1`g$-gY%6*puDB47GB>;) zXVhnd4-8_c@efTgkINuoJIqBH(Z`Z*XR3;?(?g}_dfR5`3}lz@bE8H2e6?m#=VBk< zb>2GGh3E$!=-TO5bCcK(yrb>PPCV;w&Ijv46EC7ZdcTFb@sZdLR{WPaR^fd-YQeVg z2OPdLu;CvdU+()dUf$)4T`EMYIsRtF&RfB}^{+{M1rQz(@H*CkPMX@iT5CzF+}-sV zGZZx~*!C^EH7pm6?^T2l!5B>8RL#5@Utb`yDG6$c;UxMRA}c-nEF(o_XwyD%32fz% z*F@`5T@YiMv*UtIfr0ihuh%Z+K%w&M9q*STq%Z%66t;XEJvJLHSDSLQBf&jk8FjjYt1{$8GP3)UX4*2NukY= zxbcnBv0`~pc|=`F-mMiJKBiLz^X*cpFXN_WomR1G2AyfmdgBpPjR;#5C~AFR__zL` z$=;dRU)s!hLZhSZY>BOQjT4T9e^x1`{NFw7FCwWkA7W&6nJ%ol&+X2P)~o0-eA9O5zBMMScJ#YA6 zydH7X=W7YYh73+MKTJL6F7wwgHIMO>xmDxil3&-m87M2@D%5D~inFkw&Z-t0JqaK<*;M7?ETXouI+AsPegC0t(#4Lh8n zgv0~N4#PfjIiA7>VgLf^HnggzsL(|Wa{a*H!uW!%?$!`~hOsHgXL>?9jvRy5Bg6V# zFi#qD)FdGyx4IoR#*l;sF%_!entrNg-?)UD77%jPmd1z=gbkgAB`G_gg#x( zWn1Mgih|l$y79I~R5jd`g^5X<+M%$;^}GLqoMZWQp^tW!|@P`gIy*Y$^0Hn^#oidc}N;wu`an+X^I&y*A48X_h>$X zeDuh2TQyiZ&!Vu&%?fb6)^r|J4aBd%j|fluFEFC0^=W!~xPAR};CCL4QFyVTs$A~B zO4vKGVb6-tIw$84iS(`)C{H-H*#B;cxyU19ci29FEVGB}!o1S8p}p!2V5bt?!HgRv zC{GX1r@UMM`|Ed|O}Bn}|UA?pmxMs!c=3fe6<9G6SalQ^Vf zemJjNj{b`s185`VpYfAEy5`UmSxHEj+wvU5ddI`ZaDOUSJfWx4B!s`?JO2tH5?wHy z#_nN+{?hpJ+#=X+g?R0lFR(grPu4mmQiM6UL+4~P=W^n}kP_Pb0vR7V6jx=@+)-C)!~t@ocm26Kdb7@>^3&~Z`G(~Xs6CnD zT2DD7_slfOC`doFOwS?kYdBgV{B8ZB)Qkt05}w;n(V3&R-WhM6?laxl#fL?eh0B_SmZ}zUl1`LTrfX! z3!4nD%vwHAnu*0XP!kgwjXug)^1L`Sdv)iCm}5l7+i6Uhu@V9px{2;WYa$#lN_a%uF0-4`%DjZq3(bm_ zFBUX;Abyh9jPNEnp62ZS{5mv6YY6Lraj*#8y_WBg0dFr2Nt9rIo!%kQh!?6bCslf{ zSM^%1(rDlI7GWTI* zIpP#+jp2m^wEAY|3BMHiau2VuH|y2Dr;ZcXL>Srp;S;k!w%Ju#($R6r4$=@BPQFJ& zmS-q>!z0NS83yafpNL4}!=TM@iQ>8c6)t%KImxjC(@qv`{s|s z?{R1gkNSP?Jm= zofUg0!oU5(?N7Uax_0_rDCXrs-YHOVGTNGPIgH%~bv@(CM}fjlHC{Evr%axF3`flW zjqcy2@|jU437Dj`Kz~B-^Lk^*Qq+Xt6SZDQ!@Q&mPE3!a zg!0C4{ufpq8Rf=-YXX>b&~GGyE+<+CDn)0>fD2nQi#Wkcb|)X7W%93;8lqn6mH;9@ zlo#of{E_DMiUJAlWqfW9(sjzeXwL*;Ckj6(j%n)=+QgT&OI)IUty0a})oS$dCKQ>j zz|qPjM!)g+&OD=^G(q94Ip7{L`8btoSK?z4`yOu@A7N$K?T4#QU39GFDY?|)BqnLn^=GTC zuXG{7v+OOHb$9JB*lpn%9Ra)D-Xx4ZsMDy3Y^=9hlWv$-!E)bgT4<;lmW{kzWOxqQ z4b-fC8GQGO45;jMNfn0*Y6>f}CF@VThl|j>i^;9H<@pn${C`vX(Cwniz}03wfhyLt zv_{OorzM+EyfKoYO9i!!Iv?-BoC9?SRLFg%kY3xERM(~nD|x<75JTjQ8Y3O>41BB-lp&ztrLrhEh96eM1LTJz)E3To} zvvK47rmgww)yWgI__@*FuREOCr-8i!m}qA#iu>@JsL+_r{X01Bl-=LaW(j-BxP-V< zv92K)xB1C)0QEQC;)dZzrrKISnD*E5Ck^r3-zVmwd3O$q#ldGtPCW7)DNJERILVB; zaVa_$vl*y!WrPLNtS2f?YfdulRMPBXD6Y}bqbmxF#9VCiQEv2uk^{9;!lKm$V@UBz zVpjIaEw6D4{6d&k7 zPxJIv-j+;0$tLewnd{m?sI!GomaC}ZJ%!&6I?ERj-YZ#+x+&kfZ6^)dkbC(c4(Cm| zo#h{;_or{A9eIWcu1SB)77mLTZlO3KK-uUI9!a@9vm=5VQLuG*W)&1ASl`1tT{Si+ zW->^)4H|bxz*VX`G;rlb3HZ5o-q}_Y`mz#S;(xJexmI%fR3LQt!j7%el<4Af1g_BW*ri!?oNjW<`x5;@5 zc0;E?=V`)=a!n}BYkNno_u}Y3k)MTRa7mW|@N|f@SKc?xhyUWOFT#+k$rDRbmSBvm z#I4;hwb?&A00rQ=xF3C56=qkfxaQEXs7nPs$W|x4^J3(%-}H+sUWY;GoN@7;=$fom zDyUp^(xVz&bB*o=R{A3mRrGq(%w_16`y?~_zamQBOnwz#~{ zEi*_g!18c z@;r~+q}mO?|2ryHRwH$v8;krFf1!S_5G)t4S+ral#cd7}@Df1m55xg96U7n|80~W* ze3JQ?J6<#2k%DL%g+N9f#jdX4Y7%tKMX}eP(n6RUT{%D|cv(?JkDlL4jrD0HX36Yd zx2#m=UKY)H;`Bl>cIrmWHu=nKd6cVq)=Z+3RZ*rxFB3`^Gl1_MGWhaJ#7~nl&N^&} z(|OBp$u{X)OjXcD?4IAYb{lpWp9yeT;anMD9d`D2L*LoJ&C*3_T6$>^^9}7?FW0sG zDyz&+xrziBmVJ@kC18mJ*K-_wkCx(=EgnA9%%#y59+Ole7NI%^VYX4K3hT@`%))>yBPDe&8GL0*J&oD6c30(&^z6AljVwpk_v%g}dSB-VU zxXE;B7s^&dsS`sS53ORFGfv?d^2tES1mlZmupOuGctdZAlb>pHZFdX$-B2$ioz?o= zSFEaant~vCEpttC&x+SO9j?7^uWY?7=O8AdBvmc1)&iRVE$=7wYFPi>p#3D2?(UPs z+5Wkc2pFRKJk=tpb5KVSQ6oj251ELV)}BB+7xRnHY|Jn+JNzY{b5#C*0Ql^ZfpTa( z)!Bdlel_Y;-gj&`RM`I#ntIXN;r8S%+|O5nac}CZ9cXt*+Qas)wv#6jAq{ry{>%H- zWykcwM*6z;3kE;RHx!#Z&ULC&tf*?vyk$9O+`Ytb5?jd`6r%@9pB#%-?v^n?79nuX zLyctPicH(!?e}9l?*XMf$q9uv8)q4^t$JnmLFUXUb0||EwjEFS-4DinzA>_6Dn?<~ zaJ*r?JRHPVj=}FQNEOG$mFJQ&##P5^$mq$8R{WfsQF!LeC(?3S2(MZRPAwCIn^kiq z$0K%O(oEHe*An{()0BTn@e7Zfz3PH*G{3d5^Y{bP4YlCMm%eHEUE|c7WOqQFGIGKd|<4;-&$aqfcfwIS!TG==!`F>GwE#~oRI z5Z@b@?N5j8U#{+b%j;Oe9e3|2@moyajhi)VVm=0uEXd7#Qki=a?`hHNtienR{*fi( z>`zJk{2tS72i`Hy%{W8zV<|IFGkrq<>Q@xYi+oMlFQGtX<2dQ9%7jP4^YG>P^iSy{ zCSSLpKZmlP(g0)_n5iyK>dDt`PAya3$#kXq0GTV&f{N*cI_kME-&Mhu;rLdRkz**n zElx^)(X%ad5b+0I9JnubC~IXbg2uCd6F!yn#ernS+iK`1a1XSvz;l>YzyYU=k2{k7q$)cP zBOVXOikF*(*2dm((quK~(A$_4X6MjSUg~qCR{SO_5D2(t1P zekUWP4U!dZ%{+(ZMQg7Yw zC6&ChVB{x+G)oi3h7L+}PA?zr4Hmcr0wt-s!`_nz$8atZZzFMk&wgKKDk@iWw8}=5 zS0g3;isMTO8ky!@yDt;8Wf?5R%;cS|2#OcU^~Mg5l_(w^w#!C^Ykl;o8P7B9rtTxV zH8RFgqD=;`7vD91!PJ#)mj#u?)ElNB?drM}=(qi=j*>Q=v=L7Ra_0y9e?of<#CnSc z93UX7i2omG&;CCZwf}!(l7ppdjUgZNTD zvw1uE{HyaXGIXQ{f3Lqa2(TtE>DSl&WA>cSn2K|-ZqDc7RyXweV6(K8QAvW5^QEy8 znm6xHsGTsMft`zI=n>Z+nlHg^?Qu%xI^r3|92a+rsPL4*uIIH%jx4U3g1K3^1^l<^ ztXbSomx36yqq@D>@716K4=acPMj5h>7j>eZ5p}Crh^_+o26sm|F#4HfD9DNtUjyrJ zK+|79(_2tc5n4=gZcml-4!BkrUYrj7D3^-g`?ckhmszoZL93J6V$DoQn~jAWvPBTb z9;Jof;fvHl{HZFRIT~HoXA4*T`e7S!bMYiZd1JYdb+}>yT7n^&_kYj$NO}K3doI|A z6D2@Ln}=ok&g}<#Hf07H?9GD zO`fb}NAnYASaTQLf1thgf1o{@{Fggc;dv=)0p`CQK`pbeuQjl1msy;F4CEM}(f*&okHn;osPx zWADD783It~X8mYnW;0A$s9)8&&;UGjCIA6TRTRo_pd4)|3|d*l?|8@_(YV;SF2lGJ ze4YzXLVWf4 z9fAutb0FH(SJ`G9Q5Fo1CJA$BQrd{0=muw7pr3*yIU?wN=}*TZpG)_$UEJNA!u98i z7#LXLNkpM|#($Bv|IX|7a!Qy{Tk=9nWUxoNT|{LjrS}9mVXkSRbmqGs;Fz1Y$vr-< zGuK~!v|YP;kj=tK9-{WjpT%WCe!m9RlH@)QPOE{(<5^wTY^8YwsUfY0sgpo{PGz`L zPJYO?PxiP+_9`V)!$wn34#EOYONrNESHJ>qlSBJ{;6?lO`Bhp8C|}#?+b3(fRAh@>w=NGer1w*>l(%vtx2bf5-&+ zrTU{LZqK^p!EidJE9_fu;)&%kIzi8w`Z3WL9xy-Er#~cEokrplXEbYbj1590uZRAD zKNMj3_URxHK4`mQu;lMI0vunl(o#66VIpb&x1n{ zuV!Y^em7<6_bOdmd}4bD@6YiVcjfkKTyek>>*#?~@9GqJN|+?E?f9#6_oxaNHR_WV zyytIKr+Ld`6^D#1g}!{hqkP{=M{;b)R#_szWEJyac)Jb%PkroZpDtzXBGgc-`ETRbkz5 z#x6y*gpvUCwzTuGU6=NuOC=MN^x;6$6h`qSOYB^DDG9!41|q+d8SPSXC)lxxb0=R; zHBpvUT1!b#W|f0@ojBFb&7~rlPWQJxSmmnb41Y_Dt)NeilI8d7VW3nNgcqj z6`uy+n*29TH6OX?#i>@=UCzr_&ON>_4c5Z=-REF0(0})XZf+O*-5%JvlX^RV^6)xk zPYwEWc9rovONHH-uYPO-vEm|&ML(Xpb&N{#DkUhSJ{ORtE!TS8WPAgn^lYT-n5g(r1|o8{Z~h5 zS%qStAXh^ki9u6Ez>q3x(I_1$bcD$BS}ubq7G(t`2?DZ?>8x$|vARn_Juk;KLlrk- zZ1Fe|=DRmE6DRxi8%3W!6L9oB0X>8R{CBtd()>uDo}V*n5oq(bHzysj(>WwD@@s&Z zt|uL=)Fv4@1d%nZrT;Y@InmhZvxu#G?-Tw++w;5*&8ZJoGmI%XsCC!kgHTYw)+iMI}VD^3@5B}Sw{ zWQ=!nLixlrcwe2w4ylt$74v18U9{40FH+)e7jutSxSUgx0mN+L7}q+!Z8F>pm5=8K z@+-1fv9yb=HXBjhkPoi2t_ps zixE2YLKC*1v`(ng1-Dt4azoiYX%uxUC|M&Ouj9?MnLtDFI5o^@@ zTFg#>JzC()qNU} zU+-Fw|KM-P8%MzLZ6M8~8YWZghA_zzF2k_yVV&s}j;C2lj;AUV@^zI8!#+hoHb0*5 zVS6QZk-cxMs{#dWsHT@qKmAjQ|}oae!eLPcsJier>; ze2ob~V!5>&lOlPL(F}YQzY!wbX5whQDZ42udG6HcqEWN`6S^Z%x;93|6bpf>DZI%% zyxn|M?UP*Q_Y-6`$Fr5-ge5H3UGU`& zg&;gCHm3)noc30&*$O8w+zFUj;NaPT2qH!=o@a*rPqJ|5!5M10w- z+p#({^(C`XvTCxriUSxKDMFiJ%jKaMg#s54SCjYJ+nTNZ{Z3fqI24N;IRlt2X_&p1(?osp!8_i_&kl-oOmoilT z2_x85aRnMC8IQ2fwpF(aTRTs|61Cch8Td%V?=EG7;$L6O1@kZ1jsFf+ifc9H5M3bW z6ioqSflM8!)hpEv8*lWKQg;H1%NS!~c!HiRtgl$Mt6n1$NC^ z-ATjp#dS_T#sNbD(lPvC$*ZFZw=>ucl^N$?ElProqlk1=w`!Zpze|ZLMvN*jXt7BO zaV4!qt)Rm2YF|5$j|)FnAIYv+r~adPo#6~_cHxSo8LqXS+D4zATONr{SYN+qP}n_AT4CZQHhQ z*>=^fntHDh-90fe-JL%&QjtGSW<>5>-`Q)$QN?TKQPZi-HdkWkZzqyI&FkwKYrm60 z`1reC&J<47LiuJu!U7+SkX{mV{Y~K>;WYvn+@buM7D*1&ghl4@a~mW>mDXc~l|rt& z^(DSF(kNzS?MB^4vp=tiD8isVoh1_;`a^znHEfC%kQyeS(vl+eDtuj3hMM2k7x$ku z3yrk887aBn3{V-MG2lU^3c|r?Lj%3qD?Hrz^J~yg|4&KLR{pILt9*7AH>z(a2{g{<{U-%u(>xP>hgnG@Tc@5Se2C zcwBagl1Zjh-a@Sn-P^*#yFd+Hr>>}Bf8tHki)aF=l5(udGrBn+wYniu`^T&C+ zcq$v~l)eTG@-1v=U!@fyBQ}7qpT-pQ$-xt^T%kTtCbBi^e3BZ z$>2%REqPCP)XJwL82{3~ws|59DfLC_G2c7#)mN7%u3?GzPD(iAgkhZ+2$KGG2GzugBOtbnNwr)9tg)sr;J<#5 zYi1r#T2gYmR0ZC=h5V7xP*WweJXzAiN^wf2f+uqH^w}ex{PsRUnTNv9XrLc7Gc-1T z#5#Vie++opdGO-q9;CO!9=gCmE)`z6%D=!XBf0;s&EYVa3mgUNotCB`x}xtXII^i^ z1pUf8K+b0SryZl;0H{+0-9rop2;u!EPEO4DQ`bJbd@{)E08~OlH>?rl>E$F?9%plj zVq9y`4=3dr%=dGs?#1%<%J`P#-tsl}^UBphO;{efLbeD-F%ibrqWPMp6jBsjF~i&a zIQ;jk_r|ZbwXe8bnp_%kv2Cm3?hQ$r%kdw?%NY1SiI*KY=`ZmDZC0v#HDjOky7&!* ze(WkvG+Wa4Mzh-pL0v}Vd=h_I-WRsi#)gm3(&5Btk9S%%Ne#DoE}1{KWmmqGSCbzq z7(0nR0hR$Dra4wzMWbWw$oxiKHR931C9rg~HQ1*;n9{F;vGz9IPE{PWWi(yB96QRd z-n9feUDd13?o;6RNN%n1OK%==9Y&nQo>|oWNl{z!3{a~tA5j&`pE@@VEV?w3>I}_t zFXWgU5Yq**#f;SvB0r2SB9s=BkcW!T9WN#imF)811SO8Ab^tit53p$;IY_{rwt=q| z$~M8NcoKe6+Uzc%VEqMTq8f>MmX+_5N(`(V*(sKGyWK)`kdi3r#gO?Pm9fS6`EJU< z+j4e}gy&D?dFX{NRb;zL*o(w+FdzV50o(c~MVIlr8m&$@2%I5$!fkY!9Zi>(A=8~3 zXr%oi_nM?s9mRS?d$Ovi*I?HquOld%Brq>(#vm@_sLa2>ivoLUishl2$+(T=xy^Fg zunDR^4(XS69rnP#N*~TWN;`A+Wup<;P864!>h2=i!GKw44WE)ftmv@7$fS(VuXwt3 zGG{8%FXuGL#UBP|9kp9twK)d4BxR^!;|kmT;;s|qS6?RyyXz(I#fUAa4d`qMp{u7i zKy+oZk=XnuoS54aZz76fqFKx=57_k8w0*_l9N7$srpe1T6jtfZ$ES@V{qkfL6zNZeeP}ujUj^KoOdR zgV^unb}pBl*2_fVFfvV~?Mf6>lx|9+&!i`hKpFY^9>-p%Mq6nNJk<@aC37$MhgTiQ zVl-*pi86`Q0fB!8pFkV2JTe+rKqq3|J^ZbGB3 z#yR0(E3DDU(~LSpy=xN4=%w2Z;E|5@BxlQ|nz(bxZdq8EV1Z`AIZ~nVBX@KTPD8(q zXN_@-Vf2VnEp@AXsN?>toh*q-V$oXDuE*b}S9dGQlqQWvQ}xdTJ-S7weB29HKY~?k ztmkir5RE0(#xPCepGB16SbU*sN%~f%yKixsE8WarpM(O6VW7~bwD0FDK`hI*?^-O& z*(s}HKrGCe)7@p?_!td;^_b=@Q`SY0m~Q+WIK0BR6XS~_Mt>3CZZ_$&V0;Cjw>b17 zn(>D$a1D-FVE#zG4P|c_pS=E*vwc7!&HGKSgGpoeD#jPD4kHCHvB=)zh^{cF1Ku6H z$Orpn<5DG#yc@CJ zJbz2;4E`wGv*6ri0}~VS8tfT|D|3OD;>?NQ}YuqpwK+$oj1rc$!G`lKyIz z+`$AtTKnu3W|dk=HR5aeXS^i!Z<#}os@aauL09iE`0mz?L&$dI(8-al6<6zH4uwO+WWQ8Py#Dg87O&GI;MXgpMBGM))r!bM)UCWKZ8;P=6_0q*f@D0i z1a4JBf%fBQsZjX>mV@_X-ue)twlUNSDxSrICURM(yp44tyayq))p7xfnmL5K3C+}M z)A&IwTPiGC1`pcB5UBIBU)xnNJo>C^)DiXz;|v~)<%Bl#OkE#i;|*_Ft(aKI=i|sN z0^}}#Mte8I<`BTw&%Q3x^+ZZxYGSuG$BK$5jhxE-(Ij#}em*eycJBDqxStIaQNF>hKBp7*;nH1q$ z@s2f4?e?vRs~cdJLhjAU!kMdY*D@u(vD|`=X~-BQyl`=LrL}EOtDX4P2~Ph3yWW3c zmsE-){WcM+7g_J2)r6W+UT_gFWu-~KZ?I-lOV&eFsuXzzm-Y-34@Ku&;?=b7r(V0E zAR2y`^cU4)=`ZZcZY#zt_9K*(hZjq9-TtqzD=|$MkLhToKhtQOZ_G$;GH`$*4Ot&8s6@8yNaKZ+*7g*zTA@Jc-?JZZ* zNB#}awWaultyT#90Lobm@(`z&2n_9C4sD774MUHVZ=B6{ zzRxf0N)mJOJkDE)sv`DV+O3OMPaWp|0?x2VmRA-!u#Qm_iE^?C1Z8%@YLd!*#4}oS$E+*8P-vX%dWN9Z^4>r7%p5i z&w-{Vu?`X=EQ7h1+kzP*NrU!D*zJ3$_}DM#egE?{;56=dOTWlH)*f413kvrB zHhCjQCSLIFJ9D-FQ;J(tKA+(5E(WT`q*WQdoh#rP_^j|z3gRfFdlq|MLqq!9%)7?E z@(g@ykB~AYuO*TW>br@o!wa128u$2EFF!3p%Ke^pT~e0Z;wU#p zl^!vlA3xyLDxoi~@g>oSS&mDnqb#GR1$|?gg;`uXi_H5O+^1winm2&yGzE|-X2Dt9 z6qbi7xON=Q$|7<^IOyq4$)7Z?*9sRw3Ei3+fZ#dZ(A67JUYkT-&oB9ExYu%jB0`>` zqG(ED==CUt&*WY7ChudhKQyCn>nTcWP0BiXvruG6p+7&t#d@|f##HIKrkC1PB?^|3 zE&IvrJR0Tu=V~?wDxP!EopRZQtG$~=S z7Xz*Y`1S|re@0)_uZl>kFaQ9WjQ>06i=B=Af9zLT(X#z@XF&Rf=@Uw;%7WDlyUR2Q z%cvj*fnX;*3Sdau*>r*=4rU)Uvjv_Yd%sKa~*;5IGhHa9buIB9*RR^Gu`Tny!Ixq3a~hqg-W-FU~vc}e~+n=rm@ z>qaLhWD71wC6o%d(w`O{Ue1}Z7D~1(j=yj-X47u8r|ppRJ%Vj{^VdqJe9I##tqSNC zOZ`T&>>F?Ot6lB3`tB)Ob$%5rAM7PYVcnj{E^A$DGj7s?l~bFjNEGTjYGR4w*`5_| zZc;IAVXS|VGXB;x1Rl6@(z&|+`huUgi{vO_pVX}q;dk_uk-rC1MYD}{roKo5njIJx zV7<0NHNS`Z6lBWbd&`X~sCP2x02!ReLOo`bF}~r&7tg8r~lD( z-5E4hU7}wBD8hilGr?opj`8~jt@oBf_K1y>z^?}G3vX{S8xO&Tw+b?acJFnVkYXQO zjjAr42APdDrO^R^C7;P=AP3JRCZD~robThBJLpcncJBA9^4T3*4F9BtH8KvP%Z=DAV0S&`+0$@UmI)ds=mj@a-=Pm&yay2Gck5cX;f9zO|3UEp=u1cN42I=A4YYfQ65oz`f zC!2Cc%RissW$vqq-(s7hZB5-)gGWTVMpYeaZP#1a>&FT1HnECTCSk$B*ccP)=uR5)?1V--2x+z%1!=OK+iWM(5bE zfd>7L-c?!d{I;?SB1{#Jq?ZDiJQ5g3E%1aDgr>t)2f%sD?9?%nljI4)U3)8!7ZW>% zs}J7UT`pdi=kn$Cu4FF(Y|UN6m+blRV}P?uIY4X#MgLe_2#6%!tveqNc?+%;YLq#= z3D6&C#SA+Ezd9o!gg=q!y2Lxr%{MT;A7K31q?=ccd;!@um+80nDvj(U%IMtk} zuLsFe`zpyCDKEJp#}>SmS5x$8`hQi51RkHI^hT++Lv%bAL6yU(QJP(E2KFBli?7!B2Y*UsbbvIsKF+|7v6P3HjK(EiS1$ zM8WE-`GJ=46Z^Q2-8u=C=(4NNX3@V7;NEWjRlIfzp^#asTdUn%qdm6n%T2MAYN43f zA(JJQa#s8S!Y;NEP^J*EeCz!vI>&NP(zaCK|_BDY6u6n0%^-oBITXV1?$sx(kZAuUwc*cbp~kSavC z%eXx9H-B|9Dj35K;a$D=ja9lA=U@-!JhBfQG1oHjPGYc8XbP|BP**A_G61bVW$*;w zKO_!a5izI(8XI0=+v;N^?~aRgIW*9m=!+&1Kf&W3LB{W3+sSE!I4(n%BxjIe3wF1r z!$VhUM%rc+GwvRY?%;Guzj6@lKI4bkKxiDEcOK2L%Y`9N`f zf1#%$%-a-gl_yM?akya3LTxAqFjzQl1RG{>3FiF{P$S_~fU}NQSvT(uf{avikip1Y zGO+>Xibm``nf)!qmNt9?Li>-QHZ>s}cB`6DE8SWvT1Snj-KA1vOZt6>bQ#L`y->6O zkCg-hMW@M++%&f)BiB%mzLObV1LCcyzeuef+QTz1886=ca{NKCyX_2dq0)h>Qr&_* z43o7Vx<{fIlqVCiPb%=m_T#9q1rM3?lpFZ8i~@NTEkhFrN8a30YIj{ zb?@GyubbNoU$XvdsEzKm*FwnLxY-jPUlPdf4&2m;Q6XQA-Zk9X$*6aOgU8Z?7wCwm zR&>yv2dDo}M(8mC=%t`G@vp@t-Xxt2vbzk>5lwUA#^6B3ffk}8un?=Ib3f)jlhHt2 z%$f~?9R~g|(yRVQv>XN_&E+njEDg8#>O-u|6e?A84M)d(4`drh1kkjbcWI`-l}IU^yC^JVRq?)2(nN~F~wt^C@=rT6gNO#|=GH!fekrgUkCMQk@~tvBB7 zIlZ4{%6=CbRV@jenLS!%3-&+D3Sl)`99J&a^IN1hh~5elCQLs3n45ARa}EAWWOfIu z?!MA~Yo~KwM$2Q@m5s#;oWc0z7I%XcUv%`LJ?ZN)B=J#Lw#!xeWB;O zrbv9+tEp|FRD?&ae?ZJCU1?jw2qR=ANTA8lMF=9GcY|FiTX;Y3uxAllkS`z=2R^%+ zo>!XH$sZ>AWwCIjN2XooV$Vu+2&wnWmglT_1)PZ((T-nzX7mcvT79&e{LjXTrupzv#Nku^Ewq0I zFSIAkkAE79Q=%0~a+ZP+&FSwMLB!#KTdP398~OAWO$M`SS=b;; zOvR1c=8DAMpO;4ATBClsjdeLzHT5k>F=y9srY;BqSSJ#91=(fZa+^*iGUm>laf1`M zlx)hD(=#3Je#6odZfuUaHNLe~Km+?>csR$W$jI;U!3@*v%Vv5ycbbqMF=@d@8CJ8q z6~ExR!eIMNbnj@;%CmXHqP8)G{KVS`Hadnb;qSZx*0qw0lM4kl2}6{|EcJ8Ek9kO^Jst{}G#7HgHWO5R5IxhRfllZKFfrD~>Fw ziV&6nIw2!c!)z1hzJFeLSTTASIHwmBX$vy^rCnvei+Hj=ZoaxjW4#YclTGx#cZoNk zlR1Tk0gH~|=>u8~&+Fbg#4f^iYS+hs=A&o;lo+CFic}5E51gX^#W}QfVHL3E`!Z+VdMizObO*TaB}@CNjDzYb{lq6vp=U^tf_WH?mo*^6EX5Aq8(!x@eKR6O{UfH{I0P(YrIWh_NT#U6&dxY;ahGf^ATX~KCBIV z+`xY(L(3o8RVY)dB4Xvj>C+HbK6;MRZ@^+|>e3@!8FR~vBq@=qR$*t{8E<|T5=d*Q z3D4LhMVlc6AuA7&2OO5qM{TP@+`!*QMO;7@IVl`6nhlWG^uSXu9l(aNB8U0N6p>J1 zw>r8kbd#CFNtbjhMh%1#B<%}XjiVr@Tt3}-Tl`g{%6G#Eo_&?AWyOF?h6Qm6jj+{_ z0ab+*+Nh5p40X^JmhHA`UbRl4bVx6{#xFjNIl5)sb{}S!8Cn!TNJ)1L$s~F*V2E8w z2L=Mj8#u4279Y5y0<~NrpV+`ERY(*>BMq7>c4GN;TAHim;%K$c2<_^0D-ZW3E+}7d zBenqxEhOA!L&PAB zwGwz8;~zvu<3D54F;!D&5*ZxXwb*!lJC-r^HY0T-N)F!}B7-8B*fogg9*buJ-KGm3rgj2#!z zW2ChklFdA|WW=sKh$gHB>oYZ*-(^G_haxa+cG6iWLZBAbAxWcRMhVF-u`3fZ5;;%d z>htQ!FI99TiL9VAYJTq$9dDE7`%2h4ut%5?WZ?mHOoHCwWeQ<{B%SeE-gw0u_0mF< zT@Gulk)`o(wS)!2*wh*X@Rw{%Poys{%`P)7Gq2x*(%2Q(ekg0yLPP*9VGDLSfL3Bl zF6jU7OBv^!j|7YLjeJ|B?{iy1?&VdL0@h9b@~%p*bYVvf8L`1%cH zNTEO>yflRLba?eFiGV;7)MGVj%GicGM@MyYzMpzavdH6sC_TlFZR-XmO+W=);!+9s zg!qe(57CY08hh{_uba-}>WBN@GJ5{*o*PR&c6E+gE+vR?Pa?d#q|XRI;#Ttwf&8Q% zk>A2K5Yol{0Q4D}%8A{t)d2RWCBrLLS7x-fR-k@i6NALStlH}V+c@KetuZ;C^%0wM z2J`=DG2!H3#9*|_^WAUTEkvKNnA{i53fWt?a?vHVZt#Wcp73n?1h@EvWx>R2S&M4YNs!u0un+!KI>O8 zpj0A-j>g!XaSN_LwcI$mmB_Zs)~j(BWs6pXMGS3#WrTM?y1j)0gB0!e6XsBqpnJzi z)BM}pcxqK;n)$mD{}}8lg5|#F88nP6_S1Oj+?$q;3jK8@KOi1=E9`t`8J^)76Kkl@ zoN@M} z0#tbv{oW}!{JfePrVSP%jOm>k(9=Gny?~ z8ef9r*=@k1=2+Bqy8c1xZdjU{}1aG_6Yk?!_yFr3l%2ZXv zYTym2S`P=a&WMr}xH$!AF@YmqL{@&sazTA17Xm~k%bt^;xv4WzZpt-~(}YN0*uB=x z0C~u|db9szqB2=QE)SEFsucKV1Lfq6%%(uDEwL-tPtx~=_>H9|kl11IOMi6Oab_ZV z%n|yz;@_W+`W?*0(A?h-O+K&T`O%>!`>OPNR|YVvYS7TmIlIHizYhja;awB*;Jea; za>%A4y)tiU@diVUd6x@}ZA$}6C6M-7+ORC`DF$9eDV8b~N8!}|(KkwnGHFYWFQ+h7 z`Kk$bGc0_OIvc@I5a2**FFR9z6eZd=#Rw%3&r+Nu;ADi+Y5KUDY(hoNTIg{l54x`9 z8QInUJfD2iZ1PWsxbVgXF!|M-gSz`Zvhpf>AA~RLUljcxNuGIqi)PY3fm`*z}sb$&Yt<+szIP{44bAJ!a$n99NO8z3cCc!y)oZ;K} zl5Z8I*q%*A0P)@!aHPEj{>l5ESQiW==G-Q;U-jA`!7al3@E zC$Bk4Y(ySGYnx_YJz-q*F)e~|gZAv5=XsnePGMfKxhQ8!G-uSanjnL(Leh?SG>^*J zeN3*kSJt8uwa4VrxAiaZrp8lZ#*A0zN1ZIlCFXI{newNBiD$PsynUz0K+Sk|dMD*S zr}0awjW_%=QEv6XkuW7Wx)E9y?(3vBod6l%voGiMjZo@#lH+mUdxe_I-7!EIh<)~w z^Ms=`Z}pb54KTj!YAfRJGLTo6dKkbpVxNG=$jb7O7@4C^0qcJ*sQ;zC71+{9i*xZh zvLh(iMNdBh_3q)3(nBE<&AM?@W;W$y#YKO#Yo&9*qaGJ% zt!6_z!4KqvGE|?9*kmpVn}L{EXzlpoqA9d$53byf`yfMO6P}ic#OUaZVZt^XhMjOw zC|gqzhAasD8xKnk*fVpZH3JC4blkuFO#=RM_wM-btJ{Q0afgU2KF%BV`aFpb(OkSS z`J{_vVm#|H9`R(oH*N0upw%qrw&u;ilm@7zT_jQiYcs76XO<3zM;$#vVMlDQqV6YS!3@#A>id-dhyZ1Bj& z7g~9SoWvqr5afif+|mB^C1`pd-oHX;<@>Xlg6oHSs}-loJho%&m=|sy*yz@Hj7Q&6 zXh)CA$>T(3O)9_hSev$O8gu>YPVHa4H8viL%|uLzBeVTyBCD};A3It^+=J)7MyI9? z-`e6be;j;?5f4qaZN*8G28v>A?`W1qzO*{HE)2mu&;yky18g{~)&e1`l$&DE1m^Fm z={1Su`rDrlgCRe+Ovt#Wr!4*uS*9q~hp)+p|0P9y;G;f^_@)3 zo&Se8@oTM1yQ+26?;W0z)RQ?GF6Zf)Sy8MCxB*D6_4asXOC%GY&yMJy6AX zP{(9HfWX}i_5rFO{Dey<&9%jw@x1HZuA2>ZlmK{MzRR4VC)9Ly3rulMr0~$nCZFLMBMt#q|yGTdd;H8oEq$EN4TLr%Pe1&^DE9GnP6?<JS58e~h9%>lvCo-~-G0R`~h0V3WU=Ck7_p{q@~c(aL2JTEtM3(lDCCYvi6bgnpg zUzhg(*n8A*zsN;@Szs*KQm%LO_EC|codFeGTd)~xVI5l_cds4qvh7>7#mzSBHt(_4 z9hv+hb^zm-kS__CR6Jymn;(>xt0%err-lI`Oyn;(Hc)!RcBw z4nvUNdr%UM<}bMtqy!J;xe)R_mt2s(fr0m5$!yx_l+IGo;u485k15Og0j5NxXa1RB zP|6tg@mHqX<%q`wFD6h8M`3Z#`g=Uary@MRjnQ*B4u28kH?PoXErwdQlmy0e|2vBfPm&jIlmVRdDGU~$ zzaUP0VVq#J|GCzF%5QwSY}Dy~G*G&8JnJd+kfyO~QQObUKnE*U?ROCwA{M59SNvMr z`))w!oR2?-Z2|}yCZpAmrd!kHAgD2OXR-Bfwseijfxt+-gO$}1w-4t|FVvPpcEx|k zu#6*ZO}ronzG81dg}P3NZs{HmYo@*L?5tQuttpd@Cz-bzmuZFywtYkzSQs-=+%GqQ zCx^7L>`#@Ks}GE9))3Xi+4cJ;|G+)9MaNp;4I?p?9e?axmYxKsy0lF?1L}Y%f*1+V z*2!sdP>0dTm-Ei9y?$S6f?e*4RYlZ-0f(pUE|f6~j%j9gMTGx? z%5d=4kBE^u9| z$e9^oIz_KbaDc56JA~%a1TJB|s3#{i^<@)xcTbDXI>jzCv)+-kAb?B<=ub4y9(fNW z8>t?Uuxxkl*P?(yG6)fQy9EhBAKIH|5+Kr8Q&P=)FQCv$!0(>ZOn`4tCg3)pt_aRiw1?jQs3V z(&6Su9t%StlFlkmPmb(k(~uK;SS~QXcHe!Mlo>-~6|f)s*|U|O`V$&=D{B~*Z`L(f z%OA0pJl!#uFtOT{NIL5qE=I??152m{??a2w(S4S0KyhrAgG~h!ENJ|UUca3oFyAU* zyv(A6n&T2-(GjovMBt(*|9h*HgErD>%V?+=4~i6f*43&M3!1N~=j+qE8ou{{@DGue z<_0!LDa8^5)r+a{^;gAC?c!ZCY$FtrDG+Wns%j~}zDyn+iE z9tXq>YWs#&hs$-^t^Ceph;B3pUW%oLIUqoYK-os5W~z3HGD@IUlLPqEv64m}*s*c2 z57I127r1x>VrrcAkAH-=|Hqcnj>Je)_wA7jMpGVTMiP_w~?D9TOyu!KYuD4TxlD^&@(%5DrbyO$OLtDcXxv z8dUvLAIqWxQ!QycM$J36QBxy!=q<^ieZU9c@`3*SFdYm-^e69&(Y8VU;|n=qFdGi; zyY_$ZKXTZ&Z}kbUZ`+RP%mrAWByr3=F%14KPc}TXMwtSI`>~(O!TeQ+$bTwO6079Q z)ylNJJR$M72BMNJFh>gy6(J+RLrqMNcTxGx9C%P9427f=5B-oS_gAs#4*?SorIQe( zJWgzu9~;2G77MYvyfVd?^6^Z5i-Od}MSVz*cUQ3(2tgAMO_j0mR2erH`d0k)B}wRE z??5S_ANXgO0OFzA3;cQ%%I%}pg-S>-Q(kJQbu%+hWE<6^v&em#1>_gweMqWMWMV&3 z8x~mjUb=|8Tk)lDyErU(6I-#~1C9DW>ewBQx|2ljuSymfC(^quot`(*vH3BLc!+Q-?{#l_O@ zf3APDEuFVGkp6XlpjJ8>Q~mf3 zfJGJ=nrFix<6HY0ROqu~dvM^vZP2HdT_#;DQ|rqnkr)ViCd-%6>tExi@FxeO4>swj zvvY&fr(TX+JQP2CH&)4~UV6RV*t5O3HEC+@yRY07X0JWFz1?sF>#i|BQ ztgoQ*jJ(FYwE|XyRriLe^LqgP{mw;4?i2hw@PZZ3mhS;k4p=DRBT01OyZs0j9OsW$ zj>a|pI(4-lbE=;c6$gpm&6AnBbUy|B_i+TgVG3@HKjrZJX}$|Z7DFUxuU6}pZA+;* zUBA}KB4y(~x>-fM=-5k>{Qz{~g&25Q@e8tLdC;gNg0{#nR zP!oB#d)CKf%0*pxP4!t zWY}kD)!qOC{$={*TV`&2SXXDrERx9xkvUyrNsz>%~NH}Ayw z&1Xo=D0oGmkb{CRg^dRLPh(BLN*fCP{mHNgN=3Hhx%9^06I%*?)sMpOI;X+k(oCj9 z9$9B^j>ctC@;rg}MN{$vtKTqhW0SKrF9?Tw2hzU#L(Mbs_-`Z$jl@Ubd?bo&ZKL!l zkLFBjY0^#O z_*7eQ=01GQX2h{V$HQJCT=>p@Bt8}Y5ts8X+&A_#`v+ZQZC<0AQs8~?OQh(oe;Ezy z&ze{M>(8rv-FmOy{N1s2E~i($o-62Wcqw!9)LBiV;ugPe?4`S1_u ztm6H0U|%6zTN04+WD5iKL>x?^=Ouw)8?t&wh=_w zo^5vO+{asOWjD_Nm){OYTvgX><@30Z>Q8Bf^}W>n>rme4l9I_WLZWn`~-j57Ih!7T0t za=6zzzw3pPymzr&q%f~jc^TaEU_tJayzZ4Pg6qYr1~*f}3gAOs-|kpJn4TuT^5#QJjnE|2THS zz3ln3+##Iv%(;f?UhXWD!|A+ZRj#yf~L*+3>(;_S>7A zzs_yyN9`CsV|OB2tO@@o;Wlk1Rto*y0I(}26o$SLD*M1rWS_lV^uXayUCgZXfs%TWh_o6zwNLuF>meD zSlyp!wHnF~^KB@gWv+}$z~+G#ghI*qp%;Wg+&TE@;xq@2;ANhev&EcX0#gp_00%KP zLUDKqY49E$q5yCl`;i_&NO+j6HFH}RBBq#OWHFp7C;dz0S^S2xGny z--Pvfh6?amf;7G#N_mj%w~uXZcKkSgd>b!Z`;LBF;R&JQxv&{wK`;m~EL_!qYi6zf z+fz{jWR2f9D!AANAj6Pgj3>-r(X0)SGv^jKrvZzOP%~%kcMypP066|_76??^<6%AF z1ViEuUu1`E{wM`JE2mHuD6_}1nCr$e;2;`Cyv|SuuJN$WU-{4TfsB5l-+|GJOE2^$ zECgLjO}5W76riJAYpV9zx~K6UW>3AIZPw>mU|POQ0gfyGu@(JCugJb)A;rsQJ*YGp zK8|mOkSVaP*g<3es_2$JrVHjQKJn#U1-^gO@5=huQb_Cl39g^)t{$#`RPVxY2Lwx? zDk`tpqD*^wxb}koW?+GAU^`7|{E-#axuyC&OEnlS}uBkqOc({Lv3!1;`Kv`vk zJK`YC78QMKhTJWx;x1K?Df;l`y<5>|JJF+W&B@E8?Pu;jOUdLrqVjuWJhAYJy@Vp$ zV!u|&WSON&J)}!IVV1Cjq7R?a3x$8>+ouc76}#4)*2_riNw&=#458@XW)s=>p5=ir zj+Va4WzuoQdfC7KMg8f`cE9`&9Zly~M`QUvtbqP&*x>&lqs0&Whm0n2`yQp}{41j+ zD>(xGr;L`2SMJr4?Xg26do}L(=gs>HEPlUHuTOU_TE_&E=?V)kzk@WSDQ$Mh>MgAP zdcx-AEXTKO^VHGZ^izH;eAHNLu0CCV52b)cQB96*`)`HPGF}N$xc97S$&rUQk4xfK zck9kj-Ep||4yEX(;UHIR_%%#`?@n z;_u|&Cgd%MTm?AQ&3}fb;)qApcht%{Rl=d6NSnpzo2IWhn!D@tmcV zG33dK{Z!o{n9)2K`9t&&BF)xRX}*nCoJtWre+&$);P81z+*qhqe2qWaOSt?l#X$|F;+76w*O`r`5ZVtX z_=okDa!gl3=s>`b272B6M*Hv@m81po|mh}I9%-ws=E)t`;oiW}9B3SwhyClN3MDxFk{ z0mqR~4m`tTS#6>YPs_J0j98Dtx6PFA#741*k~#`l3G*bNwsj99&Wu-&-TxSq5d5#- zjUyjY?Mx&V!~Xb;rRt7YkRNy(EQ082gUJCDurt05aBr}b+yob}me63l!2uQyK{*P5 z3`7K*a$dwa>^JBbl?|Ybl5dBo29gk~-n zx*G}04ktSv7vtrh9xFB0Un?LI>1z)|Jo!ToB3^6RroCk zfLQnD!MetD@S~g=RdEq)k~2biK`F&G!e`UWZmG`xalEY0;+QEyK<{k!CTq8Q0a-d_ zwjuyggBq$q$1YUN^mqPVsuKWKch;VpG{!j8c!%2Kr@#@Sl{j3Fk- z7B>^)PX*{~Y7#s5@tmzx)-nr!cfLRO6C^jl_{)b1F6a^k#2miQVecb8mEZg-`Vtyy zTA!Q6Emp!w#?!d%brsa+TEbse?5+k7l*E2?ZcwNIwE;}Zk55iEr7$Lj5akG3RM6T2 zFdk$tKp+ep1nvTebO)#g%ELox07pa=i&#sihse3mM*a?1(GUQt?1M_tU@*W1d6YB3 z{Z7z%#K<!}*g!Ve z{FR5R)Dztn0)iJSlmNoS8~A3kGXRFIfKhly8s4&g0J2Q8ae*c%b{3(IqUB5(67+bE z%-{DL*cj5{V}R5WTPxZ{u>Pt9CZLhSO$!i}u8Dll$xF5ptDxHwvftLLhu(4?TfGaJ zG93j2KtKu{O2<)?)(#wqh4Y;j!sq@$Ho&@EFe$CV*;LqC6ZCB6y0^W7Vjsw09_Z;W zBk#r3YjE!jAZ?m=IPg(X$E}b6lMpAI0AKtgML+V*hb+MV!`V9p>C&y+x?_}W+qP{R zqiU3G+qP}nwr$(CZJzq)xmbJ7So>nni!UN0zMIS*k(sSO+1}QaehqDfa8`q0SXhma z`&bS$qr|&Pn+X0!Ye%#W;`Vj`2@3l)0FWd3>$306=+BAE3KBq02usXUfqO?u((ECX zH@B8U%Sk+Bq@~|vU$O@T5agU;pw@-XW5_{2BETBD+%|xlF`ql+%rswgeH|%Vmen9W zY>ZLwkf-5?>?KMkFd849>F*QGT#yEo99t_?43f7;2vOY!qvVlaYy8w#q9A5adB<3o zd(%ybn1eox=;J|aDl_AAMR9c&Vg{zb@KuqX@_SOTzWFrs%#j6&nAhqXY0K)Els9}*I5P%MB%P%9nXI%0ruu( z?*!}kqlLAu!l2iR(;s5HcoUJ?^9T~j0VFc^n^th0Oe>`qdaVVMX!@Jy4%M8Xwt2Pc z8)uE48b;3?@cXq-j2Xr-e-S+pHiy&)d_@h^#LeUk++c${%3W&U@q=zBoG2{{fFg80 zFVUBj3F3-DZK(&B%_hQy@)PzBtr79{w9%1NZ|+Y@fBQwJl!*npi~ukgp`HvM6M0KK zmr%hC78oHjLUsIRvoskJP%V-|$coj%YR}!baSw>f+mo|`C8lU%s_xEtq>-MziaB;z zrpwVx)sLnyq);>>H5BOXV6*ruo?0cRP?_}-1 zUUt;Oe})~a2vZGz=Yk69NME6F!2_#WF@3g=);+ez$G^Y7@Bf10kzpB+N6s%I_4isv zcbihzbD(iRuNIS<0r_^vv<&I52Sus~mDi;sEUNNzPq2`y+0dCwB3?LomrfAAtqYtp zh}{ImlvijC;#falXUF|o2$mP9ggd>3`{M^AdNK8YYHQ8einCfpa>Lbu4DKy6t z2`3|}8XUK*88AuR*p-&z5-{&*jZ{cmOh98RHdCIpOaMmbgYJ%s_6k7((!ld(Qi77c z<2GhEg_Y(0p5T4+WfG#_S{DJ@w`t7Oe`sb(7qxZrxR=CECmt;W>FPUOgRQf$_qY!e zjOUc=>K#vD!7_w3m!j^weM0f%$U!Z75btC6cC5boV%n-`sX9)>2s1>t4 zy58bld#EWsWm&mI&V2zi9uAVBXJ}whQ&pfNFPx_#LZ+2rT)n>c#T{aQoMc8PmoUdi1?=>_JExg zhc;r-Q{li&&am+U6b8%GlRoGFjJw+`1FI=x_+spCZ0K`VV z1q!tp?-fhq6?;!(m5sGx&9A9k=ZrWOGE=}vE?TW*)?z2`6ZLjhJ)wdad-=i;Q(LiL zGUZqd{%lTcKzn53$a%gvs#S2SzXJA4EW;X*VP^4he;ellC}8EKA%U~IBQmzcsT>6V*@QD`412MFD1+X5gx=+#nMe*kTETOs^FRPl?JYrk*(9l@&72ldD3@ z;7i9dilr|qv`C#sYC-p&*LSg^zxu;J7(m>pem_7(93Z7g6;7p6hhhx^Fuq!)2Tcf* z=hI_J%~08vvZ#hI!Tb&upoyR`QD%>V$z2);a&+S`$(p1X_*H;yXUeQAk@B^2doorK z5y@IR9RJ2gymA5%rxaCVDqgOUhQd@Bft$KO5y4SD*@_oY+qXjp2Mt3-U*PfUUM?X+ z&=IDeiebFWY3q^G0I5OlD=?@xcd)SKIzj{pD^oFWqb3N7TMkW-* zX+GVqGd)PfP*ocI1s%{-E&kRgW~cw#B{rE)O>hhU8ZgMZ(T04$iFu8IDkk>-+gm3QxYW;C{yS2u>({}S|N_aYJg+Tdkfkd_* zM!)a^C#$Yl+clfIg_e?Bjuso7Fi@2fprzHiE2hfc_bl}!Q&LRd&% zXvo@$r0GK;n(XtUm6)nDEewJ05+4;jxTEbOqQHOyOe*!jmY|{r{v_Yn&PN)*HVdno z5uTw9d^*1{LpxSpSiKI2RzoX2FJQW6vS1F0!)q4?>?L|SaK;fF)A{0lGLi+H`b2B+ zfB+P_yUAz$m)gN9>)XTu%GAO#Z4IjH z)e)g&)|U5zGo1Thf*A+9z1@gR>fTu}iKbPz^xSu{EYjyfCd$#KnmwOrUIWee^&v|K zt(F+wbLK!wA6Z(L?m^tOI%>g43ctcZg!#S)ly7OQ^Hm+#e&9f*eU>i1fW+sBu+Ipc^f-j*A8REu6^6Jo(;warBwS#8Gk`F6t_ z==sc|E6%BBjZNqDo3)FZfbX@=iEkxv9a+Ju?{8aqxFQN2HCLN^?r?$Svd2H#so#O& zeNNBP!oDOoofXmkozFUd$DrBX~c^JEm z!A&F^vvMwy*}YZ4OX?ou5ag`r?htHr=KiXep~}8_SKcakK25_+b1?6-GAughFdnnr zSX{JxEwtmg}TIEwV2H}Z zbtlC|ABQIc1$>&RO7LWHYQ<%O204y)x;|Wt9k0(;z?qu>#HN2^BJ);Vdq6(QcCJAZzQtuPe9FnQ(pSOX(2RU*$4(rj(0ND zE5Uta1a#q}@e=DV8yb~g+E3I)&Q(~%Utoga za^CC^5%Rek0Bua~L^9Jn7D63p9NNhM%m&&#?3~%GE@IP#f65gd8WpaydF_1AJ%kms zvXg5Nud`3jC59Oy;gEz2Bi+&aLXRXum~$#lsj`kzZ=r&$yVfB9uW0XbcI*Mji!&4W z?i_*wS##x_BH6~uwHTm(Ot;)bw8XU^#@AT*tkfdtk7@ue#Ckj|W8@fU)N-4u^S_#j z*qETdjpK!VS~R^Cj3wR*E5*JZC;#Cdyi)^ps{4y2Rl-ZsP^aA|y_0eiUOt*q!)#Zm zycG=jYR^9xICkjQG42)no0^78TVv+1kE0hjlBS|lbLPCSY~658Gf5R|Iv;rQIg3@y z^z9q+-^DoDyoBTR53|(%ubAb(Dr5hLSt|V>%<>=WA5xgx|Akos{UqjeS{oZHPt607 zKQTYgY{2zxiXZDTdq}T18Z?1AOnE6B)|Y6mU#;KPKBXwxZ%|t$yRu!$C&(;q>=pi5 z$S5EP7M(IEJy7AsRUegBUVS>{y&G>+jId}Sn2+yU9cWGIpnmdx0pXtg6^y(c_IUKE z4`_*v^s2KqJeYTSRijafA{Rw_;H8@-r3$JJjX%`&FY8}lP?c^>S{XZKUjgjslHucz z^)KMN1&V;>!dPF@rC{Iu3l3zIY-!})y#GID^P1;jiwb_ee`ox7|Nh^h@2h>==Obf8pl3 z*6`S`1biW^wWKjiKcoIiUv+NsyoHW07R}Of*ou7tc4;<11`$VkYt2isEk0S*Gb+_r zuay!+1;g*Frvm#M;J|S(B>83cdYCrR_p^x?LITrEi-EiwA`N^RpV52?zzLG&*Y|+E zAu)--DP2cf5H37|R(;()Bek*0gTr8x%w{o)@#PN}-0l^MalySd*@;Veb6DJPK2`gb zCY}=GDW#bdVUvCF1g;u9yCg1iY+#VG?;FTIi(Y!Ofj9tf7tNNYK0lU1(kBELU|#pa zbShcgQLJfN!Y2vsaWm_qhPCyd2#*Baf~cUb^6%(wri zRcxCt-M@CMmtfZfyxUh^A}pPQ9txB}&mjKO2^?h0+JrAjw+??OfQ@b;Vm1Wm13gI{ z0U#dFL%XpjJQTDh8JjN7@?@^afA_9U9VZo9o&j@u!-_6G{qJ#Wf;){87cS?m?6-TC zx$@%u1`i8K!zz-+MkWrjqGBWeOw&TD;q;&JIg;k45py*h<+lpqeWFWcYqh_14DVp% z{^x_o3VerTxws+*r$WXmagLsh51zCxCXjAQ>gj)fz&x}wO!@S}3zO~k}^u&5n;Uf~Uk+(sJI+2Dc2!}uJKX7vB z<^e~9{*wD|H zK!=bO((eyWajE^R#s=Rq2aslot+}Olok5g>!z2)ar-yuQ94Q`X8{a-MLn9n6KV{sX z+csst;gi*R_H!<;dQ#wa$ydC$uBH)R>CyBlH(7rrHKe)`qD*gzo<-zY4VT_t4E<aUkHe(%Cg=^e=(|C!_3zYc=0CbH0*$`Rl= zm^*IYb(9GJY)TK&HHXicBa&J*Z&)e9IJvojvX&jaXOgj)*SWT&39BA4a#lk;HoCm@ z!}&f#sulPxjEnN=MPYZYU~252js3(PyR)yEm6JisGYdyGsaR5=tZ7xMkWK`|l@yWz zPAGn%d-j}QDLikHhooc-H-QXBu?#U4|!psz|_m$eVVuL-*TO(sdN~ zgyxoP2LelnNX_3y z>x|d3mFRroHI%rpr;1lgb!#yRWwX*Pv|cqy=Gj++3B8t294Wk#AC5SipaU^HGcPe3 z$JH9MEyrs>+=>#zt0=D2@Z0|0F$JA;+BFE}7c*VKi<&ddjmaHg*yFZ#X)X3+c%BpD zVC2tAO-clZhAr+50_Mkg@48{nFB50Xoe*Q(s!(W~S141OO?-|&U?1Hy^1OxwuL;pF z)^gNB%v#Z`Rnp)JEEC^)p>wWh%7&s=5VTp^lqp924UOcP%So!b$scOb8f@{9f5l8m zvd3noqWmV-46Y%cKI@(*4`!sqDfn(W(K-muON{4*gZJ{LjCFq9dAVvwnBI#qmOoNm zxTS)~;_`73OQkunaPo;HO|ZBdNp@ts@y)7DGUVJ3DvkrC1bWoDWmg2@;5BO+WSZ{u z(lyME?#pE%J@TZ~6gVO>GcJVKIgOh9XF20?ZFXt zih`EWkOR&lw?N6)$#9a8_tUlYmv-i8K*!z&9&AH*?Wkuig30QFC|@1x41CpzqYv`* z;)2qDT6Aq>ht*mF;`(XV+>~&vXT9mz@|BdE!+gDYUa%s*}f2L!o}12hh6QaFtxt;_Lohk=rp%pP)7Rh(Y=T65KSJh!HKdU29-MU7trq!r`!WiY)N6_R9=T#sF=6pSgDhbn+n= zv$FbBBdNM(lAO7=i;2CKn32GNcmV{|I2njh4KW__g+qbOcmZyj1y1sXV}Z|jfn<%7 zIx;dWQ17%wf`-%~?R(vn^7sMVmYLvq0T@&}83^junY)6SFvv<7h*pgeZt?;<{0;a0 zLNNE|%jofu{PVrdX3^p8`S7tyur9h>v^L!}RzSq{ggy?(W6d^lv1(iE3@|gKXXOtw zG_o^mip$>0I4>pAkzs9R@|PQ&3cFSNhq?J7ZDfjDABoCb4yRrHY0)+o;)vpCB1cq~ zVcH0xcdLrjBHg1G{b$$>FpL%)99=9|+neowSCd*6`TK}J8fYK;zd^hI(m?+U+HF*Q zx87hu_}cUap8tr;vziRX(b&-Z-Mxp=xUb_(8>kE9r<`{|z0^J=A+rSi(T&TT4=Sp+ z@kC<`B*#M73Tq9%gy@ zPC_oNM7`W>I0vBArjnW-eW86Zj#KXq3L3HLz13{}?EIv9bvCT{xqh)@y3P7z38(_B z@(3i@O*abIIW1I@hjBD1c;}*!&u@|a6#hwye9VjleBru%2IW+i3x}xc9ovS34Sb`f z7&6lZqBo})qOued79(&;)CfS1c1_j;A>ga*fJCqcE^`T&YjK$}G6jPiB*^)PDwYVV zt&j7s!Hygmb6!kcy}p2lZri;ud)G)UZQY}ASX6#3pmAYSekyW}{{+O^?4(2sq#6WT zge-bfJ!QimK?ocmn!uINAV1R(0=WQ3s#t=VTSz2F9vLTsHTSP!t>%!TV&l=#LFWDT zzOUAs!B>l{3D{}c3s$Rd5Xl__H+TKus3%^*bS!Z814xHhA` zap9x2C>#Lww;}>T21)km=>|J48I%M_*)5?Vwp`s#9Uif^V2V=(d?fR($hS?cT90?M z>DP~O)v)@}CM`b01ez9?W80-(>8Ok*nL8PpFRWX-S+AsrK#LRs9)`1&Q!73!Cmi$9 zp9X(~{v>q-2o_GigMywPUTO|ru{v?q);2a~f94cA(}gq>sQTTD9@>2IRh5eTO};KJ zu3vp!Y;QZ#yqVX{0>wXK*Je`*(3PL+gVFO16hMHo<8X$8Y>jG5xZ>w2Gr&h3T9L~Rl9H*miD4O4KCP0*a_vdrHr)(TL*zGC0r$o&*vWh(m?Zgcv^9k7GiA zIm>AVj`Cxb3%|{ft=UuXzIQU-)j{KJ-U)(0lw^CH)w5%=g+L5vE2gW>S$IYRB3T0R z?>X_&flEm_8`NsWTVbR7`5Q<{S5G>`{&y7-TtzfKHAUOfZk}hZt^DF6Au9A5fT)}e z_Xr3X8!n9DzEx)n84w-}$VtID8w~{ijFFE3+8jP$RSiA_dmOZ-57lUJhj8*WOcQ@D6^#+^=zD=Cow4P+TB1~v5L{_a$--ac_@tuE|P5l*_T zcoqWH{mF=^YI!=0Y^`2I=&aSSo*ow9h8FBWLNM)(PA+ITmq)E=H4ckLxa%IN0xa%O z^n`kSJ~x5N)vUS5B0uYA(g^cp_CzdFe`V2N0cbx0->|@kfN)KXIu!R&Xm*4swVGsz zwpi_GiKRLCt>T~SK7alXq{DGhOLa-cH4qcpB}# zPiKpiq7VqRJWC4-sRE^WHic{X$}2X9ScL+?=juU*>0V!Mf6}BS1$ntYF3aD(tJc;n z-~JexddhzB%8I+wx>Rzr^IYpAfW2tp^xwne%LK3arm5-b`MZJ7 z|GQL{4HT3Ygw}?+6E3SI&0%`wN&uVY5#a9{A$%7h?UngNh-6n!AZxOYd_*zt);JTV zMzUl_PaxF}s`4h9VVe2^KB~7b#kKfN(l2+}2v0^dh0mKfRX8{<YVi=VQbBLdFNKY?)Y0+67{0;0i?juvOAJ5 z7sMEc;lAk72uwv7wJpo0=hHIE&#DBcf{Dt;z4+4(Mo%H;>%*zFv@^sBtlrYm)r~g3 zLjqD%bpib)oY4eMN<^Y)<>(>85Q0)dHb&TSr0?_5ihIM_=l?)2vDDofDnAg*M)2^zG9{mz18J+IPhrMsTU{KXu$ff>WHC?g zGC(G`t{uBSkiH{gSNefPXsD{vPKCl4($4Q5+pP~x6d~nn{Q*k~y@9Xg4usIX--%-XWFt`CCe(Hx*S%}M| z-1eJPH#8p5$Xo25^&EOP0xkU*k;FAtlUDHLpIOn`C@~*Y%()bPU=TtW))X3`u;<$- zAe^w#Agc8DDh)|KMi|6~=+Nb{{LSXb)t*K7_^n7?jH!%3b=9NUuAm&|*;sF)Q;bns zJ-Am4ay>Zoh7v!xnY>PbpZDTce&! zulSetNZCBKnmGS^6X)tis1A+&6ZkrZ?oGq>LfbARl7jci`)rTv5Og!EpyXrMpN#~; zwxLO^PCXTz48ZGqlnn}5pLh<-7NaUs9-UMjUCwnHy9oHfoJ33{>Cr|i{s+MCKRT%= zrdyaNms^!txX0z5H3qcdceXGBR|lP(XOX-QQUeN=gryNkIJxjJPcHr@PtdXp<XKHBUv5Y$f$ni7Ai?^5>_!tJ4PupYbrDGJ4t=wg7;CX$qh@garq6$ zl`erA-}TxS4{d|$01&p6xvXVN%eBZly#rPDk9@;ViH%enZ%Zy&f6&CIW?XE-cbh-# zg>4ge$`VO<)L>PHez$nFuQ8jUHk#pT4BNRG{m=p$$}0|h$La%9sDH)2y1B@71+uZoC)q~C#kuo=4+ zG0UBhpITdAG{!y$^1hKP-dBq$`?7q0-DNjkIb7P5#dq`ip^Z^`@SL}1hL)V^ktq8c zWZdp^HS)<^{lUvoI>PivjIq{H>hkRbB#zpoT!=;cYAYd*)C}>AR`y9|q9KUzICeaP?4gbmgimbzZr};Bh z|Cm%^?=no9Bb)$yW|hGp!6r;c2@iWI##-mB&ou0uxKELohhaT&<_;SP4hBQrtyjAb z!A@2aOgNn?q4`7;riCEVjj=-paAENSf%vvE^^HE8@|-Tvnn3$PhxFW7e0C6-SC4Ib zuauQm=CQ<8tHP~E!2Unv6;tLP@|uOnG#Wom6lfUj1``d9t8G?BgiKzVp7gbqsZvy~ zuFtVwfRLQ_XKkJe-+32A)ay=S9UjW^5z)bAw_c?>i*Q-4IWz*(+P04sb`8|wh(6z5 zGPZUUzc-T`EzBYH4-c&G3fuu?u;klF{ynLB`Y|e!yQ)#^#ZQ3@2ZH(c-A7vAdRT_- zRDEh7E@Xet0{;@Mwt;luj@I5=FS4sxYcSSi@QwiEjYyK@Ln6yJ-F$-4Jbh@?*2Q|d zQ6EFNew`JYV&X~q+|_0=w}JPZ2`PbUS&xH$U!QEcFVRLO%*Oyu3sY>yZ>C#00*(g1 z*jIp(&oRvi!My@gn#4&-UWKt8=819)h*V{M%n}Wh7TZUKyiu#53!8p3dw#M_Ihg=| zm*9Mm%YOU}P1sL+S?dB6%#M!%t?+4cUic?|L&8o11lv2eqqv?lDa0B;QJgkEA#wES zJ-@-$$Qn^&y>Vt39PqwEUjWZ^Ry5URxOwh}q?VKN={*abi{oj31Y29s9=ncRQzdiGZ%%#)-jaH~oLFuP zfW8wwek!T@iS|h2r9WvQH5sa&jwo!*m?+{{mb1-zR5EfMlT*n`N(K27%8ms3*>y6|KKev-&1AGOZ2)GjV7=G z3lk{nEs-;Fsq?<*LXyzOzv{E&wIA_Y_Mu64r_jrLjZB*COz-%(E%x<^Z^uSY8r&{C zyglZR)Ak<43)aSJ-1@0NDb?#ILb_RM+{xOFWs*D7(;jS%Vr!>J zcnvyXdq#aWl!)@fC-3VW&gxoO)*CB=92fT|B;}#0)2`6tp&Xg!vr>Q$(H_nQzrr>o`KXxmM!q$LuBcDL61UQIBVR)Lz*3BXVQ zsAr>^n~=op_8e-zKNT3?@TgbUAFQ@nV598WxYF1&@Po(F2dP32Cm^b=HKSu~JXlgS zI(Tt|y8}S27kUU{JM}@WQN$ZGhUea7wFbfqUUqjy5z~uK=YDm^zv`2P5xnp2azaaI z`*6kIcVMxNK+01QP)fvi)G$19L))t&g&SlAGs}gXDr|Yze3^T4)GSQv=%|(-6Fyo# z=J4E0zuqc*QM2BZb`_tgL#X^#xdl+V!Lf3heR(sVOmZ+bf-k6={R z!)fNhEe)=V)VF&HP?{&VWM{MI!0Iw546d)0hKXLFk*hwu@vp+gDgVgX3POk7Dl~zX(S!*;+u}F4Y z9@-yTYr#~u-Ld#^xLCHCfv#i+J7x~vmKbmCp3JQY%9onZ5$yDz{^l_$Gj(vn$Ju5XEF^nr#Eg>6lQ{{u+^loWh z?|WhD!jzq=>Wd#ec~fweV%bp6O$3fFuHRENm>Vl7qiv}2piG7as?lpjqCKlt>hh0T zbcjB!pQvE-lz}OWOAuErT-vt_wI3bI9Uv!_Kfaf@Y!s_>i5(S7bevm(Kg0Fq8>!0vzSGthp{ZFPi79sitrF#hT&;A|u+0khP$@7MbVt{SL(Q(;U?*1l%R(xT!u1h3 ztVYJdN+(BFM^B3sl3>qyBLwWa7BVf(l76JDXQ0TjsF^c5_)%BTb08ecX)HS0&T4Y5 zAx0JZ;?__R;EbriQQ8#Eyr#|0i`R-G*{-frn+(dtsL6MA{g!;9^sj8c+~4IMzQ5}Y zf|`JU_wICC{=QXRuTV3K%-O%&B<77{9Lw zXb?Y@XAVNnbhiN1FY)2&Z#_!2ydf9#v948tQR*8UgqxZg`fHp!j zalAKWyvBbh($|w?r@!5!a~vN;+j2kKB7H~i!ti^dmuQ(A!sUtc zWLF59I}Z#KD_8NOVz=88d=IoJRg#V4XZZ5WY8lu#j?p>9+x_E-85{X^1jzfQb@N0M zC&@|UJlf{Z$*o`JC1fTm72GsCy1106x;joPstLoWO&YZ&w!QIpwjlqNni*Sc=_;a1 zb@}q6Sgf>T`q*@HceB5<3Z`pB9J4A;u6DG_3QfM7Bkk_%Tcm}#diiUAZTMM`^&CyK z`1gh;T~>4Y)6cmJzjyUMjPP_KRqCp&uU(-PJ7TzBh1*X4ce+8_2g!K^CDookDS z9(os%MKoxbGXuJ!t-ps1XRd)K8N}bamRkdh)A?l}ZJ%1-v3;pEgIgm{nCl~|W2JQj zCb=`%JPaByW5R_eekmvp26eMPkjf>Gk?XwIC@k}Z1$9*rxtYjiwnuR=7_QvBTjcSR zr=(Y~kGIG8PiJ_uRKf77TKl^~|~DiHfl63Jz}>X-b~nytAJb0R+`u zdIPKr}@d@xOJtYBCHu9Bhm&ZRYpJII89slV1kBV_r)`SAVFhDMo^zlo)T z)?uTgc;9FynDrVm>b)9iwD;e6y9v zlk)Bo;vtN3rW{p>OV*v@V12#C+IpYoGymr^AZ{c>C~Cd`_kXY#04}PM?H^Pm_g{IQ z{#85lKe$LUhF@}k4=V8bp0e*LPgqTsrX&Opva01uH@Oo=khH(RDV~9`(tF$V9KucF zj=1AlOp}6a_k`Rp9|nJUD*$K@&o#g^x`UG+w_uP&X}%IYF4+dw+FzHADQkjE>SpDr zH8JGYFRQblKDWy3;N#)^j(FGJ?zDtI%0yQuT$g3tG8)wb5Z*S(zU=vO{Aj=CH|cMx zA}Wkxs`!(yhA#DOKWHYs7vDyT5kpl_HCp%2vPKF9{2}j{mQ#@Mny^(Bfy2KN@W-$b z_t5l(<%Wfb36jpAIS>YtFlTSo8?67X&x#%kJ*E(T{W6jMZ#+)_tlWQ6t4fV?t97xY zuS>5m01TCtNNM!~u}}y%=cEEL)%CgqctiHB9DX?l%X-3Cj5vvmp^p^j6xI#WRYhTi zGIw`h@R`hkvTII9_GgO2w_@BGS(C>!r4#n`La=YGgc2|5*}a&fW^)I!@a=ia$g;fG zQ#UrWrx}*fSrXx43eP^E(ONI+vFMZT=$m_!_cUVda4x~_4;b(7&XT#Z_gU{wlt-RX zCtmCDPj3JOfAuj2&(Sgt>RH9Nj6>B-WY$1OK*hfhR1Ql7VM?+--jI4aR~g6eU-V67YNvWBYq58&9;28*o= z9=RIyfXiM&i><4ayi4=A zTv>kYzTWvhu=HFmoJfnc0iJfZyBGj(cGxsyR~bB(c8bIO4dOgezU2V zRZR5+K9qtPC0RFobOr)kq#_yAJ5iCsmdw_pcck*f4i~O{aHLwow8vYa-;fqo2gE>~ z-$)#shu6S{O2vJ~v=LUJMH9>%IM*R3yg2}>5Ax9x*Um-k5+Z#AlZxI9@xtP6adu%v zYZ=6{R3LS^T2}FRq-F=hDnDd;GVZQZqyrx=o8bB5s8C#DveMM9_YQnZv1AjmnK*8P z#x?w#^Dat4Bm8>Gk1d4}E7DeY9SA@njTG}G@@vpO;uCb#34t|`H;xYPONweRHGK74G^Szmx)qa3B;6i}ce77S%s{{K0ydX&Yg4$rj25MzoK9 z=4bOTtlYO-!roIqw98$8px6RjobDyrFaf{XV zU`XGs6@V}Fh)ra(0zrF!ao9)#zb`?87j*@|fqKd$OPs?|5EGa2YM8*S+ZkY|#n%^{ zNqh@4&D1WCjlmH}vq55wggb$94meC+9A6d~Fy)4uNjMJ&o_UYM(F@O{HR2&>VU@o0>n{M^-7Q>ZCzZ>m^n)^ zMze0ryv1T+s@etIMjXFmfu~g5=@S65m^1_xKYM4i(i6w);xmY*oQeCdFj4hjBl_1y4KGJ`EeY4V zBl-SMG2){s43TS{AW!JgMiewMA_Fm}6VCY623%MbnaV9zWO$gzw*On9<~T#(rlQ@z zdFZ$u6Ahr;;FZ`@3O4@!3ngd4y#W#^r{D37GGO|A{uH}QdG@Wb9NO~W(}j_66j_RB zxM7C_E?E?dx9mN{RNR*sPxOrwvTR48VH%QXJ1%1Nc)6ORIp`RhP7QaBDU9UQEX*I< z6{z6+qnbplB*R&(t~fy01Y54HYQwi$otp(b^kL15*68CuS>R1=beFHi`%rc+dHyPi?rPj zRaI!?#(T(xg`S~@kov3aY_=yNlzY6UWkbJ#q*1;y{xHLUFRwt7v?u3HoGo_-P^bYr z-*UKRydy2LJAe2PTzr*ZL^puL+KB9==b}_ImSD~pB_C)X6g=CrOsFW#>2J*{7iW*6 zUbf!bjwI7n7K`1HVi9K5V>`K5=I?;p+z=8?{GMqKB5f9ThI3@0CX``SBv)k1oCj7i zF3?4FTN8I6&)}oKU2T*z1r@q;5&$^sjn9#pq(r8O8D(hOm&>Wi>;FR(cI6mm<&&45 zb3$;{8^1HeG!?T4klNuhbQ{F1fRAU5Ze z%>w?UKsRabAyF3LAryt$EHM39RDc@xMtM~Y5{44pUQfH$<|n-sOo@Xs?=PdL1lYd)<@ zxC}ZyauN|<84;ym@Rc@Yky$CVwFYyuuywPr0oHI{YpQ_W*jFF!bit9@SaiUi(Fu-P zc6|eAe`mIAQwI0~xkle7Y4gWFzhsd)k%{bMqum`=y?ZpI!DSwdKlM{rd?s1PbN?C7&M36 zB3psMS$^@Vlb67fmjK(WUv=QoeJYk;whG?O$d1RJ{d@q|fGNrF@W$vnWIB715y68? zQhn6onIY_j!svo`%o2KNCn)MDh?TXnVvSR z70rew+vj8C4Q|P>w~=gW(^H?Db%V;1G}7^J(h?u9&fzWQh!NQqqI7OW=s|;4!r*zW zZwnlt)?g6>xPqCn=}34V$&T;*VnxjB8PMaiL#-pNBWk_Gsw75R6^b^_%8#wSD`|LQ2>2*ssY0EiI zw66n0tmEOYvCIehygND02CUg)n%Q=lXlv>g4WawdZg>tu=;a$g`eq!w?67SW47l(^ zQ)m4V#Fkq~N?41N%Wg3at3t9+8cBdXFKA`9v_Y0=KVK*!SmkexFoT2A!;k8_#hqiI z%$hMg9{uVw>EL|&TvU*vmkHfjNlv7Ij_6Eb3Du|N3?`32xz_uwe@4HjiLLV{A%&+Q zR}!tDeIICAS-Oy9gkrlFVWc*--;!`ol`2P z(YC!eZseQZ_3>O}6|mxu|9rJ|TFRf;xg%nbPHI_KAdq}|{}XuF)pRD|R?>p`-uk`n zjW*#xib=rFq8WD~#qv$SPsEh2hYso|LIDlNvni&H`zvUJE;{=C^Lfcy*1l)?1DVkX z4$HEChaAmlgSgSdn@(`v>>c$ono}z{EH%ztk&AldbWNP)UJGTL0!Vma^j2j1p1O^x z##`WYJ8HB_0EB=(0kv>N+0NDJ-AW$mG{0TBW0$`FDHHr|J@X95wc@o2asexrOOKWK z1UgotxK)-&qaMlk!W2Oc%@~5@(TWWX;V?g&$JSO60W+P8%=dz*@3W%H0#ml1P6gy1 zIpe{jQDZ_5>En?n<^A{j%Z^b|+$(wX9Y;`*0y^dm^KXaS(&2jIUn$ z7#DBtSeHv81QJU;9VD7++$L@b9or2V3g-eaNewda#Ho(UHzHlf9x$5Nr&QLB*b2(7 zz^zFo@7YS&v9U)g0e^K~DwD~tG`4(noCbEk-sBRQ@+t%5s(C0unLT-IADIr_8dD_S z;of4-Bk)x6h?ZB}4Hheq*-9e0M~ve}r)#$vK~`@t`}ocpzt-2f)>HtsClt^bee^ck zstR1$jdk^8HMRNG6ry)ef}$W)>@zRyR!WG)2ga-A5(&IunGtd#m&eih>*2C3efPa$ z8uvN?#bj<0Iw}zC2vUgQWzwkVmJ}w?(x@vNbpF^EqZ%u_{AQW@-MWnYyldTeI#QLR zs}Hq01hrL554h1b1U)t-v{wEOTHPv#It~{Rr+MB-WzgJaKxHrex$|n*-~1*~42~Ac zuk~waPM63igkB;zir((Cc>xBS4#9EIE{G%IfQWu*m)@eGRzwQIBC?@&)NXaQDvc{e zg`pS*uljeRB|-Qo`0%|6v1|_29{I4TmcfBEFwj==n^Ij4J?CJp7yoZg4mp|=r$_N3 zLo_6o%D}dXtsY20y);Y43;z^2K$k&6_4eYPbS$k~ym=O+7oOn1TsRHVz*~DaVa~cv z+P=-~9eb_emgF}R@u=49nHv>H{7B#~dR|&T&Yi=SiexQxzjnKPWY3kS99vpCw7(H% z39vBHu2PUI_~Fv`O^4#tta^XOosh~02BBSP`+o+UFS2qrSj;P%w$v<_)GT||R%dZI z*XE<2R%g4sn9LKJw#Y1($SixwR%h`!IToUD9#Urgv%HwrThY-Zp6Tuj`N8}{Tqox` z2w1f*J&n`*FhY#w*+TiX`}^1+c24R&BYg{dpV%t~1uUT#_HtMtu}yMV&ebS1nrN_!54CK%xkf-0_J7IB5{~42cCZC3{XlTY0v(qs;wCfDxJ%5P3KLI9V>U8{6fWFhEMl|I|B7eHq6vMxCieuGu5&YqjH!alm=H= zu;c^bnv20Rq@L#=A#{gA;BCsDBE2mhIgSz3t1bZJB4YL;7kdh8tE^}OKjqzF@s_7h z(ciXDP9`?EWHZGLB9h-G)yAvsq27~LhP`E-aI4hb>iuR7O}LQH$ivUvI~|b2LUFMtTgpJySBH#}B7SeCOKFB$#<%m8QHJo*{qj45>kCH&j@)0XE4?>(T|1$EeZ zH4_(^m~0@Z&Z4YU2nAYK`~P)z6;N4iP5T82X{5VDx*JL9?(XjH5TvBL8xatsrKB6A zL%JKJyZK)~um4rAH;;?OSuTB^*|TS6*V!|I%Dt`Zf$i2fpXY=2j4V_NVFNUiaR;kZ z87dKdhBVkT=EVA~wlY5N?>&)SyN9Eah;NGeST%zRIiXTl6ByRZ#dPK03rHw1O5Ugi z?YQu85rTL@&NgeM_0(PPLU{u#kcY0d1DB%;OoW_2x)Hv+ALzR4> zJH>I=8zz+8(<*sk;Ct;V`%9F@X%FD*tMxE23io>aQ28mo)%Qbp^|fnTFl<4eEBQ3- z{a5y2^%kqfV2;-HMll`6MpSIzCo4OFzip*^poSA|UuO{L=F+VSLs04FmaKNw99h!V z(SSv{HBHPdXJ3|w3DSaXL!fm@aH{$oI9zTl!^r@g;@Zw49gXIER%Kn!qf0WdLri$F zY`O9+HCWp@$}dghr1~IQ*L8`#;8~Fz%A%W~7+1o~a@q2tO(luF$PL#VK>d|_>VjCR zJ!&C^Pz#CmC0XkjXUa+s1M*K6T!&+|RzP7G%3AAHt>^e=iU4T(F8L<2_)|4A`8TA# zl1O6iHr5Bv4D$-?X-(dh%&1NulO)wel5cmYrcOdi5qfddchl$D6WCj*IOah8$cUVcOi>x8^&k<3A|q;N>(g4Sfgz zQAV4#^T^Svq;#kb_{69!sx1lvAK zk$hc3{baS930^l7pmajAe_EZIiiRHg%!~RwaL(3?;JlV^cl%K}+e+Z)*Ja&8dNTL{ z&myr)5L)rjUp~9D@}DGqzxIM&Q`B^bk}2X2TUqc~&Jy-DzT`}lN;TIp!e`6SJCM;L z{|9AcJ$;%1i<7zh2i_6Gc6ye{`|;4W3*>leIfbjl10sEwPn++$RMe%I(qI~zjw_ll zt?k<5FHQ$+Q?W~4IIKJ5t$&v4+m!5^OidU{PAF}Pjhu^b*01UyPe27`lI*#Df5{+D zUq?Nbcv6$MG`Swh+ZIW{0zH2-y>no5iLF54A8q33Me|_NbN^F5IMs3Sqc3+Wwf@^a z}2s#%^}b#GOdumm_2Lw9-hIqG!H2}#?XzX&LfYe<~rB4Wfx1pU1WD2v`-s^MSH2=7hG-COEo*c-RfQI zQj2iGcR_YRq9U||AcXcQITeO}Y`%I&g+z&38MRmQ?u=`-wCk9K18de09s6*T@S}`y zHH&m&(iA;dB6$)=Wo|DcPr zn&j1!!k+GWPkk6}m4Kak2{ug33Y&cr|EaPxoZDM0A|Io9fy~Taydv(I{n`9QFB-FD zIKAej!7bfmoA$%{gK59={nc`&WXz7Cv)QvMyEBz`OpmUI+wK=wt53zkof0yRElnDh zcq5Ls<8tXB#7!Y-FL^#G zWjQ^cVUJvm+X#N6mc+b-f{DEzZth;l_N4-$)65d1pIkeeakbVycO3IE{S$bg6_Wa8|nbw+G$mN zY*|*=jn_sPl3owmQ7p;J;M3=ZI4*Xc^ykxLtSHl0?Tk3IWK_J&i{_1z&fYw@gX9LW zxmQWz;j{-i!6gXR@=$vFip2*Ns-=9jnr&D zOU8V+avS&Vb$aWx9O>l~$98P(TI`@Vi6&wQiSuktH=tB=>La5l5)aC*m94c#}>PBzS zDg|e!u(Pf~M3HH>ePiE5X4sY?P6eIay<-Uid;+#1+ZkI@#;jjPHsfdJqY<{4RY#j-ZG?i^FXN4vMOQ_a*M6$zV1_o8n`0 zRA*NiDMFQ|hVhYhnw%VmHO)-y z1H>EyE5Ql(cp%ZS`|)!MS7QB~v)t-aflWOQ1bp(!<=c^{nYImnhWb8k$z;QKlVxZJ zdUu`ud!*K6lv7Y2eB(84_BQfAH)EnFMW}E9^Gn$VcOMMw%iJ}y&d5ix znyZq*nunj^zVS+QyBAFO1S)x%)+@3&P``^trh;M$^ZD07Q4EG=7?QZZVl$Z;1KIAVu2WsH1cw46o5 z%t-J>kPkCnAuyQ{M(dy3vgp@I%u|r}qedm;e$E&N z;iM=$58F1C(DS5ifwAk{bq*#p8q3Im!xW3q1D& z&UOtMO|3ZZoNR>34NjxjVFz{n$WJDY`C2BdI051Ay6fgSj((y2tkc3bPuZab;B?+5Vr-{{+JvO03%*HGO6%sN!=MZ3n9B62($%Wa5 z+&$AzZ=#i(7=z9sww3QqLEma*pU!@>?57?=Biqj!GB3OhgHjYeYV&&h$TEI<1jSQU zV*7EGMt%6GoOz9(ZEX^=bZFy|UIs4gy;8ZXz{LB(<6BR5)FW20daX;i<`#0)*X5;- z^Z|M4taox|U6X9O-PMNOd2t{KHuVxy4BWGd7TQ5fFLtTPsdP~GLt(tJnp?KKBiueo zL^pRbSkwj=>W19W2|C)|?~0+?B9uF^t6w$&me3*>6(9A4*z)Qbf#sY#q^ximBMi52 zkzWb{)YX@GIK5xKt>f4f^(+o;@C+VZLcbT^EmK=P!N|Lnt470liUF?{apTP@tIlB@ zSLP^_A3I&kz%!@W_oNHS=)FNTzD>)zPw&WTyKktN>~W!37T{#o= ztx^fM#LnDfE!XvD8HXqwiPJrf$*Nn@33|3%m^9!FJXM;awUMR0=Opo-$x?ViqgG{( zXc845Y@;O|uQ(Eb31kT1X7dvf>6cmA50dTlmL~>AUL9i;OC#4-^=Gbc)!yb}GTmK4(dwlAf-NuKVYZeA)dMN4gL6}9-ZA$%g=0X~lf z6Kj7Gc`>z7N?RK$N%NdmuU&cSy&57giy?UnCX0Y#lhUnkrUXFbNuoy&_s%@Ov`Jb^ zun#1N4xD(%z>~L>`YE}L`-SWAeR1TCm=KjmC}{ATB_a6|&B#7uZH9qr(enH`{P-d; zbMS@S@80@I1LrLo+*8rybaaRLQ}`}hoFICx!}t=CEW+HMY9&SuUo611Q@l6d(kFUe z!HGiUZa|%Y!%}^}z9SMqaS~9(Sgap?i?g86QE0t2QI>UQ%f94IXRMwm#oYJ?a%-P! zL}8ALc(};JJcq$i=6NK&LSws@c))xbK3crMqwoVIWo6&^)yXN*D>OE0=hPGL2h@R! zOFsq+S5Z}~r*x0{eb)$+b)F^=Y?!5ZUn)-G-$m9e6z?!hcRSuBb75Bq+Xpd;MJ~;) z(Rh={?t?ro38c3|Qog=-hf6Hnrdb&;$q8EL&C!BWA(k>orV>PHNXbcW*oJWxtaa1! zI4|AC(z<4%t~lZy9N9Nbg3u5*R7#09;u3Z_P$uQeOpD);jbou4yvESBn=9ac|61~4 zyM2By##B9-^%InFvW)c54N15ya%M5YEynOMOJro}v^8;gK34S=4J$1=;|B&hthcg$ z1DZ?zxQR=B9Io`%L>cOv7z+Von0nwX+v8~UEfXy-ykPSp0ztF5%-BFIYoP_&S#>)f zE1^RI_7?)XJnK7&xTqB%ACnMivzpi-u|1jFp(#>5dk?4UanMGqUS{y7&Zp50-C2`O zr4wm3C;k4W3vlDZxKzGG^XxUXz56GOw@5Od-V+2G4xyT8!4WJt@F-Udmi8_I^H z4cD6_Li3fw+N(gHb83xgKq{0osrC`yD{ilpkLAqfil+)zB4a-LNK6|N$E5fsBxPv0 zD8h;;yUH{qH<>9tvK*7;ZGj=nbvSokEm&xXVou(fX)@t0kv8lhP?bxqzYS1B*i z3D`;WIgI9k(5-xPPTbH$9kdpfjy+dM$0mIRaTlS|aX2iLA)?bqmr|I)TsU=nbNWyi z0!Nu`&X;O>=bV}pz?3G75!6PVBW2Hn1V>1O3H&DGY-UE4>}+qoqxch*cb^Qf3ucbR zMhJk>5G?cmZ?;@lXts0YP~A-CS?D#aooVevuDX;Kvt)h$tGj< z?88=Se#PFSf)C{i5s*bXD}DokwpYf?$^pqwhjJK>*jXqJIP~ACgQpQ7Ks{VH2)Y!} z!dBhB{tT5WvtG`AInJ)6R;jdI>M*gC0kReh^QZ;**2DqmfJIkWLPC!qdtJwgcWE{f zUL<92I3I08A5|L^U+bOFep1GmGq5<+9b5h^KW#+Wx!A6?%nc)_7Lwugt;50M{_ec} zk=*V(wdZZFE#CP2B=>psCHyO7mFp|ouzS-V`v$LZE3}?pC`4j&xwu5cP=vj{<1xo% zqZv%+T%aS0fLyFMy@QfCsog_d4i+cOk&~$%ukD`g9Ywe6N9JxK)3sdepx|~J0dVcd zI8$?rGWS8N2p-6JzPZI6XAwD&V?{-Bpfa$mQ>60Snz-f6T%<72uG+Yuo?V5gX{{1sS6VvRtvo-dNkMPAKfdl#l?AX;+Xjm}tz) z-ub)n(p1}GLU5=x8M|INK7TWJdmT$gusCG!D4dTmTJrLPhpDMTx_dBnaquPcFzsU|Xuj=>eG zC=!h^rkOP6ugDjj$u(4oZjd9X&&{=`TFEOFJs0i87Ls?uV9PbsiB{x>;1(2vM-l5Z zNP09X@s>5WUncZDp+ps-k+(V=_RHGT!l0~N9g(F4jt5vX)HQnk+lp@Y9ulFTgM92& z9*LzzYO<#ZAV!mDai-qL3DU+taq2d z*PK6hpln)N%j27-50t$_CQ9`OS zjpN<>gQx18#p)1y>RUt>i%Rd`%XriP|4s0oM@YiC8}GOURG!1iCC(UQT-DN(C{^3* zR$4A*nbiT%oetove}^HAH&@L*Z2J-0z3r|=e>x2e4_@*4m`Yek-Ms={KHacG&TT0D zOd;eX$vbN#r$J-1!_auj;-(0RQ|U2!cOuw#=i1QZuto${Cw_wuP_8w%1;HQ?{Su2u zJ6W}L2I9vl>t4g3dNg7hjMwo_nU8VBv@4$?STi42la#92KzfCHs>DoEzwz@GZkiPB z2eh=Yfi5uvLhRyWmWR{jtKJIO$EHtAw9V<~{Z@INxxo@k~2(g@Ljlostd}Y@5F?gSj4a z7{+J0_e}P#VuK#cXsUua?n65U@5?kBZ&8a9m(rXtW|Y_m)XlNMV7%J5$8bTm6$ALV zFW_;Ug_H2RCS!448mf@6Rx{`CPeyTH1rxBg`Uy5jmkp*hkgyBiC&Z6SC>p#9^md{N zov})eM>oHu%aV5%CmDBL=3XvP;G!|$eq!stJkdfjPKhJMb1k_20e#U(4O639rH2lr zV3rMO$M^XW=YBiB;Bo`()wr^=%0~`2Qc0@;Jc1lhldCcx-%Pf1#Ly2`j+~p(u8g@? z#M##Pp6dl-O55c1pZGo@fDPC+I@2% z9*JBKwHe{?Rpb%d<>EtUdX@2eA5_`q6eLwkv&S=H1JRjbCay;zx^-!1F%_RxYVpHM zebe;|tpsOG%(N3q)wI<%j7#)LtQO>2(M%}V$~)yLX0?l4g12*?y@tJ;_C$JB?=;7x zI5Kl1hhfH&E?EwFgS8IHN`k(7t)mI4k%;qxTCfDo-GS|`Gul}ogGhN+eL%Z1?aB-{ z_Ps%Q_RGYysRM=8q|lu%5S7iUxEu&$>%5D!%{M&KY`qh;A7Z={KRkLd4+ z9JS$T^`WS$SAESqJ2te_?DeD{@7hbXakv60qRBiSsL8mn>OAns+I&GCd0yQN2?(ul z<|n%3`!i;6(H(y1(cxEonsg*`e7+DcJuw_{!ycj5f^C}mZmGrXl9M@W5djuF(vojN znRz;y8&ZeCY>^SG53A!qf-$=!S50sOXD=ET=I6C0*I-?qz4+t+W;$Gtk+~IHcMp*i z5nZAM=_ufNJ^AX2;Bep(LrqJZ(E^|-!cBxbGl?owKxd0^%!LOgxqc_AMI+E9PbNBS z;N`Fhn_R&bwcZnwL=ZlUJPBuE2gZzpsMQfq?gN78gYM9Syh)hjJ5yF{DKrBw={zQT z#`LrZ^wn*!g~1-vXwt!Fiq6}_^l;~Ryk3Wa55r(hDT3F?-;)QAIG zLK`75@h%cx=p;lMJ2t95%5XmX*uCtQ?E*}|lk~a|;avww797!1y+H);lP9(CDWrI! zlxCbkhjz;Ob@_A*1Ln^n#zp*!TX)~sQE5sz(gyQ1Gj`>hbcuMeLb6~azUK8+kAUyZ z@ULf0Uoh|EZntB{a{{Gwx>?8*5yUtrld>QRmK;$g~@U!2|*YI zktC{t8B{S89DbRA+CdNr!RDQ*Df?*xr__S}m!~1l=_}_&eEVUMmn+IEXZ6*XDX-GQPa#C)gPHO8 ztx5(GV%+&~`sNBK>ub`6%wc>mbODxj?fL~I`L}+u3u_F^xWyqJlA5fcBhVF#~lh~Mgp@^6*w;+d5m9; zvgPx_fE!sHWuL-~8T9=;2nHj)UcyN0jh|jQoju{`^tu;lsg7Ub`Lhs_87~XY_AcUb zwmg2^4-9R3UCcrT(5xO-peb*f#6al{0NK?3SMa{!0zO_cFM0gmf!xjqG<)EV?m2^# zTRrZRK=AC!*-d~r2+jkce?V`!@_VSK(ssUThW5pKF-Qkzm5Yl#Q||n7Xa6>~&OId> zc@HK0c1ZvE7|9t4U45?vhYD7q$fFWxE?s#%s1XJNX)Nrayb!BL=WM0~1FY(J34>AB z!c*osIye}5sZ;z@!Q|>QXqU?5{Eh`M7Is)~Lkym*<0i$p_0uEUdv0JA)BxJwWC^Dl z`5#3;uLVpvTgP9cSaj%{A@ zhFZA;eDd-0bjTjrrP-;(39Nx}tfZS}i)b6ZJ00woeQ^m9Cs-%DJHY^LhRe3zL&Rru z_h^j|eZ)rL4DOAP#iIlibAY8Baglwhwd?r7xy0UiR+QRCd)}vI7uB#OJqzA$4K!sJ zC9u+EDrwf8!=AF6(R=7_YY*y%-fwLvrB(J){YMkS~Jv9b^|HHHNY`}%m zLuiqj7Cgkp+p7OwB}HGB2Vp&asg7c#7*a`9uWKFxp~R@H#pd|Fdb>1cd+c*MUM9B6 zYB0kj`hYhT1q@rD%L$WpOREY`d_@9U7EGBz0Uo-nli+6;6&R*V?hhwC?aZ7c8aI}X z43lG^qWv!S35)oWkWw|LaRV(Xn<}ejkR0v~Bx_TQHA3(Ry=?N>C|R=MAFIQKx}{(q zJ+31R873spN{Kv?iA5HCD3C$JJr>{4EL4rgR(cv|&QL$GE_YaYc6&Ic+RV-}Xgnq{ zCu9Yfn!BfGeC=#z7X{)@D?04diVkU;PaQt((4RB}$cyma1Tj)8wI|6S; z+1SRX+*=iPDQ-zqM;JYXEHfdP`!T5Z#>B5}Mat3CK%ywv6I%6r7{+Jq|euC-n{tXFNcvreoqOp*wKLxOsbEQlImxTNxZOERz4S$pNZPzA1?S`$6lt`#7v15{qDP1l2b_u+lp+h+myFHG*b*)4Y9 zBHSe8J}d3Czjj{A?%@M1hq4A!lzLHS-1pIY=#28iOO_7%N0mX0=a2*>@5rs8HvO!3 zlf-OR!pYyls7IK1k+IerIj5aHw%n|=bvuH)hGQDida|}*hWG?+rT7{)Mk7&h#&;e4 zP46|b3re+o1Zl=gwyg8=g2FIGmB1=iE&nc)VDeWDlZOLQdHy!47#&#^Q-rfi<+Y;7 z;+%l7jXGUhayuju0&dvM`{Ztd4Dq+Y<=EF^vOOY1O{&J9$c&*5$&A3b4F=?*m_D&{ z_29FZn6ko{ABw*w^N00!Q=3{N&QCvb8J?n3_w>PHbK-DG0wtr0NHdS+MUw1yD`d)` zHtu@PJ1H~dbj&m9&5~|VFOHQY!i0@2S~ILViH^S39>gBl%+Y4elN5rQS#5WXSmuPW zwBsKA8pk&xr+DSFc;?=GT@Kqhp&0&v%F$%Pcqd;z8G!&6e8`1OvQX-+HMqEoGXp0Z zZQi2JPX3xo4yyrUDIzGSr_K{J3ss*~a5Js|_wdPQluQ-fYfvPg(J3ExvCPxzyt6Z@ zQ{D>EmcF1jzP^fN+(66ekx7Sz^j#+$nYP?efN=>=b?cX2z;RP7aeS|dx-;GB_#Q)M z(5NFE^Llqd*JyP)4Ig`Ph@l=o^M0660y%FY^!cW^NAhlYt2RkP*p2750X5;hh~H$H z4#mSveqUm6WD;GRwt!dDA*fi-y;l6LYV}e)ZdiD0BxG%S47g@;O0m=Uh4;Q!l*i&g zN;-!@)s^R)~mMC1P^CGc>}|(REcr8Mj%lq~f_k04fzVg@LkKd0Px=0S8rr zs=N@AGB+-bFnv~_Gf62_pC!daBV*+7z6$~)9b#494&=Iow;qTQEj3SVsl0YDIku&1 zIs7A9{})5%JE3(?(Oh3;%9NUry9aT5P;9N8JGxpPAj-Ky<+U05nxZnI&LV?k`5X9z zvGpSB&OK|FlME_8RuhmGmRj(Oae<&luJC-}%!^FGVI1D3&D(T$VBkE6O2>rbCU3)(5f~zN9h7$=&(E4xjd!eIc7^tV6`!@K8Ms=^!kcu2|I;c^c@v=)UM-!XuJt;1kRrbxv^7RC)ul2v|` zmAeKFi4-AdT~O;PWz`htT9TEiFYwwEEuIo~A>z{Ap=aUrO*E2AoZEFmnX(=X+f~m( zYv7yt5F1fP4@~ zQ%FX@HmSTqKHjt*AlS3{=(|3PQ*{|t)T6N7QWwaUtAoQ#CkvmCG5v8M(KEwfb*10$ z?GWoo;MN!u(g_IKY9cX*4sI*Bx0T&)%2eyoEC(HKLKqZ`1ZM_gbKTOECM(K*Q9^NW zBV&fTQ4x-Vg0=AZ(37}R3T(0d;NndGM*&=6Y}W7VvbnEcL4k}ajHwf62kt;s$yIjX zXCCrH)h&g{K$zLKh#Pt0PLk3Fv}%@TS70$aK#8@wlP1JgFRsM9;*@s0U2G&@+!snu zJK#4M?;W5TQO<$ea@E)k){sC2^YX(GL}KXbkw?U_%NLAGrWi^LIi;YJ)Q*xT-04{l zS9_#RY0W>%37vc?_9WR?nH2JjIfyd84N7W6BU2&-Tafe&_GbLhpf1}pDK~>yNjwWh z?XE4DD2G5*R?bDSEEqV+ ztJiihOK!0dyMd;1mUj+=lx_0?7fo#1HCYWJ-neex>w4;G%xZ1|n~Z9bP6Xl6HLky` zYhGZ)CwaDdUac2%*s!+h%KZI7k5lwk+Lx0m z{HPErwD!V|z%8#k@42{_(_vgGQE|p$#isjSX36PE%KEGNIRZ6%@i_*qLhtAGm!URG?qhHo%48Z};$J4uthO>h+hFmgH3i&`$H9 zlTuFjngk%dA@gP&D|C@_n?Y%)FC)F3d?6Yz3=ld8` zf4zHPYj5VP=V+v3W^G{UWN7qf`w`Tuih*O`06;8oPlD;+A^x^A@%y)XRVoiQGr}kj zZnvm5is=M745Ugm@l`R80*A?>^vJdH#q!&E+F8!iKXU~Q7e3r9Oa~?brgO}m$`2lR z?Vl{%%(?Wv%#c*3$fRR-oS~bgUF7ToJ46L%%RdNCvOUtDd{Fucz&a2lo1bj%D4_WeKK9eL%jzkp2 zu-nJs{5N9m$@T-C4dCK|?qUkYrl2K`A5D@MMlI8cl(oUL%oX|Oo%PlxmdBk%tox!o zaokK|sStQ(#@;?V7mk35r8p&9z3;)6>bhhBc-%tqgmvAa(JR8PmeqSIN~O#d^uE;O zOe`-(5m3v`McO!tx=~F@GI1Q#rL|F`EF%(C#d0tbR?>Uvn;q=h&+@6xEWcz`eNQcB z)J?LSBy$-Z-8&8()?0ydNdTX2VSaXpvi9iB5(n`YfG z1oohgMIU0*@5#CDLYWKck$UBm3rB!?!nUg$%bsXHD@U0vK_oXXd{Y52mE?|m;d3L= z#pv9U|MmmNl03CE+FQGc3~=hTm;6Ibwt1}Enee^qrjlvpUheyR^%zTDO(|;mk#M|m z%})MK7e}AK9ktQtPx!rIFCBvWoM?$kiqwXCQph!9o-)So8rb5N+RgeS389RU=+{u2 z-!eDgNU47krKz==*gu&bdXINlXrAk`amvH|)S86TY8gH^)u)iz+j7_#xv91J{;ujt z8+FP_@35Kcd709S609M=4Q$tv9U1~dTbBJ~D}TfjCmt-Wynyo5`EE6aN@~<$dd&R* zo@JMzYp+NQL-r#wY#|~}j}Jr-H5d-T`2gc{oq#Fh;#YPHpOJ}0AKJ)P#TE(kRs4LJ zI^hlxRwC{l0a&Fo&CV%RDcYp4Tp-A5u#5JzV@;c=Qku$-m}le2IWQ{9Rdh8hqY+nRJ#i9K-`80V zGkt6_KbNp7F$XTB zrV75k%uxxFEC|WMs1txbCNA;lgA^sIM9q1-@^Kxj+FaGU+sAIcB8Rj=AW9sROEeWj zP^rko)75-6=A@o6w&g?p;MzIxuziEJZ~B{y!ww}sJ|2%DBgslpwhR_{$rvkw9-!xa zg_WGhJsi4UZPnV(rORbTOUVT6F6x={RdvoSI`Pzaa7!}rx(&jIX6={u=NvjdRSkID zVlcUqSlDzpddQiuvyW4DAfpm62joYeQ|{`Oh)_$7=r~hZCvx2lzhkzfm4b-9IYv94 z6UTc;G4VRM(jV#sY)Y^1#86+y(?P1`>Sztj`Bm*wif#OUq2d5bl+cIzf;0Mr7LE-g zGT!jZTRmrlI5v8wGIMlg>q=635Cb+ACrNwuf*2Rj8HmUT8-kYU|@+>bYw$a6DD&-&PE}yt;)*tMjfCtMad0@bDi)+*0HAskh;=<;J?(@HTHqO&x2Fmcp z%K+oxy6ZH`*mbeGIp;80BdHe>?_A9PnOapRz$*E7kWO3eL^7%@X`n(=&MS+c~?;a1}?rilB zc*VUU5mSI^?1SGDMvrK?&?K1yYa1ax`}oky_lV*i+;1`DR0MRlh#>%fx?J_lSzdSN zt}4+4Qq&GB$W3_Vm67qz3@m4te9bbPCw*E*Xl)dK>V*t+47f+&`D5Hq8K0$lZP}iu z=$IB4$p+CeWU;;UP_D|P;_%6+aII7sZ_E{CxoiRt8mkRC)#4sH1f{1 z(1m5U>H1}<)hwd=_wdrK=-D;uMtj0Ng3HIHt0V9i%OGZ|eK*kIjU63DYwOYPUzeT; zAl06TbBezJ=1c(rS~N89{=y#*dQgB9qOT9)Z@>R{wnGCvkTJ5?qp{L+a5S?2erEL} zl$D26GOz$F;L8u*Z&1KH62ONG`130$S`#xzGZSkYd!zq9?)hE5uRgek6;b2?M4Q2mt7T{TfkHOi)N#LFi8r(YoP_ zK~MpJY90Up_d6K@07T%miC@VmFU0>`N{H6V@CVHA+dUe?NdHB3Sir^-e22N`t3>f@ zOamJ$TN`U5Ye!luiyvVB%#Oan0>uOpkyHK|thKX|y@Q#JwZpG$>Z?vqcNfe75C8xv zP_OjgNecj2{{vW{hIGtrO>_*63~UUIzKhSlTiGAI;OTEPU#0J)|9k0wvF7;Z8DCpb z67=pvBG9VPfkw>!Ln}}i{yF?lt@(W+@_TE((vgCtO-BPw&IkEtI%wv9PG@Fiq64&e zU>^a!-r>7DOFz?{0^&^GVgs$*4gmP3yf2;v09>>GJsq%rfIVYt`;RQ^n+bgFn`6bl zhyROp;g5a9wXD?1`6U(r{f+g9J`z*<_cZ@&)t~p!x|lBG9Z*O1j{pFcAIO$8|2>(T zlk;D6k8i;N=`t|B`2Zc}$L`_K`Fk!$8y$Ts+h4lQS7Q^w1`GUBAa5KC0HFM#DQ0H> z2p*`aKk5Ik1ozCI%EUnTF9W&?=??_j_J2?CMK`~HOz^wu{?YHhh3v1kHQ@VeY(wBa z!>X_wl=)a79-;X#Lx<(5{Km+u~0RZT}lY7s1=$|2f zC;yKw^*xIr{Zklgp#P2mBhi<=qA$;vD75E0T=d-Q>aX{KerE`Dk{>5& zRLuX5;+s|e7)`$KgijBelc~U2R35N!%=h6K0B~aYPh3CWjQ@S+^EJ#I84%s40|`@s zUBmNTI0gnzp8rPpixll&NWZs-rZkt&Uo@%-)adu)+n%q3$iE}~%@==E==W6n4)5x{ zfzts$;B??i-R3XPmteQ&+aUU1sQyl$FSI`LQUG^8Hi3)(XW#JwgBUO&EpP_I#Q4)b z$~Q0i*&e?(P3Iq|^XDqxzqgSbsNsLd`BTmBpL_1-GT6UU*#Yxl{ClduQ|HemC%^Fl z{lWM@@%;mJ{#hIJk{Zj>oZ+rkimFs`v z`xAA3AJTr#$NW1dw(q~?{3$#0&%x~H1jfHpDZTtJRKHT^_rc}o6focT_I&-~|25x_ z31NP=l%I2?{7yBH@L#BY)wDkwSASd)lfC;j-ybg$eA`(0IZXJq6s jU}k1zH=t)`VA5yjFk~|_W@Th#)@NWcVlgl@WH +#include +#include +#include "time.h" +#include "sys/time.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/gpio.h" +#include "esp_system.h" +#include "nvs_flash.h" +#include "nvs.h" +#include "sensor.h" +#include "sccb.h" +#include "cam_hal.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 +#if CONFIG_NT99141_SUPPORT +#include "nt99141.h" +#endif +#if CONFIG_OV7670_SUPPORT +#include "ov7670.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 = "camera"; +#endif + +typedef struct { + sensor_t sensor; + camera_fb_t fb; +} camera_state_t; + +static const char* CAMERA_SENSOR_NVS_KEY = "sensor"; +static const char* CAMERA_PIXFORMAT_NVS_KEY = "pixformat"; +static camera_state_t *s_state = NULL; + +#if CONFIG_IDF_TARGET_ESP32S3 // LCD_CAM module of ESP32-S3 will generate xclk +#define CAMERA_ENABLE_OUT_CLOCK(v) +#define CAMERA_DISABLE_OUT_CLOCK() +#else +#define CAMERA_ENABLE_OUT_CLOCK(v) camera_enable_out_clock((v)) +#define CAMERA_DISABLE_OUT_CLOCK() camera_disable_out_clock() +#endif + +static esp_err_t camera_probe(const camera_config_t *config, camera_model_t *out_camera_model) +{ + *out_camera_model = CAMERA_NONE; + if (s_state != NULL) { + return ESP_ERR_INVALID_STATE; + } + + s_state = (camera_state_t *) calloc(sizeof(camera_state_t), 1); + if (!s_state) { + return ESP_ERR_NO_MEM; + } + + if (config->pin_xclk >= 0) { + ESP_LOGD(TAG, "Enabling XCLK output"); + CAMERA_ENABLE_OUT_CLOCK(config); + } + + if (config->pin_sscb_sda != -1) { + ESP_LOGD(TAG, "Initializing SSCB"); + SCCB_Init(config->pin_sscb_sda, config->pin_sscb_scl); + } + + if (config->pin_pwdn >= 0) { + ESP_LOGD(TAG, "Resetting camera by power down line"); + 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) { + CAMERA_DISABLE_OUT_CLOCK(); + return ESP_ERR_NOT_FOUND; + } + + ESP_LOGI(TAG, "Detected camera at address=0x%02x", slv_addr); + s_state->sensor.slv_addr = slv_addr; + s_state->sensor.xclk_freq_hz = config->xclk_freq_hz; + + /** + * Read sensor ID + */ + sensor_id_t *id = &s_state->sensor.id; + + if (slv_addr == OV2640_SCCB_ADDR || slv_addr == OV7725_SCCB_ADDR) { + SCCB_Write(slv_addr, 0xFF, 0x01);//bank sensor + id->PID = SCCB_Read(slv_addr, REG_PID); + id->VER = SCCB_Read(slv_addr, REG_VER); + id->MIDL = SCCB_Read(slv_addr, REG_MIDL); + id->MIDH = SCCB_Read(slv_addr, REG_MIDH); + } else if (slv_addr == OV5640_SCCB_ADDR || slv_addr == OV3660_SCCB_ADDR) { + id->PID = SCCB_Read16(slv_addr, REG16_CHIDH); + id->VER = SCCB_Read16(slv_addr, REG16_CHIDL); + } else if (slv_addr == NT99141_SCCB_ADDR) { + SCCB_Write16(slv_addr, 0x3008, 0x01);//bank sensor + id->PID = SCCB_Read16(slv_addr, 0x3000); + id->VER = SCCB_Read16(slv_addr, 0x3001); + if (config->xclk_freq_hz > 10000000) { + ESP_LOGE(TAG, "NT99141: only XCLK under 10MHz is supported, and XCLK is now set to 10M"); + s_state->sensor.xclk_freq_hz = 10000000; + } + } + vTaskDelay(10 / portTICK_PERIOD_MS); + ESP_LOGI(TAG, "Camera PID=0x%02x VER=0x%02x MIDL=0x%02x MIDH=0x%02x", + id->PID, id->VER, id->MIDH, id->MIDL); + + /** + * Initialize sensor according to sensor ID + */ + 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 +#if CONFIG_OV7670_SUPPORT + case OV7670_PID: + *out_camera_model = CAMERA_OV7670; + ov7670_init(&s_state->sensor); + break; +#endif +#if CONFIG_NT99141_SUPPORT + case NT99141_PID: + *out_camera_model = CAMERA_NT99141; + NT99141_init(&s_state->sensor); + break; +#endif + default: + id->PID = 0; + CAMERA_DISABLE_OUT_CLOCK(); + ESP_LOGE(TAG, "Detected camera not supported."); + return ESP_ERR_NOT_SUPPORTED; + } + + ESP_LOGD(TAG, "Doing SW reset of sensor"); + s_state->sensor.reset(&s_state->sensor); + + return ESP_OK; +} + + +esp_err_t esp_camera_init(const camera_config_t *config) +{ + esp_err_t err; + err = cam_init(config); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Camera init failed with error 0x%x", err); + return err; + } + + camera_model_t camera_model = CAMERA_NONE; + err = camera_probe(config, &camera_model); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Camera probe failed with error 0x%x(%s)", err, esp_err_to_name(err)); + goto fail; + } + if (camera_model == CAMERA_OV7725) { + ESP_LOGI(TAG, "Detected OV7725 camera"); + } else if (camera_model == CAMERA_OV2640) { + ESP_LOGI(TAG, "Detected OV2640 camera"); + } else if (camera_model == CAMERA_OV3660) { + ESP_LOGI(TAG, "Detected OV3660 camera"); + } else if (camera_model == CAMERA_OV5640) { + ESP_LOGI(TAG, "Detected OV5640 camera"); + } else if (camera_model == CAMERA_OV7670) { + ESP_LOGI(TAG, "Detected OV7670 camera"); + } else if (camera_model == CAMERA_NT99141) { + ESP_LOGI(TAG, "Detected NT99141 camera"); + } else { + ESP_LOGI(TAG, "Camera not supported"); + err = ESP_ERR_CAMERA_NOT_SUPPORTED; + goto fail; + } + + framesize_t frame_size = (framesize_t) config->frame_size; + pixformat_t pix_format = (pixformat_t) config->pixel_format; + + if (frame_size > camera_sensor[camera_model].max_size) { + frame_size = camera_sensor[camera_model].max_size; + } + + err = cam_config(config, frame_size, s_state->sensor.id.PID); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Camera config failed with error 0x%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 (pix_format == PIXFORMAT_JPEG) { + (*s_state->sensor.set_quality)(&s_state->sensor, config->jpeg_quality); + } + s_state->sensor.init_status(&s_state->sensor); + + cam_start(); + + return ESP_OK; + +fail: + CAMERA_DISABLE_OUT_CLOCK(); + return err; +} + +esp_err_t esp_camera_deinit() +{ + esp_err_t ret = cam_deinit(); + if (s_state) { + SCCB_Deinit(); + + free(s_state); + s_state = NULL; + } + + return ret; +} + +#define FB_GET_TIMEOUT (4000 / portTICK_PERIOD_MS) + +camera_fb_t *esp_camera_fb_get() +{ + if (s_state == NULL) { + return NULL; + } + camera_fb_t *fb = cam_take(FB_GET_TIMEOUT); + //set the frame properties + if (fb) { + fb->width = resolution[s_state->sensor.status.framesize].width; + fb->height = resolution[s_state->sensor.status.framesize].height; + fb->format = s_state->sensor.pixformat; + } + return fb; +} + +void esp_camera_fb_return(camera_fb_t *fb) +{ + if (s_state == NULL) { + return; + } + cam_give(fb); +} + +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) +{ +#if ESP_IDF_VERSION_MAJOR > 3 + 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) +{ +#if ESP_IDF_VERSION_MAJOR > 3 + 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-rtos/esp_camera.h b/esp32-cam-rtos/esp_camera.h index dadd0c0..bfb729b 100644 --- a/esp32-cam-rtos/esp_camera.h +++ b/esp32-cam-rtos/esp_camera.h @@ -38,7 +38,8 @@ .pixel_format = PIXFORMAT_JPEG, .frame_size = FRAMESIZE_SVGA, .jpeg_quality = 10, - .fb_count = 2 + .fb_count = 2, + .grab_mode = CAMERA_GRAB_WHEN_EMPTY }; esp_err_t camera_example_init(){ @@ -74,6 +75,14 @@ extern "C" { #endif +/** + * @brief Configuration structure for camera initialization + */ +typedef enum { + CAMERA_GRAB_WHEN_EMPTY, /*!< Fills buffers when they are empty. Less resources but first 'fb_count' frames might be old */ + CAMERA_GRAB_LATEST /*!< Except when 1 frame buffer is used, queue will always contain the last 'fb_count' frames */ +} camera_grab_mode_t; + /** * @brief Configuration structure for camera initialization */ @@ -95,7 +104,7 @@ typedef struct { 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) */ + int xclk_freq_hz; /*!< Frequency of XCLK signal, in Hz. EXPERIMENTAL: Set to 16MHz on ESP32-S2 or ESP32-S3 to enable EDMA mode */ 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 */ @@ -105,6 +114,7 @@ typedef struct { 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_grab_mode_t grab_mode; /*!< When buffers should be filled */ } camera_config_t; /** diff --git a/esp32-cam-rtos/esp_jpg_decode.c b/esp32-cam-rtos/esp_jpg_decode.c index d42794f..a9615e3 100644 --- a/esp32-cam-rtos/esp_jpg_decode.c +++ b/esp32-cam-rtos/esp_jpg_decode.c @@ -17,7 +17,11 @@ #if ESP_IDF_VERSION_MAJOR >= 4 // IDF 4+ #if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 #include "esp32/rom/tjpgd.h" -#else +#elif CONFIG_IDF_TARGET_ESP32S2 +#include "tjpgd.h" +#elif CONFIG_IDF_TARGET_ESP32S3 +#include "esp32s3/rom/tjpgd.h" +#else #error Target CONFIG_IDF_TARGET is not supported #endif #else // ESP32 Before IDF 4.0 diff --git a/esp32-cam-rtos/img_converters.h b/esp32-cam-rtos/img_converters.h index 330f8db..f736200 100644 --- a/esp32-cam-rtos/img_converters.h +++ b/esp32-cam-rtos/img_converters.h @@ -22,6 +22,7 @@ extern "C" { #include #include #include "esp_camera.h" +#include "esp_jpg_decode.h" typedef size_t (* jpg_out_cb)(void * arg, size_t index, const void* data, size_t len); @@ -120,6 +121,8 @@ bool frame2bmp(camera_fb_t * fb, uint8_t ** out, size_t * out_len); */ bool fmt2rgb888(const uint8_t *src_buf, size_t src_len, pixformat_t format, uint8_t * rgb_buf); +bool jpg2rgb565(const uint8_t *src, size_t src_len, uint8_t * out, jpg_scale_t scale); + #ifdef __cplusplus } #endif diff --git a/esp32-cam-rtos/library.json b/esp32-cam-rtos/library.json new file mode 100644 index 0000000..322e932 --- /dev/null +++ b/esp32-cam-rtos/library.json @@ -0,0 +1,26 @@ +{ + "name": "esp32-camera", + "version": "1.0.0", + "keywords": "esp32, camera, espressif, esp32-cam", + "description": "ESP32 compatible driver for OV2640, OV3660, OV5640, OV7670 and OV7725 image sensors.", + "repository": { + "type": "git", + "url": "https://github.com/espressif/esp32-camera" + }, + "frameworks": "espidf", + "platforms": "*", + "build": { + "flags": [ + "-Idriver/include", + "-Iconversions/include", + "-Idriver/private_include", + "-Iconversions/private_include", + "-Isensors/private_include", + "-Itarget/private_include", + "-fno-rtti" + ], + "includeDir": ".", + "srcDir": ".", + "srcFilter": ["-<*>", "+", "+", "+"] + } +} diff --git a/esp32-cam-rtos/ll_cam.c b/esp32-cam-rtos/ll_cam.c new file mode 100644 index 0000000..8dc1fc6 --- /dev/null +++ b/esp32-cam-rtos/ll_cam.c @@ -0,0 +1,515 @@ +// Copyright 2010-2020 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 "soc/i2s_struct.h" +#include "esp_idf_version.h" +#if ESP_IDF_VERSION_MAJOR >= 4 +#include "hal/gpio_ll.h" +#else +#include "rom/ets_sys.h" +#include "soc/gpio_periph.h" +#define esp_rom_delay_us ets_delay_us +static inline int gpio_ll_get_level(gpio_dev_t *hw, int gpio_num) +{ + if (gpio_num < 32) { + return (hw->in >> gpio_num) & 0x1; + } else { + return (hw->in1.data >> (gpio_num - 32)) & 0x1; + } +} +#endif +#include "ll_cam.h" +#include "xclk.h" +#include "cam_hal.h" + +static const char *TAG = "esp32 ll_cam"; + +#define I2S_ISR_ENABLE(i) {I2S0.int_clr.i = 1;I2S0.int_ena.i = 1;} +#define I2S_ISR_DISABLE(i) {I2S0.int_ena.i = 0;I2S0.int_clr.i = 1;} + +typedef union { + struct { + uint32_t sample2:8; + uint32_t unused2:8; + uint32_t sample1:8; + uint32_t unused1:8; + }; + 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; + +typedef size_t (*dma_filter_t)(uint8_t* dst, const uint8_t* src, size_t len); + +static i2s_sampling_mode_t sampling_mode = SM_0A00_0B00; + +static size_t ll_cam_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 size_t IRAM_ATTR ll_cam_dma_filter_jpeg(uint8_t* dst, const uint8_t* src, size_t len) +{ + const dma_elem_t* dma_el = (const dma_elem_t*)src; + size_t elements = len / sizeof(dma_elem_t); + size_t end = elements / 4; + // manually unrolling 4 iterations of the loop here + for (size_t i = 0; i < end; ++i) { + dst[0] = dma_el[0].sample1; + dst[1] = dma_el[1].sample1; + dst[2] = dma_el[2].sample1; + dst[3] = dma_el[3].sample1; + dma_el += 4; + dst += 4; + } + return elements; +} + +static size_t IRAM_ATTR ll_cam_dma_filter_grayscale(uint8_t* dst, const uint8_t* src, size_t len) +{ + const dma_elem_t* dma_el = (const dma_elem_t*)src; + size_t elements = len / sizeof(dma_elem_t); + size_t end = elements / 4; + for (size_t i = 0; i < end; ++i) { + // manually unrolling 4 iterations of the loop here + dst[0] = dma_el[0].sample1; + dst[1] = dma_el[1].sample1; + dst[2] = dma_el[2].sample1; + dst[3] = dma_el[3].sample1; + dma_el += 4; + dst += 4; + } + return elements; +} + +static size_t IRAM_ATTR ll_cam_dma_filter_grayscale_highspeed(uint8_t* dst, const uint8_t* src, size_t len) +{ + const dma_elem_t* dma_el = (const dma_elem_t*)src; + size_t elements = len / sizeof(dma_elem_t); + size_t end = elements / 8; + for (size_t i = 0; i < end; ++i) { + // manually unrolling 4 iterations of the loop here + dst[0] = dma_el[0].sample1; + dst[1] = dma_el[2].sample1; + dst[2] = dma_el[4].sample1; + dst[3] = dma_el[6].sample1; + dma_el += 8; + dst += 4; + } + // the final sample of a line in SM_0A0B_0B0C sampling mode needs special handling + if ((elements & 0x7) != 0) { + dst[0] = dma_el[0].sample1; + dst[1] = dma_el[2].sample1; + elements += 1; + } + return elements / 2; +} + +static size_t IRAM_ATTR ll_cam_dma_filter_yuyv(uint8_t* dst, const uint8_t* src, size_t len) +{ + const dma_elem_t* dma_el = (const dma_elem_t*)src; + size_t elements = len / sizeof(dma_elem_t); + size_t end = elements / 4; + for (size_t i = 0; i < end; ++i) { + dst[0] = dma_el[0].sample1;//y0 + dst[1] = dma_el[0].sample2;//u + dst[2] = dma_el[1].sample1;//y1 + dst[3] = dma_el[1].sample2;//v + + dst[4] = dma_el[2].sample1;//y0 + dst[5] = dma_el[2].sample2;//u + dst[6] = dma_el[3].sample1;//y1 + dst[7] = dma_el[3].sample2;//v + dma_el += 4; + dst += 8; + } + return elements * 2; +} + +static size_t IRAM_ATTR ll_cam_dma_filter_yuyv_highspeed(uint8_t* dst, const uint8_t* src, size_t len) +{ + const dma_elem_t* dma_el = (const dma_elem_t*)src; + size_t elements = len / sizeof(dma_elem_t); + size_t end = elements / 8; + for (size_t i = 0; i < end; ++i) { + dst[0] = dma_el[0].sample1;//y0 + dst[1] = dma_el[1].sample1;//u + dst[2] = dma_el[2].sample1;//y1 + dst[3] = dma_el[3].sample1;//v + + dst[4] = dma_el[4].sample1;//y0 + dst[5] = dma_el[5].sample1;//u + dst[6] = dma_el[6].sample1;//y1 + dst[7] = dma_el[7].sample1;//v + dma_el += 8; + dst += 8; + } + if ((elements & 0x7) != 0) { + dst[0] = dma_el[0].sample1;//y0 + dst[1] = dma_el[1].sample1;//u + dst[2] = dma_el[2].sample1;//y1 + dst[3] = dma_el[2].sample2;//v + elements += 4; + } + return elements; +} + +static void IRAM_ATTR ll_cam_vsync_isr(void *arg) +{ + //DBG_PIN_SET(1); + cam_obj_t *cam = (cam_obj_t *)arg; + BaseType_t HPTaskAwoken = pdFALSE; + // filter + esp_rom_delay_us(1); + if (gpio_ll_get_level(&GPIO, cam->vsync_pin) == !cam->vsync_invert) { + ll_cam_send_event(cam, CAM_VSYNC_EVENT, &HPTaskAwoken); + if (HPTaskAwoken == pdTRUE) { + portYIELD_FROM_ISR(); + } + } + //DBG_PIN_SET(0); +} + +static void IRAM_ATTR ll_cam_dma_isr(void *arg) +{ + //DBG_PIN_SET(1); + cam_obj_t *cam = (cam_obj_t *)arg; + BaseType_t HPTaskAwoken = pdFALSE; + + typeof(I2S0.int_st) status = I2S0.int_st; + if (status.val == 0) { + return; + } + + I2S0.int_clr.val = status.val; + + if (status.in_suc_eof) { + ll_cam_send_event(cam, CAM_IN_SUC_EOF_EVENT, &HPTaskAwoken); + } + if (HPTaskAwoken == pdTRUE) { + portYIELD_FROM_ISR(); + } + //DBG_PIN_SET(0); +} + +bool ll_cam_stop(cam_obj_t *cam) +{ + I2S0.conf.rx_start = 0; + I2S_ISR_DISABLE(in_suc_eof); + I2S0.in_link.stop = 1; + return true; +} + +bool ll_cam_start(cam_obj_t *cam, int frame_pos) +{ + I2S0.conf.rx_start = 0; + + I2S_ISR_ENABLE(in_suc_eof); + + I2S0.conf.rx_reset = 1; + I2S0.conf.rx_reset = 0; + I2S0.conf.rx_fifo_reset = 1; + I2S0.conf.rx_fifo_reset = 0; + I2S0.lc_conf.in_rst = 1; + I2S0.lc_conf.in_rst = 0; + I2S0.lc_conf.ahbm_fifo_rst = 1; + I2S0.lc_conf.ahbm_fifo_rst = 0; + I2S0.lc_conf.ahbm_rst = 1; + I2S0.lc_conf.ahbm_rst = 0; + + I2S0.rx_eof_num = cam->dma_half_buffer_size / sizeof(dma_elem_t); + I2S0.in_link.addr = ((uint32_t)&cam->dma[0]) & 0xfffff; + + I2S0.in_link.start = 1; + I2S0.conf.rx_start = 1; + return true; +} + +esp_err_t ll_cam_config(cam_obj_t *cam, const camera_config_t *config) +{ + // Enable and configure I2S peripheral + periph_module_enable(PERIPH_I2S0_MODULE); + + I2S0.conf.rx_reset = 1; + I2S0.conf.rx_reset = 0; + I2S0.conf.rx_fifo_reset = 1; + I2S0.conf.rx_fifo_reset = 0; + I2S0.lc_conf.in_rst = 1; + I2S0.lc_conf.in_rst = 0; + I2S0.lc_conf.ahbm_fifo_rst = 1; + I2S0.lc_conf.ahbm_fifo_rst = 0; + I2S0.lc_conf.ahbm_rst = 1; + I2S0.lc_conf.ahbm_rst = 0; + + I2S0.conf.rx_slave_mod = 1; + 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.conf2.lcd_en = 1; + I2S0.conf2.camera_en = 1; + + // Configure clock divider + I2S0.clkm_conf.clkm_div_a = 0; + I2S0.clkm_conf.clkm_div_b = 0; + I2S0.clkm_conf.clkm_div_num = 2; + + I2S0.fifo_conf.dscr_en = 1; + I2S0.fifo_conf.rx_fifo_mod = sampling_mode; + I2S0.fifo_conf.rx_fifo_mod_force_en = 1; + + I2S0.conf_chan.rx_chan_mod = 1; + I2S0.sample_rate_conf.rx_bits_mod = 0; + I2S0.timing.val = 0; + I2S0.timing.rx_dsync_sw = 1; + + return ESP_OK; +} + +void ll_cam_vsync_intr_enable(cam_obj_t *cam, bool en) +{ + if (en) { + gpio_intr_enable(cam->vsync_pin); + } else { + gpio_intr_disable(cam->vsync_pin); + } +} + +esp_err_t ll_cam_set_pin(cam_obj_t *cam, const camera_config_t *config) +{ + gpio_config_t io_conf = {0}; + io_conf.intr_type = cam->vsync_invert ? GPIO_PIN_INTR_NEGEDGE : GPIO_PIN_INTR_POSEDGE; + io_conf.pin_bit_mask = 1ULL << config->pin_vsync; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pull_up_en = 1; + io_conf.pull_down_en = 0; + gpio_config(&io_conf); + gpio_install_isr_service(ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM); + gpio_isr_handler_add(config->pin_vsync, ll_cam_vsync_isr, cam); + gpio_intr_disable(config->pin_vsync); + + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[config->pin_pclk], PIN_FUNC_GPIO); + gpio_set_direction(config->pin_pclk, GPIO_MODE_INPUT); + gpio_set_pull_mode(config->pin_pclk, GPIO_FLOATING); + gpio_matrix_in(config->pin_pclk, I2S0I_WS_IN_IDX, false); + + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[config->pin_vsync], PIN_FUNC_GPIO); + gpio_set_direction(config->pin_vsync, GPIO_MODE_INPUT); + gpio_set_pull_mode(config->pin_vsync, GPIO_FLOATING); + gpio_matrix_in(config->pin_vsync, I2S0I_V_SYNC_IDX, false); + + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[config->pin_href], PIN_FUNC_GPIO); + gpio_set_direction(config->pin_href, GPIO_MODE_INPUT); + gpio_set_pull_mode(config->pin_href, GPIO_FLOATING); + gpio_matrix_in(config->pin_href, I2S0I_H_SYNC_IDX, false); + + int data_pins[8] = { + config->pin_d0, config->pin_d1, config->pin_d2, config->pin_d3, config->pin_d4, config->pin_d5, config->pin_d6, config->pin_d7, + }; + for (int i = 0; i < 8; i++) { + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[data_pins[i]], PIN_FUNC_GPIO); + gpio_set_direction(data_pins[i], GPIO_MODE_INPUT); + gpio_set_pull_mode(data_pins[i], GPIO_FLOATING); + gpio_matrix_in(data_pins[i], I2S0I_DATA_IN0_IDX + i, false); + } + + gpio_matrix_in(0x38, I2S0I_H_ENABLE_IDX, false); + return ESP_OK; +} + +esp_err_t ll_cam_init_isr(cam_obj_t *cam) +{ + return esp_intr_alloc(ETS_I2S0_INTR_SOURCE, ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM, ll_cam_dma_isr, cam, &cam->cam_intr_handle); +} + +void ll_cam_do_vsync(cam_obj_t *cam) +{ +} + +uint8_t ll_cam_get_dma_align(cam_obj_t *cam) +{ + return 0; +} + +static bool ll_cam_calc_rgb_dma(cam_obj_t *cam){ + size_t dma_half_buffer_max = 16 * 1024 / cam->dma_bytes_per_item; + size_t dma_buffer_max = 2 * dma_half_buffer_max; + size_t node_max = LCD_CAM_DMA_NODE_BUFFER_MAX_SIZE / cam->dma_bytes_per_item; + + size_t line_width = cam->width * cam->in_bytes_per_pixel; + size_t image_size = cam->height * line_width; + if (image_size > (2 * 1024 * 1024) || (line_width > dma_half_buffer_max)) { + ESP_LOGE(TAG, "Resolution too high"); + return 0; + } + + size_t node_size = node_max; + size_t nodes_per_line = 1; + size_t lines_per_node = 1; + size_t lines_per_half_buffer = 1; + size_t dma_half_buffer_min = node_max; + size_t dma_half_buffer = dma_half_buffer_max; + size_t dma_buffer_size = dma_buffer_max; + + // Calculate DMA Node Size so that it's divisable by or divisor of the line width + if(line_width >= node_max){ + // One or more nodes will be requied for one line + for(size_t i = node_max; i > 0; i=i-1){ + if ((line_width % i) == 0) { + node_size = i; + nodes_per_line = line_width / node_size; + break; + } + } + } else { + // One or more lines can fit into one node + for(size_t i = node_max; i > 0; i=i-1){ + if ((i % line_width) == 0) { + node_size = i; + lines_per_node = node_size / line_width; + while((cam->height % lines_per_node) != 0){ + lines_per_node = lines_per_node - 1; + node_size = lines_per_node * line_width; + } + break; + } + } + } + // Calculate minimum EOF size = max(mode_size, line_size) + dma_half_buffer_min = node_size * nodes_per_line; + // Calculate max EOF size divisable by node size + dma_half_buffer = (dma_half_buffer_max / dma_half_buffer_min) * dma_half_buffer_min; + // Adjust EOF size so that height will be divisable by the number of lines in each EOF + lines_per_half_buffer = dma_half_buffer / line_width; + while((cam->height % lines_per_half_buffer) != 0){ + dma_half_buffer = dma_half_buffer - dma_half_buffer_min; + lines_per_half_buffer = dma_half_buffer / line_width; + } + // Calculate DMA size + dma_buffer_size =(dma_buffer_max / dma_half_buffer) * dma_half_buffer; + + ESP_LOGI(TAG, "node_size: %4u, nodes_per_line: %u, lines_per_node: %u, dma_half_buffer_min: %5u, dma_half_buffer: %5u, lines_per_half_buffer: %2u, dma_buffer_size: %5u, image_size: %u", + node_size * cam->dma_bytes_per_item, nodes_per_line, lines_per_node, dma_half_buffer_min * cam->dma_bytes_per_item, dma_half_buffer * cam->dma_bytes_per_item, lines_per_half_buffer, dma_buffer_size * cam->dma_bytes_per_item, image_size); + + cam->dma_buffer_size = dma_buffer_size * cam->dma_bytes_per_item; + cam->dma_half_buffer_size = dma_half_buffer * cam->dma_bytes_per_item; + cam->dma_node_buffer_size = node_size * cam->dma_bytes_per_item; + cam->dma_half_buffer_cnt = cam->dma_buffer_size / cam->dma_half_buffer_size; + return 1; +} + +bool ll_cam_dma_sizes(cam_obj_t *cam) +{ + cam->dma_bytes_per_item = ll_cam_bytes_per_sample(sampling_mode); + if (cam->jpeg_mode) { + cam->dma_half_buffer_cnt = 8; + cam->dma_node_buffer_size = 2048; + cam->dma_half_buffer_size = cam->dma_node_buffer_size * 2; + cam->dma_buffer_size = cam->dma_half_buffer_cnt * cam->dma_half_buffer_size; + } else { + return ll_cam_calc_rgb_dma(cam); + } + return 1; +} + +static dma_filter_t dma_filter = ll_cam_dma_filter_jpeg; + +size_t IRAM_ATTR ll_cam_memcpy(cam_obj_t *cam, uint8_t *out, const uint8_t *in, size_t len) +{ + //DBG_PIN_SET(1); + size_t r = dma_filter(out, in, len); + //DBG_PIN_SET(0); + return r; +} + +esp_err_t ll_cam_set_sample_mode(cam_obj_t *cam, pixformat_t pix_format, uint32_t xclk_freq_hz, uint8_t sensor_pid) +{ + if (pix_format == PIXFORMAT_GRAYSCALE) { + if (sensor_pid == OV3660_PID || sensor_pid == OV5640_PID || sensor_pid == NT99141_PID) { + if (xclk_freq_hz > 10000000) { + sampling_mode = SM_0A00_0B00; + dma_filter = ll_cam_dma_filter_yuyv_highspeed; + } else { + sampling_mode = SM_0A0B_0C0D; + dma_filter = ll_cam_dma_filter_yuyv; + } + cam->in_bytes_per_pixel = 1; // camera sends Y8 + } else { + if (xclk_freq_hz > 10000000 && sensor_pid != OV7725_PID) { + sampling_mode = SM_0A00_0B00; + dma_filter = ll_cam_dma_filter_grayscale_highspeed; + } else { + sampling_mode = SM_0A0B_0C0D; + dma_filter = ll_cam_dma_filter_grayscale; + } + cam->in_bytes_per_pixel = 2; // camera sends YU/YV + } + cam->fb_bytes_per_pixel = 1; // frame buffer stores Y8 + } else if (pix_format == PIXFORMAT_YUV422 || pix_format == PIXFORMAT_RGB565) { + if (xclk_freq_hz > 10000000 && sensor_pid != OV7725_PID) { + if (sensor_pid == OV7670_PID) { + sampling_mode = SM_0A0B_0B0C; + } else { + sampling_mode = SM_0A00_0B00; + } + dma_filter = ll_cam_dma_filter_yuyv_highspeed; + } else { + sampling_mode = SM_0A0B_0C0D; + dma_filter = ll_cam_dma_filter_yuyv; + } + cam->in_bytes_per_pixel = 2; // camera sends YU/YV + cam->fb_bytes_per_pixel = 2; // frame buffer stores YU/YV/RGB565 + } else if (pix_format == PIXFORMAT_JPEG) { + if (sensor_pid != OV2640_PID && sensor_pid != OV3660_PID && sensor_pid != OV5640_PID && sensor_pid != NT99141_PID) { + ESP_LOGE(TAG, "JPEG format is not supported on this sensor"); + return ESP_ERR_NOT_SUPPORTED; + } + cam->in_bytes_per_pixel = 1; + cam->fb_bytes_per_pixel = 1; + dma_filter = ll_cam_dma_filter_jpeg; + sampling_mode = SM_0A00_0B00; + } else { + ESP_LOGE(TAG, "Requested format is not supported"); + return ESP_ERR_NOT_SUPPORTED; + } + I2S0.fifo_conf.rx_fifo_mod = sampling_mode; + return ESP_OK; +} diff --git a/esp32-cam-rtos/ll_cam.h b/esp32-cam-rtos/ll_cam.h new file mode 100644 index 0000000..0df922d --- /dev/null +++ b/esp32-cam-rtos/ll_cam.h @@ -0,0 +1,136 @@ +// Copyright 2010-2020 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. + +#pragma once + +#include +#include "sdkconfig.h" +#if CONFIG_IDF_TARGET_ESP32 +#if ESP_IDF_VERSION_MAJOR >= 4 +#include "esp32/rom/lldesc.h" +#else +#include "rom/lldesc.h" +#endif +#elif CONFIG_IDF_TARGET_ESP32S2 +#include "esp32s2/rom/lldesc.h" +#elif CONFIG_IDF_TARGET_ESP32S3 +#include "esp32s3/rom/lldesc.h" +#endif +#include "esp_log.h" +#include "esp_camera.h" +#include "camera_common.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/task.h" +#include "freertos/semphr.h" + +#define CAMERA_DBG_PIN_ENABLE 0 +#if CAMERA_DBG_PIN_ENABLE + #if CONFIG_IDF_TARGET_ESP32 + #define DBG_PIN_NUM 26 + #else + #define DBG_PIN_NUM 7 + #endif + #include "hal/gpio_ll.h" + #define DBG_PIN_SET(v) gpio_ll_set_level(&GPIO, DBG_PIN_NUM, v) +#else + #define DBG_PIN_SET(v) +#endif + +#define CAM_CHECK(a, str, ret) if (!(a)) { \ + ESP_LOGE(TAG,"%s(%d): %s", __FUNCTION__, __LINE__, str); \ + return (ret); \ + } + +#define CAM_CHECK_GOTO(a, str, lab) if (!(a)) { \ + ESP_LOGE(TAG,"%s(%d): %s", __FUNCTION__, __LINE__, str); \ + goto lab; \ + } + +#define LCD_CAM_DMA_NODE_BUFFER_MAX_SIZE (4092) + +typedef enum { + CAM_IN_SUC_EOF_EVENT = 0, + CAM_VSYNC_EVENT +} cam_event_t; + +typedef enum { + CAM_STATE_IDLE = 0, + CAM_STATE_READ_BUF = 1, +} cam_state_t; + +typedef struct { + camera_fb_t fb; + uint8_t en; + //for RGB/YUV modes + lldesc_t *dma; + size_t fb_offset; +} cam_frame_t; + +typedef struct { + uint32_t dma_bytes_per_item; + uint32_t dma_buffer_size; + uint32_t dma_half_buffer_size; + uint32_t dma_half_buffer_cnt; + uint32_t dma_node_buffer_size; + uint32_t dma_node_cnt; + uint32_t frame_copy_cnt; + + //for JPEG mode + lldesc_t *dma; + uint8_t *dma_buffer; + + cam_frame_t *frames; + + QueueHandle_t event_queue; + QueueHandle_t frame_buffer_queue; + TaskHandle_t task_handle; + intr_handle_t cam_intr_handle; + + uint8_t dma_num;//ESP32-S3 + intr_handle_t dma_intr_handle;//ESP32-S3 + + uint8_t jpeg_mode; + uint8_t vsync_pin; + uint8_t vsync_invert; + uint32_t frame_cnt; + uint32_t recv_size; + bool swap_data; + bool psram_mode; + + //for RGB/YUV modes + uint16_t width; + uint16_t height; + uint8_t in_bytes_per_pixel; + uint8_t fb_bytes_per_pixel; + uint32_t fb_size; + + cam_state_t state; +} cam_obj_t; + + +bool ll_cam_stop(cam_obj_t *cam); +bool ll_cam_start(cam_obj_t *cam, int frame_pos); +esp_err_t ll_cam_config(cam_obj_t *cam, const camera_config_t *config); +void ll_cam_vsync_intr_enable(cam_obj_t *cam, bool en); +esp_err_t ll_cam_set_pin(cam_obj_t *cam, const camera_config_t *config); +esp_err_t ll_cam_init_isr(cam_obj_t *cam); +void ll_cam_do_vsync(cam_obj_t *cam); +uint8_t ll_cam_get_dma_align(cam_obj_t *cam); +bool ll_cam_dma_sizes(cam_obj_t *cam); +size_t ll_cam_memcpy(cam_obj_t *cam, uint8_t *out, const uint8_t *in, size_t len); +esp_err_t ll_cam_set_sample_mode(cam_obj_t *cam, pixformat_t pix_format, uint32_t xclk_freq_hz, uint8_t sensor_pid); + +// implemented in cam_hal +void ll_cam_send_event(cam_obj_t *cam, cam_event_t cam_event, BaseType_t * HPTaskAwoken); diff --git a/esp32-cam-rtos/nt99141.c b/esp32-cam-rtos/nt99141.c index 07a9cc4..450780b 100644 --- a/esp32-cam-rtos/nt99141.c +++ b/esp32-cam-rtos/nt99141.c @@ -144,28 +144,6 @@ static int write_addr_reg(uint8_t slv_addr, const uint16_t reg, uint16_t x_value #define write_reg_bits(slv_addr, reg, mask, enable) set_reg_bits(slv_addr, reg, 0, mask, enable?mask:0) -static int calc_sysclk(int xclk, bool pll_bypass, int pll_multiplier, int pll_sys_div, int pll_pre_div, bool pll_root_2x, int pll_seld5, bool pclk_manual, int pclk_div) -{ - const int pll_pre_div2x_map[] = { 2, 3, 4, 6 };//values are multiplied by two to avoid floats - const int pll_seld52x_map[] = { 2, 2, 4, 5 }; - - if (!pll_sys_div) { - pll_sys_div = 1; - } - - int pll_pre_div2x = pll_pre_div2x_map[pll_pre_div]; - int pll_root_div = pll_root_2x ? 2 : 1; - int pll_seld52x = pll_seld52x_map[pll_seld5]; - - int VCO = (xclk / 1000) * pll_multiplier * pll_root_div * 2 / pll_pre_div2x; - int PLLCLK = pll_bypass ? (xclk) : (VCO * 1000 * 2 / pll_sys_div / pll_seld52x); - int PCLK = PLLCLK / 2 / ((pclk_manual && pclk_div) ? pclk_div : 1); - int SYSCLK = PLLCLK / 4; - - ESP_LOGD(TAG, "Calculated VCO: %d Hz, PLLCLK: %d Hz, SYSCLK: %d Hz, PCLK: %d Hz", VCO * 1000, PLLCLK, SYSCLK, PCLK); - return SYSCLK; -} - static int set_pll(sensor_t *sensor, bool bypass, uint8_t multiplier, uint8_t sys_div, uint8_t pre_div, bool root_2x, uint8_t seld5, bool pclk_manual, uint8_t pclk_div) { return -1; @@ -309,7 +287,7 @@ static int set_framesize(sensor_t *sensor, framesize_t framesize) ret = write_regs(sensor->slv_addr, sensor_framesize_VGA); } - return 0; + return ret; } static int set_hmirror(sensor_t *sensor, int enable) @@ -682,7 +660,6 @@ static int set_brightness(sensor_t *sensor, int level) { int ret = 0; uint8_t value = 0; - bool negative = false; switch (level) { case 3: @@ -699,17 +676,14 @@ static int set_brightness(sensor_t *sensor, int level) case -1: value = 0x78; - negative = true; break; case -2: value = 0x70; - negative = true; break; case -3: value = 0x60; - negative = true; break; default: // 0 @@ -730,7 +704,6 @@ static int set_contrast(sensor_t *sensor, int level) { int ret = 0; uint8_t value1 = 0, value2 = 0 ; - bool negative = false; switch (level) { case 3: diff --git a/esp32-cam-rtos/ov2640.c b/esp32-cam-rtos/ov2640.c index 811023c..e96d4c2 100644 --- a/esp32-cam-rtos/ov2640.c +++ b/esp32-cam-rtos/ov2640.c @@ -157,26 +157,40 @@ static int set_window(sensor_t *sensor, ov2640_sensor_mode_t mode, int offset_x, {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; + if (sensor->pixformat == PIXFORMAT_JPEG) { + c.clk_2x = 0; + c.clk_div = 0; + c.pclk_auto = 0; + c.pclk_div = 8; + if(mode == OV2640_MODE_UXGA) { + c.pclk_div = 12; + } + // if (sensor->xclk_freq_hz == 16000000) { + // c.pclk_div = c.pclk_div / 2; + // } + } else { +#if CONFIG_IDF_TARGET_ESP32 + c.clk_2x = 0; +#else + c.clk_2x = 1; +#endif c.clk_div = 7; + c.pclk_auto = 1; + c.pclk_div = 8; + if (mode == OV2640_MODE_CIF) { + c.clk_div = 3; + } else if(mode == OV2640_MODE_UXGA) { + c.pclk_div = 12; + } } + ESP_LOGI(TAG, "Set PLL: clk_2x: %u, clk_div: %u, pclk_auto: %u, pclk_div: %u", c.clk_2x, c.clk_div, c.pclk_auto, c.pclk_div); 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); diff --git a/esp32-cam-rtos/ov3660.c b/esp32-cam-rtos/ov3660.c index 723ec5c..59393b7 100644 --- a/esp32-cam-rtos/ov3660.c +++ b/esp32-cam-rtos/ov3660.c @@ -142,7 +142,7 @@ static int calc_sysclk(int xclk, bool pll_bypass, int pll_multiplier, int pll_sy 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); + ESP_LOGI(TAG, "Calculated VCO: %d Hz, PLLCLK: %d Hz, SYSCLK: %d Hz, PCLK: %d Hz", VCO*1000, PLLCLK, SYSCLK, PCLK); return SYSCLK; } @@ -310,13 +310,13 @@ static int set_image_options(sensor_t *sensor) 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; + ESP_LOGW(TAG, "Invalid framesize: %u", framesize); + framesize = FRAMESIZE_QXGA; } + framesize_t old_framesize = sensor->status.framesize; + sensor->status.framesize = framesize; uint16_t w = resolution[framesize].width; uint16_t h = resolution[framesize].height; aspect_ratio_t ratio = resolution[sensor->status.framesize].aspect_ratio; @@ -355,7 +355,7 @@ static int set_framesize(sensor_t *sensor, framesize_t framesize) } if (sensor->pixformat == PIXFORMAT_JPEG) { - if (framesize == FRAMESIZE_QXGA) { + if (framesize == FRAMESIZE_QXGA || sensor->xclk_freq_hz == 16000000) { //40MHz SYSCLK and 10MHz PCLK ret = set_pll(sensor, false, 24, 1, 3, false, 0, true, 8); } else { @@ -363,12 +363,16 @@ static int set_framesize(sensor_t *sensor, framesize_t framesize) 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); + //tuned for 16MHz XCLK and 8MHz PCLK + if (framesize > FRAMESIZE_HVGA) { + //8MHz SYSCLK and 8MHz PCLK (4.44 FPS) + ret = set_pll(sensor, false, 4, 1, 0, false, 2, true, 2); + } else if (framesize >= FRAMESIZE_QVGA) { + //16MHz SYSCLK and 8MHz PCLK (10.25 FPS) + ret = set_pll(sensor, false, 8, 1, 0, false, 2, true, 4); } else { - //25MHz SYSCLK and 10MHz PCLK (15.45 FPS) - ret = set_pll(sensor, false, 5, 1, 0, false, 0, true, 5); + //32MHz SYSCLK and 8MHz PCLK (17.77 FPS) + ret = set_pll(sensor, false, 8, 1, 0, false, 0, true, 8); } } diff --git a/esp32-cam-rtos/ov5640.c b/esp32-cam-rtos/ov5640.c index e7adcf4..a9ab2a8 100644 --- a/esp32-cam-rtos/ov5640.c +++ b/esp32-cam-rtos/ov5640.c @@ -196,7 +196,7 @@ static int calc_sysclk(int xclk, bool pll_bypass, int pll_multiplier, int pll_sy 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); + ESP_LOGI(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; } @@ -209,6 +209,7 @@ static int set_pll(sensor_t *sensor, bool bypass, uint8_t multiplier, uint8_t sy if(multiplier > 127){ multiplier &= 0xFE;//only even integers above 127 } + ESP_LOGI(TAG, "Set PLL: bypass: %u, multiplier: %u, sys_div: %u, pre_div: %u, root_2x: %u, pclk_root_div: %u, pclk_manual: %u, pclk_div: %u", bypass, multiplier, sys_div, pre_div, root_2x, pclk_root_div, pclk_manual, pclk_div); calc_sysclk(sensor->xclk_freq_hz, bypass, multiplier, sys_div, pre_div, root_2x, pclk_root_div, pclk_manual, pclk_div); @@ -432,14 +433,22 @@ static int set_framesize(sensor_t *sensor, framesize_t framesize) if (sensor->pixformat == PIXFORMAT_JPEG) { //10MHz PCLK uint8_t sys_mul = 200; - if(framesize < FRAMESIZE_QVGA){ + if(framesize < FRAMESIZE_QVGA || sensor->xclk_freq_hz == 16000000){ sys_mul = 160; } else if(framesize < FRAMESIZE_XGA){ sys_mul = 180; } ret = set_pll(sensor, false, sys_mul, 4, 2, false, 2, true, 4); + //Set PLL: bypass: 0, multiplier: sys_mul, sys_div: 4, pre_div: 2, root_2x: 0, pclk_root_div: 2, pclk_manual: 1, pclk_div: 4 } else { - ret = set_pll(sensor, false, 10, 1, 1, false, 1, true, 4); + //ret = set_pll(sensor, false, 8, 1, 1, false, 1, true, 4); + if (framesize > FRAMESIZE_HVGA) { + ret = set_pll(sensor, false, 10, 1, 2, false, 1, true, 2); + } else if (framesize >= FRAMESIZE_QVGA) { + ret = set_pll(sensor, false, 8, 1, 1, false, 1, true, 4); + } else { + ret = set_pll(sensor, false, 20, 1, 1, false, 1, true, 8); + } } if (ret == 0) { diff --git a/esp32-cam-rtos/sccb.c b/esp32-cam-rtos/sccb.c index cb615bb..1a2c56e 100644 --- a/esp32-cam-rtos/sccb.c +++ b/esp32-cam-rtos/sccb.c @@ -11,6 +11,7 @@ #include #include #include "sccb.h" +#include "sensor.h" #include #include "sdkconfig.h" #if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) @@ -36,12 +37,10 @@ const int SCCB_I2C_PORT = 1; #else const int SCCB_I2C_PORT = 0; #endif -static uint8_t ESP_SLAVE_ADDR = 0x3c; int SCCB_Init(int pin_sda, int pin_scl) { - ESP_LOGI(TAG, "pin_sda %d pin_scl %d\n", pin_sda, pin_scl); - //log_i("SCCB_Init start"); + ESP_LOGI(TAG, "pin_sda %d pin_scl %d", pin_sda, pin_scl); i2c_config_t conf; memset(&conf, 0, sizeof(i2c_config_t)); conf.mode = I2C_MODE_MASTER; @@ -56,10 +55,30 @@ int SCCB_Init(int pin_sda, int pin_scl) return 0; } -uint8_t SCCB_Probe() +int SCCB_Deinit(void) +{ + return i2c_driver_delete(SCCB_I2C_PORT); +} + +uint8_t SCCB_Probe(void) { uint8_t slave_addr = 0x0; - while(slave_addr < 0x7f) { + // for (size_t i = 1; i < 0x80; i++) { + // i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + // i2c_master_start(cmd); + // i2c_master_write_byte(cmd, ( i << 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_LOGW(TAG, "Found I2C Device at 0x%02X", i); + // } + // } + for (size_t i = 0; i < CAMERA_MODEL_MAX; i++) { + if (slave_addr == camera_sensor[i].sccb_addr) { + continue; + } + slave_addr = camera_sensor[i].sccb_addr; 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); @@ -67,12 +86,10 @@ uint8_t SCCB_Probe() 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; + return slave_addr; } - slave_addr++; } - return ESP_SLAVE_ADDR; + return 0; } uint8_t SCCB_Read(uint8_t slv_addr, uint8_t reg) diff --git a/esp32-cam-rtos/sccb.h b/esp32-cam-rtos/sccb.h index 4d5b5b4..ace081a 100644 --- a/esp32-cam-rtos/sccb.h +++ b/esp32-cam-rtos/sccb.h @@ -10,6 +10,7 @@ #define __SCCB_H__ #include int SCCB_Init(int pin_sda, int pin_scl); +int SCCB_Deinit(void); 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); diff --git a/esp32-cam-rtos/sensor.c b/esp32-cam-rtos/sensor.c index 2e6d111..15714aa 100644 --- a/esp32-cam-rtos/sensor.c +++ b/esp32-cam-rtos/sensor.c @@ -1,5 +1,14 @@ #include "sensor.h" +const camera_sensor_info_t camera_sensor[CAMERA_MODEL_MAX] = { + {CAMERA_OV7725, OV7725_SCCB_ADDR, OV7725_PID, FRAMESIZE_VGA}, + {CAMERA_OV2640, OV2640_SCCB_ADDR, OV2640_PID, FRAMESIZE_UXGA}, + {CAMERA_OV3660, OV3660_SCCB_ADDR, OV3660_PID, FRAMESIZE_QXGA}, + {CAMERA_OV5640, OV5640_SCCB_ADDR, OV5640_PID, FRAMESIZE_QSXGA}, + {CAMERA_OV7670, OV7670_SCCB_ADDR, OV7670_PID, FRAMESIZE_VGA}, + {CAMERA_NT99141, NT99141_SCCB_ADDR, NT99141_PID, FRAMESIZE_HD}, +}; + const resolution_info_t resolution[FRAMESIZE_INVALID] = { { 96, 96, ASPECT_RATIO_1X1 }, /* 96x96 */ { 160, 120, ASPECT_RATIO_4X3 }, /* QQVGA */ diff --git a/esp32-cam-rtos/sensor.h b/esp32-cam-rtos/sensor.h index ad6cd89..ac54731 100644 --- a/esp32-cam-rtos/sensor.h +++ b/esp32-cam-rtos/sensor.h @@ -11,13 +11,45 @@ #include #include -#define NT99141_PID (0x14) -#define OV9650_PID (0x96) -#define OV7725_PID (0x77) -#define OV2640_PID (0x26) -#define OV3660_PID (0x36) -#define OV5640_PID (0x56) -#define OV7670_PID (0x76) +// Chip ID Registers +#define REG_PID 0x0A +#define REG_VER 0x0B +#define REG_MIDH 0x1C +#define REG_MIDL 0x1D + +#define REG16_CHIDH 0x300A +#define REG16_CHIDL 0x300B + +typedef enum { + OV9650_PID = 0x96, + OV7725_PID = 0x77, + OV2640_PID = 0x26, + OV3660_PID = 0x36, + OV5640_PID = 0x56, + OV7670_PID = 0x76, + NT99141_PID = 0x14 +} camera_pid_t; + +typedef enum { + CAMERA_OV7725, + CAMERA_OV2640, + CAMERA_OV3660, + CAMERA_OV5640, + CAMERA_OV7670, + CAMERA_NT99141, + CAMERA_MODEL_MAX, + CAMERA_NONE, + CAMERA_UNKNOWN +} camera_model_t; + +typedef enum { + OV2640_SCCB_ADDR = 0x30, + OV5640_SCCB_ADDR = 0x3C, + OV3660_SCCB_ADDR = 0x3C, + OV7725_SCCB_ADDR = 0x21, + OV7670_SCCB_ADDR = 0x21, + NT99141_SCCB_ADDR = 0x2A, +} camera_sccb_addr_t; typedef enum { PIXFORMAT_RGB565, // 2BPP/RGB565 @@ -58,6 +90,13 @@ typedef enum { FRAMESIZE_INVALID } framesize_t; +typedef struct { + const camera_model_t model; + const camera_sccb_addr_t sccb_addr; + const camera_pid_t pid; + const framesize_t max_size; +} camera_sensor_info_t; + typedef enum { ASPECT_RATIO_4X3, ASPECT_RATIO_3X2, @@ -101,6 +140,8 @@ typedef struct { // Resolution table (in sensor.c) extern const resolution_info_t resolution[]; +// camera sensor table (in sensor.c) +extern const camera_sensor_info_t camera_sensor[]; typedef struct { uint8_t MIDH; diff --git a/esp32-cam-rtos/to_bmp.c b/esp32-cam-rtos/to_bmp.c index 85f9c88..5a54bdb 100644 --- a/esp32-cam-rtos/to_bmp.c +++ b/esp32-cam-rtos/to_bmp.c @@ -24,6 +24,10 @@ #if ESP_IDF_VERSION_MAJOR >= 4 // IDF 4+ #if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 #include "esp32/spiram.h" +#elif CONFIG_IDF_TARGET_ESP32S2 +#include "esp32s2/spiram.h" +#elif CONFIG_IDF_TARGET_ESP32S3 +#include "esp32s3/spiram.h" #else #error Target CONFIG_IDF_TARGET is not supported #endif @@ -115,6 +119,54 @@ static bool _rgb_write(void * arg, uint16_t x, uint16_t y, uint16_t w, uint16_t return true; } +static bool _rgb565_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 jw2 = jpeg->width*2; + size_t t = y * jw; + size_t t2 = y * jw2; + size_t b = t + (h * jw); + size_t l = x * 2; + uint8_t *out = jpeg->output+jpeg->data_offset; + uint8_t *o = out; + size_t iy, iy2, ix, ix2; + + w = w * 3; + + for(iy=t, iy2=t2; iy> 3); + o[ix2+1] = c>>8; + o[ix2] = c&0xff; + } + data+=w; + } + return true; +} + //input buffer static uint32_t _jpg_read(void * arg, size_t index, uint8_t *buf, size_t len) { @@ -140,6 +192,21 @@ static bool jpg2rgb888(const uint8_t *src, size_t src_len, uint8_t * out, jpg_sc return true; } +bool jpg2rgb565(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, _rgb565_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) { diff --git a/esp32-cam-rtos/to_jpg.cpp b/esp32-cam-rtos/to_jpg.cpp index f8987a8..9b8905a 100644 --- a/esp32-cam-rtos/to_jpg.cpp +++ b/esp32-cam-rtos/to_jpg.cpp @@ -25,6 +25,10 @@ #if ESP_IDF_VERSION_MAJOR >= 4 // IDF 4+ #if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 #include "esp32/spiram.h" +#elif CONFIG_IDF_TARGET_ESP32S2 +#include "esp32s2/spiram.h" +#elif CONFIG_IDF_TARGET_ESP32S3 +#include "esp32s3/spiram.h" #else #error Target CONFIG_IDF_TARGET is not supported #endif @@ -195,7 +199,7 @@ public: return true; } if ((size_t)len > (max_len - index)) { - ESP_LOGW(TAG, "JPG output overflow: %d bytes", len - (max_len - index)); + //ESP_LOGW(TAG, "JPG output overflow: %d bytes (%d,%d,%d)", len - (max_len - index), len, index, max_len); len = max_len - index; } if (len) { @@ -215,7 +219,7 @@ bool fmt2jpg(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixf { //todo: allocate proper buffer for holding JPEG data //this should be enough for CIF frame size - int jpg_buf_len = 64*1024; + int jpg_buf_len = 128*1024; uint8_t * jpg_buf = (uint8_t *)_malloc(jpg_buf_len); diff --git a/esp32-cam-rtos/twi.c b/esp32-cam-rtos/twi.c deleted file mode 100644 index 25d71fc..0000000 --- a/esp32-cam-rtos/twi.c +++ /dev/null @@ -1,432 +0,0 @@ -/* - 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 @@ -41,11 +45,15 @@ esp_err_t camera_enable_out_clock(camera_config_t* config) ledc_channel_config_t ch_conf; ch_conf.gpio_num = config->pin_xclk; +#if CONFIG_IDF_TARGET_ESP32 ch_conf.speed_mode = LEDC_HIGH_SPEED_MODE; +#else + ch_conf.speed_mode = LEDC_LOW_SPEED_MODE; +#endif 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.duty = 1; ch_conf.hpoint = 0; err = ledc_channel_config(&ch_conf); if (err != ESP_OK) { diff --git a/esp32-cam/.gitignore b/esp32-cam/.gitignore new file mode 100644 index 0000000..5509140 --- /dev/null +++ b/esp32-cam/.gitignore @@ -0,0 +1 @@ +*.DS_Store diff --git a/esp32-cam/CMakeLists.txt b/esp32-cam/CMakeLists.txt new file mode 100644 index 0000000..536f6ba --- /dev/null +++ b/esp32-cam/CMakeLists.txt @@ -0,0 +1,61 @@ +if(IDF_TARGET STREQUAL "esp32" OR IDF_TARGET STREQUAL "esp32s2" OR IDF_TARGET STREQUAL "esp32s3") + set(COMPONENT_SRCS + driver/esp_camera.c + driver/cam_hal.c + driver/sccb.c + driver/sensor.c + sensors/ov2640.c + sensors/ov3660.c + sensors/ov5640.c + sensors/ov7725.c + sensors/ov7670.c + sensors/nt99141.c + conversions/yuv.c + conversions/to_jpg.cpp + conversions/to_bmp.c + conversions/jpge.cpp + conversions/esp_jpg_decode.c + ) + + set(COMPONENT_ADD_INCLUDEDIRS + driver/include + conversions/include + ) + + set(COMPONENT_PRIV_INCLUDEDIRS + driver/private_include + sensors/private_include + conversions/private_include + target/private_include + ) + + if(IDF_TARGET STREQUAL "esp32") + list(APPEND COMPONENT_SRCS + target/xclk.c + target/esp32/ll_cam.c + ) + endif() + + if(IDF_TARGET STREQUAL "esp32s2") + list(APPEND COMPONENT_SRCS + target/xclk.c + target/esp32s2/ll_cam.c + target/esp32s2/tjpgd.c + ) + + list(APPEND COMPONENT_PRIV_INCLUDEDIRS + target/esp32s2/private_include + ) + endif() + + if(IDF_TARGET STREQUAL "esp32s3") + list(APPEND COMPONENT_SRCS + target/esp32s3/ll_cam.c + ) + endif() + + set(COMPONENT_REQUIRES driver) + set(COMPONENT_PRIV_REQUIRES freertos nvs_flash) + + register_component() +endif() diff --git a/esp32-cam/Kconfig b/esp32-cam/Kconfig new file mode 100644 index 0000000..49813f6 --- /dev/null +++ b/esp32-cam/Kconfig @@ -0,0 +1,71 @@ +menu "Camera configuration" + + config OV7670_SUPPORT + bool "Support OV7670 VGA" + default y + help + Enable this option if you want to use the OV7670. + Disable this option to save memory. + + config OV7725_SUPPORT + bool "Support OV7725 SVGA" + default n + help + Enable this option if you want to use the OV7725. + Disable this option to save memory. + + config NT99141_SUPPORT + bool "Support NT99141 HD" + default y + help + Enable this option if you want to use the NT99141. + Disable this option to save memory. + + config OV2640_SUPPORT + bool "Support OV2640 2MP" + default y + help + Enable this option if you want to use the OV2640. + Disable this option to save memory. + + config OV3660_SUPPORT + bool "Support OV3660 3MP" + default y + help + Enable this option if you want to use the OV3360. + Disable this option to save memory. + + config OV5640_SUPPORT + bool "Support OV5640 5MP" + default y + help + Enable this option if you want to use the OV5640. + Disable this option to save memory. + + choice SCCB_HARDWARE_I2C_PORT + bool "I2C peripheral to use for SCCB" + default SCCB_HARDWARE_I2C_PORT1 + + config SCCB_HARDWARE_I2C_PORT0 + bool "I2C0" + config SCCB_HARDWARE_I2C_PORT1 + bool "I2C1" + + endchoice + + choice CAMERA_TASK_PINNED_TO_CORE + bool "Camera task pinned to core" + default CAMERA_CORE0 + help + Pin the camera handle task to a certain core(0/1). It can also be done automatically choosing NO_AFFINITY. + + config CAMERA_CORE0 + bool "CORE0" + config CAMERA_CORE1 + bool "CORE1" + config CAMERA_NO_AFFINITY + bool "NO_AFFINITY" + + endchoice + +endmenu diff --git a/esp32-cam/LICENSE b/esp32-cam/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/esp32-cam/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/esp32-cam/README.md b/esp32-cam/README.md index d773b9f..96872e6 100644 --- a/esp32-cam/README.md +++ b/esp32-cam/README.md @@ -1,16 +1,358 @@ -# ESP32 MJPEG Multiclient Streaming Server +# ESP32 Camera Driver -This is a simple MJPEG streaming webserver implemented for AI-Thinker ESP32-CAM or ESP-EYE modules. +## General Information -This is tested to work with **VLC** and **Blynk** video widget. +This repository hosts ESP32, ESP32-S2 and ESP32-S3 compatible driver for OV2640, OV3660, OV5640, OV7670 and OV7725 image sensors. Additionally it provides a few tools, which allow converting the captured frame data to the more common BMP and JPEG formats. +## Important to Remember +- Except when using CIF or lower resolution with JPEG, the driver requires PSRAM to be installed and activated. +- Using YUV or RGB puts a lot of strain on the chip because writing to PSRAM is not particularly fast. The result is that image data might be missing. This is particularly true if WiFi is enabled. If you need RGB data, it is recommended that JPEG is captured and then turned into RGB using `fmt2rgb888` or `fmt2bmp`/`frame2bmp`. +- When 1 frame buffer is used, the driver will wait for the current frame to finish (VSYNC) and start I2S DMA. After the frame is acquired, I2S will be stopped and the frame buffer returned to the application. This approach gives more control over the system, but results in longer time to get the frame. +- When 2 or more frame bufers are used, I2S is running in continuous mode and each frame is pushed to a queue that the application can access. This approach puts more strain on the CPU/Memory, but allows for double the frame rate. Please use only with JPEG. -**This version uses FreeRTOS tasks to enable streaming to up to 10 connected clients** +## Installation Instructions +### Using esp-idf -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/) +- Clone or download and extract the repository to the components folder of your ESP-IDF project +- Enable PSRAM in `menuconfig` (also set Flash and PSRAM frequiencies to 80MHz) +- Include `esp_camera.h` in your code + +### Using PlatformIO + +The easy way -- on the `env` section of `platformio.ini`, add the following: + +```ini +[env] +lib_deps = + esp32-camera +``` + +Now the `esp_camera.h` is available to be included: + +```c +#include "esp_camera.h" +``` + +Enable PSRAM on `menuconfig` or type it direclty on `sdkconfig`. Check the [official doc](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/kconfig.html#config-esp32-spiram-support) for more info. + +``` +CONFIG_ESP32_SPIRAM_SUPPORT=y +``` + +***Arduino*** The easy-way (content above) only seems to work if you're using `framework=arduino` which seems to take a bunch of the guesswork out (thanks Arduino!) but also suck up a lot more memory and flash, almost crippling the performance. If you plan to use the `framework=espidf` then read the sections below carefully!! + +## Platform.io lib/submodule (for framework=espidf) + +It's probably easier to just skip the platform.io library registry version and link the git repo as a submodule. (i.e. using code outside the platform.io library management). In this example we will install this as a submodule inside the platform.io $project/lib folder: +``` +cd $project\lib +git submodule add -b master https://github.com/espressif/esp32-camera.git +``` + +Then in `platformio.ini` file +``` +build_flags = + -I../lib/esp32-camera +``` +After that `#include "esp_camera.h"` statement will be available. Now the module is included, and you're hopefully back to the same place as the easy-Arduino way. + +**Warning about platform.io/espidf and fresh (not initialized) git repos** +There is a sharp-edge on you'll discover in the platform.io build process (in espidf v3.3 & 4.0.1) where a project which has only had `git init` but nothing committed will crash platform.io build process with highly non-useful output. The cause is due to lack of a version (making you think you did something wrong, when you didn't at all) - the output is horribly non-descript. Solution: the devs want you to create a file called version.txt with a number in it, or simply commit any file to the projects git repo and use git. This happens because platform.io build process tries to be too clever and determine the build version number from the git repo - it's a sharp edge you'll only encounter if you're experimenting on a new project with no commits .. like wtf is my camera not working let's try a 'clean project'?! + +## Platform.io Kconfig +Kconfig is used by the platform.io menuconfig (accessed by running: `pio run -t menuconfig`) to interactively manage the various #ifdef statements throughout the espidf and supporting libraries (i.e. this repo: esp32-camera and arduino-esp32.git). The menuconfig process generates the `sdkconfig` file which is ultimately used behind the scenes by espidf compile+build process. + +**Make sure to append or symlink** [this `Kconfig`](./Kconfig) content into the `Kconfig` of your project. + +You symlink (or copy) the included Kconfig into your platform.io projects src directory. The file should be named `Kconfig.projbuild` in your projects src\ directory or you could also add the library path to a CMakefile.txt and hope the `Kconfig` (or `Kconfig.projbuild`) gets discovered by the menuconfig process, though this unpredictable for me. + +The unpredictable wonky behavior in platform.io build process around Kconfig naming (Kconfig vs. Kconfig.projbuild) occurs between espidf versions 3.3 and 4.0 - but if you don't see "Camera configuration" in your `pio run -t menuconfig` then there is no point trying to test camera code (it may compile, but it probably won't work!) and it seems the platform.io devs (when they built their wrapper around the espidf menuconfig) didn't implement it properly. You've probably already figured out you can't use the espidf build tools since the files are in totally different locations and also different versions with sometimes different syntax. This is one of those times you might consider changing the `platformio.ini` from `platform=espressif32` to `platform=https://github.com/platformio/platform-espressif32.git#develop` to get a more recent version of the espidf 4.0 tools. + +However with a bit of patience and experimenting you'll figure the Kconfig out. Once Kconfig (or Kconfig.projbuild) is working then you will be able to choose the configurations according to your setup or the camera libraries will be compiled. Although you might also need to delete your .pio/build directory before the options appear .. again, the `pio run -t menuconfig` doens't always notice the new Kconfig files! + +If you miss-skip-ignore this critical step the camera module will compile but camera logic inside the library will be 'empty' because the Kconfig sets the proper #ifdef statements during the build process to initialize the selected cameras. It's very not optional! + +### Kconfig options + +| config | description | default | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------ | +| CONFIG_OV2640_SUPPORT | Support for OV2640 camera | enabled | +| CONFIG_OV7725_SUPPORT | Support for OV7725 camera | disabled | +| CONFIG_OV3660_SUPPORT | Support for OV3660 camera | enabled | +| CONFIG_OV5640_SUPPORT | Support for OV5640 camera | enabled | +| CONFIG_SCCB_HARDWARE_I2C | Enable this option if you want to use hardware I2C to control the camera. Disable this option to use software I2C. | enabled | +| CONFIG_SCCB_HARDWARE_I2C_PORT | I2C peripheral to use for SCCB. Can be I2C0 and I2C1. | CONFIG_SCCB_HARDWARE_I2C_PORT1 | +| CONFIG_CAMERA_TASK_PINNED_TO_CORE | Pin the camera handle task to a certain core(0/1). It can also be done automatically choosing NO_AFFINITY. Can be CAMERA_CORE0, CAMERA_CORE1 or NO_AFFINITY. | CONFIG_CAMERA_CORE0 | + +## Examples + +### Initialization + +```c +#include "esp_camera.h" + +//WROVER-KIT PIN Map +#define CAM_PIN_PWDN -1 //power down is not used +#define CAM_PIN_RESET -1 //software reset will be performed +#define CAM_PIN_XCLK 21 +#define CAM_PIN_SIOD 26 +#define CAM_PIN_SIOC 27 + +#define CAM_PIN_D7 35 +#define CAM_PIN_D6 34 +#define CAM_PIN_D5 39 +#define CAM_PIN_D4 36 +#define CAM_PIN_D3 19 +#define CAM_PIN_D2 18 +#define CAM_PIN_D1 5 +#define CAM_PIN_D0 4 +#define CAM_PIN_VSYNC 25 +#define CAM_PIN_HREF 23 +#define CAM_PIN_PCLK 22 + +static camera_config_t camera_config = { + .pin_pwdn = CAM_PIN_PWDN, + .pin_reset = CAM_PIN_RESET, + .pin_xclk = CAM_PIN_XCLK, + .pin_sscb_sda = CAM_PIN_SIOD, + .pin_sscb_scl = CAM_PIN_SIOC, + + .pin_d7 = CAM_PIN_D7, + .pin_d6 = CAM_PIN_D6, + .pin_d5 = CAM_PIN_D5, + .pin_d4 = CAM_PIN_D4, + .pin_d3 = CAM_PIN_D3, + .pin_d2 = CAM_PIN_D2, + .pin_d1 = CAM_PIN_D1, + .pin_d0 = CAM_PIN_D0, + .pin_vsync = CAM_PIN_VSYNC, + .pin_href = CAM_PIN_HREF, + .pin_pclk = CAM_PIN_PCLK, + + .xclk_freq_hz = 20000000,//EXPERIMENTAL: Set to 16MHz on ESP32-S2 or ESP32-S3 to enable EDMA mode + .ledc_timer = LEDC_TIMER_0, + .ledc_channel = LEDC_CHANNEL_0, + + .pixel_format = PIXFORMAT_JPEG,//YUV422,GRAYSCALE,RGB565,JPEG + .frame_size = FRAMESIZE_UXGA,//QQVGA-QXGA Do not use sizes above QVGA when not JPEG + + .jpeg_quality = 12, //0-63 lower number means higher quality + .fb_count = 1, //if more than one, i2s runs in continuous mode. Use only with JPEG + .grab_mode = CAMERA_GRAB_WHEN_EMPTY//CAMERA_GRAB_LATEST. Sets when buffers should be filled +}; + +esp_err_t camera_init(){ + //power up the camera if PWDN pin is defined + if(CAM_PIN_PWDN != -1){ + pinMode(CAM_PIN_PWDN, OUTPUT); + digitalWrite(CAM_PIN_PWDN, LOW); + } + + //initialize the camera + esp_err_t err = esp_camera_init(&camera_config); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Camera Init Failed"); + return err; + } + + return ESP_OK; +} + +esp_err_t camera_capture(){ + //acquire a frame + camera_fb_t * fb = esp_camera_fb_get(); + if (!fb) { + ESP_LOGE(TAG, "Camera Capture Failed"); + return ESP_FAIL; + } + //replace this with your own function + process_image(fb->width, fb->height, fb->format, fb->buf, fb->len); + + //return the frame buffer back to the driver for reuse + esp_camera_fb_return(fb); + return ESP_OK; +} +``` + +### JPEG HTTP Capture + +```c +#include "esp_camera.h" +#include "esp_http_server.h" +#include "esp_timer.h" + +typedef struct { + httpd_req_t *req; + size_t len; +} jpg_chunking_t; + +static size_t jpg_encode_stream(void * arg, size_t index, const void* data, size_t len){ + jpg_chunking_t *j = (jpg_chunking_t *)arg; + if(!index){ + j->len = 0; + } + if(httpd_resp_send_chunk(j->req, (const char *)data, len) != ESP_OK){ + return 0; + } + j->len += len; + return len; +} + +esp_err_t jpg_httpd_handler(httpd_req_t *req){ + camera_fb_t * fb = NULL; + esp_err_t res = ESP_OK; + size_t fb_len = 0; + int64_t fr_start = esp_timer_get_time(); + + fb = esp_camera_fb_get(); + if (!fb) { + ESP_LOGE(TAG, "Camera capture failed"); + httpd_resp_send_500(req); + return ESP_FAIL; + } + res = httpd_resp_set_type(req, "image/jpeg"); + if(res == ESP_OK){ + res = httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg"); + } + + if(res == ESP_OK){ + if(fb->format == PIXFORMAT_JPEG){ + fb_len = fb->len; + res = httpd_resp_send(req, (const char *)fb->buf, fb->len); + } else { + jpg_chunking_t jchunk = {req, 0}; + res = frame2jpg_cb(fb, 80, jpg_encode_stream, &jchunk)?ESP_OK:ESP_FAIL; + httpd_resp_send_chunk(req, NULL, 0); + fb_len = jchunk.len; + } + } + esp_camera_fb_return(fb); + int64_t fr_end = esp_timer_get_time(); + ESP_LOGI(TAG, "JPG: %uKB %ums", (uint32_t)(fb_len/1024), (uint32_t)((fr_end - fr_start)/1000)); + return res; +} +``` + +### JPEG HTTP Stream + +```c +#include "esp_camera.h" +#include "esp_http_server.h" +#include "esp_timer.h" + +#define PART_BOUNDARY "123456789000000000000987654321" +static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY; +static const char* _STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n"; +static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n"; + +esp_err_t jpg_stream_httpd_handler(httpd_req_t *req){ + camera_fb_t * fb = NULL; + esp_err_t res = ESP_OK; + size_t _jpg_buf_len; + uint8_t * _jpg_buf; + char * part_buf[64]; + static int64_t last_frame = 0; + if(!last_frame) { + last_frame = esp_timer_get_time(); + } + + res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE); + if(res != ESP_OK){ + return res; + } + + while(true){ + fb = esp_camera_fb_get(); + if (!fb) { + ESP_LOGE(TAG, "Camera capture failed"); + res = ESP_FAIL; + break; + } + if(fb->format != PIXFORMAT_JPEG){ + bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len); + if(!jpeg_converted){ + ESP_LOGE(TAG, "JPEG compression failed"); + esp_camera_fb_return(fb); + res = ESP_FAIL; + } + } else { + _jpg_buf_len = fb->len; + _jpg_buf = fb->buf; + } + + if(res == ESP_OK){ + res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY)); + } + if(res == ESP_OK){ + size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, _jpg_buf_len); + + res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen); + } + if(res == ESP_OK){ + res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len); + } + if(fb->format != PIXFORMAT_JPEG){ + free(_jpg_buf); + } + esp_camera_fb_return(fb); + if(res != ESP_OK){ + break; + } + int64_t fr_end = esp_timer_get_time(); + int64_t frame_time = fr_end - last_frame; + last_frame = fr_end; + frame_time /= 1000; + ESP_LOGI(TAG, "MJPG: %uKB %ums (%.1ffps)", + (uint32_t)(_jpg_buf_len/1024), + (uint32_t)frame_time, 1000.0 / (uint32_t)frame_time); + } + + last_frame = 0; + return res; +} +``` + +### BMP HTTP Capture + +```c +#include "esp_camera.h" +#include "esp_http_server.h" +#include "esp_timer.h" + +esp_err_t bmp_httpd_handler(httpd_req_t *req){ + camera_fb_t * fb = NULL; + esp_err_t res = ESP_OK; + int64_t fr_start = esp_timer_get_time(); + + fb = esp_camera_fb_get(); + if (!fb) { + ESP_LOGE(TAG, "Camera capture failed"); + httpd_resp_send_500(req); + return ESP_FAIL; + } + + uint8_t * buf = NULL; + size_t buf_len = 0; + bool converted = frame2bmp(fb, &buf, &buf_len); + esp_camera_fb_return(fb); + if(!converted){ + ESP_LOGE(TAG, "BMP conversion failed"); + httpd_resp_send_500(req); + return ESP_FAIL; + } + + res = httpd_resp_set_type(req, "image/x-windows-bmp") + || httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.bmp") + || httpd_resp_send(req, (const char *)buf, buf_len); + free(buf); + int64_t fr_end = esp_timer_get_time(); + ESP_LOGI(TAG, "BMP: %uKB %ums", (uint32_t)(buf_len/1024), (uint32_t)((fr_end - fr_start)/1000)); + return res; +} +``` diff --git a/esp32-cam/cam_hal.c b/esp32-cam/cam_hal.c new file mode 100644 index 0000000..f2b65de --- /dev/null +++ b/esp32-cam/cam_hal.c @@ -0,0 +1,472 @@ +// Copyright 2010-2020 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_heap_caps.h" +#include "ll_cam.h" +#include "cam_hal.h" + +static const char *TAG = "cam_hal"; + +static cam_obj_t *cam_obj = NULL; + +static const uint32_t JPEG_SOI_MARKER = 0xFFD8FF; // written in little-endian for esp32 +static const uint16_t JPEG_EOI_MARKER = 0xD9FF; // written in little-endian for esp32 + +static int cam_verify_jpeg_soi(const uint8_t *inbuf, uint32_t length) +{ + uint32_t sig = *((uint32_t *)inbuf) & 0xFFFFFF; + if(sig != JPEG_SOI_MARKER) { + for (uint32_t i = 0; i < length; i++) { + sig = *((uint32_t *)(&inbuf[i])) & 0xFFFFFF; + if (sig == JPEG_SOI_MARKER) { + ESP_LOGW(TAG, "SOI: %d", i); + return i; + } + } + ESP_LOGW(TAG, "NO-SOI"); + return -1; + } + return 0; +} + +static int cam_verify_jpeg_eoi(const uint8_t *inbuf, uint32_t length) +{ + int offset = -1; + uint8_t *dptr = (uint8_t *)inbuf + length - 2; + while (dptr > inbuf) { + uint16_t sig = *((uint16_t *)dptr); + if (JPEG_EOI_MARKER == sig) { + offset = dptr - inbuf; + //ESP_LOGW(TAG, "EOI: %d", length - (offset + 2)); + return offset; + } + dptr--; + } + return -1; +} + +static bool cam_get_next_frame(int * frame_pos) +{ + if(!cam_obj->frames[*frame_pos].en){ + for (int x = 0; x < cam_obj->frame_cnt; x++) { + if (cam_obj->frames[x].en) { + *frame_pos = x; + return true; + } + } + } else { + return true; + } + return false; +} + +static bool cam_start_frame(int * frame_pos) +{ + if (cam_get_next_frame(frame_pos)) { + if(ll_cam_start(cam_obj, *frame_pos)){ + // Vsync the frame manually + ll_cam_do_vsync(cam_obj); + uint64_t us = (uint64_t)esp_timer_get_time(); + cam_obj->frames[*frame_pos].fb.timestamp.tv_sec = us / 1000000UL; + cam_obj->frames[*frame_pos].fb.timestamp.tv_usec = us % 1000000UL; + return true; + } + } + return false; +} + +void IRAM_ATTR ll_cam_send_event(cam_obj_t *cam, cam_event_t cam_event, BaseType_t * HPTaskAwoken) +{ + if (xQueueSendFromISR(cam->event_queue, (void *)&cam_event, HPTaskAwoken) != pdTRUE) { + ll_cam_stop(cam); + cam->state = CAM_STATE_IDLE; + ESP_EARLY_LOGE(TAG, "EV-OVF"); + } +} + +//Copy fram from DMA dma_buffer to fram dma_buffer +static void cam_task(void *arg) +{ + int cnt = 0; + int frame_pos = 0; + cam_obj->state = CAM_STATE_IDLE; + cam_event_t cam_event = 0; + + xQueueReset(cam_obj->event_queue); + + while (1) { + xQueueReceive(cam_obj->event_queue, (void *)&cam_event, portMAX_DELAY); + DBG_PIN_SET(1); + switch (cam_obj->state) { + + case CAM_STATE_IDLE: { + if (cam_event == CAM_VSYNC_EVENT) { + //DBG_PIN_SET(1); + if(cam_start_frame(&frame_pos)){ + cam_obj->frames[frame_pos].fb.len = 0; + cam_obj->state = CAM_STATE_READ_BUF; + } + cnt = 0; + } + } + break; + + case CAM_STATE_READ_BUF: { + camera_fb_t * frame_buffer_event = &cam_obj->frames[frame_pos].fb; + size_t pixels_per_dma = (cam_obj->dma_half_buffer_size * cam_obj->fb_bytes_per_pixel) / (cam_obj->dma_bytes_per_item * cam_obj->in_bytes_per_pixel); + + if (cam_event == CAM_IN_SUC_EOF_EVENT) { + if(!cam_obj->psram_mode){ + if (cam_obj->fb_size < (frame_buffer_event->len + pixels_per_dma)) { + ESP_LOGW(TAG, "FB-OVF"); + ll_cam_stop(cam_obj); + DBG_PIN_SET(0); + continue; + } + frame_buffer_event->len += ll_cam_memcpy(cam_obj, + &frame_buffer_event->buf[frame_buffer_event->len], + &cam_obj->dma_buffer[(cnt % cam_obj->dma_half_buffer_cnt) * cam_obj->dma_half_buffer_size], + cam_obj->dma_half_buffer_size); + } + //Check for JPEG SOI in the first buffer. stop if not found + if (cam_obj->jpeg_mode && cnt == 0 && cam_verify_jpeg_soi(frame_buffer_event->buf, frame_buffer_event->len) != 0) { + ll_cam_stop(cam_obj); + cam_obj->state = CAM_STATE_IDLE; + } + cnt++; + + } else if (cam_event == CAM_VSYNC_EVENT) { + //DBG_PIN_SET(1); + ll_cam_stop(cam_obj); + + if (cnt || !cam_obj->jpeg_mode || cam_obj->psram_mode) { + if (cam_obj->jpeg_mode) { + if (!cam_obj->psram_mode) { + if (cam_obj->fb_size < (frame_buffer_event->len + pixels_per_dma)) { + ESP_LOGW(TAG, "FB-OVF"); + cnt--; + } else { + frame_buffer_event->len += ll_cam_memcpy(cam_obj, + &frame_buffer_event->buf[frame_buffer_event->len], + &cam_obj->dma_buffer[(cnt % cam_obj->dma_half_buffer_cnt) * cam_obj->dma_half_buffer_size], + cam_obj->dma_half_buffer_size); + } + } + cnt++; + } + + cam_obj->frames[frame_pos].en = 0; + + if (cam_obj->psram_mode) { + if (cam_obj->jpeg_mode) { + frame_buffer_event->len = cnt * cam_obj->dma_half_buffer_size; + } else { + frame_buffer_event->len = cam_obj->recv_size; + } + } else if (!cam_obj->jpeg_mode) { + if (frame_buffer_event->len != cam_obj->fb_size) { + cam_obj->frames[frame_pos].en = 1; + ESP_LOGE(TAG, "FB-SIZE: %u != %u", frame_buffer_event->len, cam_obj->fb_size); + } + } + //send frame + if(!cam_obj->frames[frame_pos].en && xQueueSend(cam_obj->frame_buffer_queue, (void *)&frame_buffer_event, 0) != pdTRUE) { + //pop frame buffer from the queue + camera_fb_t * fb2 = NULL; + if(xQueueReceive(cam_obj->frame_buffer_queue, &fb2, 0) == pdTRUE) { + //push the new frame to the end of the queue + if (xQueueSend(cam_obj->frame_buffer_queue, (void *)&frame_buffer_event, 0) != pdTRUE) { + cam_obj->frames[frame_pos].en = 1; + ESP_LOGE(TAG, "FBQ-SND"); + } + //free the popped buffer + cam_give(fb2); + } else { + //queue is full and we could not pop a frame from it + cam_obj->frames[frame_pos].en = 1; + ESP_LOGE(TAG, "FBQ-RCV"); + } + } + } + + if(!cam_start_frame(&frame_pos)){ + cam_obj->state = CAM_STATE_IDLE; + } else { + cam_obj->frames[frame_pos].fb.len = 0; + } + cnt = 0; + } + } + break; + } + DBG_PIN_SET(0); + } +} + +static lldesc_t * allocate_dma_descriptors(uint32_t count, uint16_t size, uint8_t * buffer) +{ + lldesc_t *dma = (lldesc_t *)heap_caps_malloc(count * sizeof(lldesc_t), MALLOC_CAP_DMA); + if (dma == NULL) { + return dma; + } + + for (int x = 0; x < count; x++) { + dma[x].size = size; + dma[x].length = 0; + dma[x].sosf = 0; + dma[x].eof = 0; + dma[x].owner = 1; + dma[x].buf = (buffer + size * x); + dma[x].empty = (uint32_t)&dma[(x + 1) % count]; + } + return dma; +} + +static esp_err_t cam_dma_config() +{ + bool ret = ll_cam_dma_sizes(cam_obj); + if (0 == ret) { + return ESP_FAIL; + } + + cam_obj->dma_node_cnt = (cam_obj->dma_buffer_size) / cam_obj->dma_node_buffer_size; // Number of DMA nodes + cam_obj->frame_copy_cnt = cam_obj->recv_size / cam_obj->dma_half_buffer_size; // Number of interrupted copies, ping-pong copy + + ESP_LOGI(TAG, "buffer_size: %d, half_buffer_size: %d, node_buffer_size: %d, node_cnt: %d, total_cnt: %d", + cam_obj->dma_buffer_size, cam_obj->dma_half_buffer_size, cam_obj->dma_node_buffer_size, cam_obj->dma_node_cnt, cam_obj->frame_copy_cnt); + + cam_obj->dma_buffer = NULL; + cam_obj->dma = NULL; + + cam_obj->frames = (cam_frame_t *)heap_caps_calloc(1, cam_obj->frame_cnt * sizeof(cam_frame_t), MALLOC_CAP_DEFAULT); + CAM_CHECK(cam_obj->frames != NULL, "frames malloc failed", ESP_FAIL); + + uint8_t dma_align = 0; + size_t fb_size = cam_obj->fb_size; + if (cam_obj->psram_mode) { + dma_align = ll_cam_get_dma_align(cam_obj); + if (cam_obj->fb_size < cam_obj->recv_size) { + fb_size = cam_obj->recv_size; + } + } + for (int x = 0; x < cam_obj->frame_cnt; x++) { + cam_obj->frames[x].dma = NULL; + cam_obj->frames[x].fb_offset = 0; + cam_obj->frames[x].en = 0; + cam_obj->frames[x].fb.buf = (uint8_t *)heap_caps_malloc(fb_size * sizeof(uint8_t) + dma_align, MALLOC_CAP_SPIRAM); + CAM_CHECK(cam_obj->frames[x].fb.buf != NULL, "frame buffer malloc failed", ESP_FAIL); + if (cam_obj->psram_mode) { + //align PSRAM buffer. TODO: save the offset so proper address can be freed later + cam_obj->frames[x].fb_offset = dma_align - ((uint32_t)cam_obj->frames[x].fb.buf & (dma_align - 1)); + cam_obj->frames[x].fb.buf += cam_obj->frames[x].fb_offset; + ESP_LOGI(TAG, "Frame[%d]: Offset: %u, Addr: 0x%08X", x, cam_obj->frames[x].fb_offset, (uint32_t)cam_obj->frames[x].fb.buf); + cam_obj->frames[x].dma = allocate_dma_descriptors(cam_obj->dma_node_cnt, cam_obj->dma_node_buffer_size, cam_obj->frames[x].fb.buf); + CAM_CHECK(cam_obj->frames[x].dma != NULL, "frame dma malloc failed", ESP_FAIL); + } + cam_obj->frames[x].en = 1; + } + + if (!cam_obj->psram_mode) { + cam_obj->dma_buffer = (uint8_t *)heap_caps_malloc(cam_obj->dma_buffer_size * sizeof(uint8_t), MALLOC_CAP_DMA); + CAM_CHECK(cam_obj->dma_buffer != NULL, "dma_buffer malloc failed", ESP_FAIL); + + cam_obj->dma = allocate_dma_descriptors(cam_obj->dma_node_cnt, cam_obj->dma_node_buffer_size, cam_obj->dma_buffer); + CAM_CHECK(cam_obj->dma != NULL, "dma malloc failed", ESP_FAIL); + } + + return ESP_OK; +} + +esp_err_t cam_init(const camera_config_t *config) +{ + CAM_CHECK(NULL != config, "config pointer is invalid", ESP_ERR_INVALID_ARG); + + esp_err_t ret = ESP_OK; + cam_obj = (cam_obj_t *)heap_caps_calloc(1, sizeof(cam_obj_t), MALLOC_CAP_DMA); + CAM_CHECK(NULL != cam_obj, "lcd_cam object malloc error", ESP_ERR_NO_MEM); + + cam_obj->swap_data = 0; + cam_obj->vsync_pin = config->pin_vsync; + cam_obj->vsync_invert = true; + + ll_cam_set_pin(cam_obj, config); + ret = ll_cam_config(cam_obj, config); + CAM_CHECK_GOTO(ret == ESP_OK, "ll_cam initialize failed", err); + +#if CAMERA_DBG_PIN_ENABLE + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[DBG_PIN_NUM], PIN_FUNC_GPIO); + gpio_set_direction(DBG_PIN_NUM, GPIO_MODE_OUTPUT); + gpio_set_pull_mode(DBG_PIN_NUM, GPIO_FLOATING); +#endif + + ESP_LOGI(TAG, "cam init ok"); + return ESP_OK; + +err: + free(cam_obj); + cam_obj = NULL; + return ESP_FAIL; +} + +esp_err_t cam_config(const camera_config_t *config, framesize_t frame_size, uint8_t sensor_pid) +{ + CAM_CHECK(NULL != config, "config pointer is invalid", ESP_ERR_INVALID_ARG); + esp_err_t ret = ESP_OK; + + ret = ll_cam_set_sample_mode(cam_obj, (pixformat_t)config->pixel_format, config->xclk_freq_hz, sensor_pid); + + cam_obj->jpeg_mode = config->pixel_format == PIXFORMAT_JPEG; +#if CONFIG_IDF_TARGET_ESP32 + cam_obj->psram_mode = false; +#else + cam_obj->psram_mode = (config->xclk_freq_hz == 16000000); +#endif + cam_obj->frame_cnt = config->fb_count; + cam_obj->width = resolution[frame_size].width; + cam_obj->height = resolution[frame_size].height; + + if(cam_obj->jpeg_mode){ + cam_obj->recv_size = cam_obj->width * cam_obj->height / 5; + cam_obj->fb_size = cam_obj->recv_size; + } else { + cam_obj->recv_size = cam_obj->width * cam_obj->height * cam_obj->in_bytes_per_pixel; + cam_obj->fb_size = cam_obj->width * cam_obj->height * cam_obj->fb_bytes_per_pixel; + } + + ret = cam_dma_config(); + CAM_CHECK_GOTO(ret == ESP_OK, "cam_dma_config failed", err); + + cam_obj->event_queue = xQueueCreate(cam_obj->dma_half_buffer_cnt - 1, sizeof(cam_event_t)); + CAM_CHECK_GOTO(cam_obj->event_queue != NULL, "event_queue create failed", err); + + size_t frame_buffer_queue_len = cam_obj->frame_cnt; + if (config->grab_mode == CAMERA_GRAB_LATEST && cam_obj->frame_cnt > 1) { + frame_buffer_queue_len = cam_obj->frame_cnt - 1; + } + cam_obj->frame_buffer_queue = xQueueCreate(frame_buffer_queue_len, sizeof(camera_fb_t*)); + CAM_CHECK_GOTO(cam_obj->frame_buffer_queue != NULL, "frame_buffer_queue create failed", err); + + ret = ll_cam_init_isr(cam_obj); + CAM_CHECK_GOTO(ret == ESP_OK, "cam intr alloc failed", err); + + +#if CONFIG_CAMERA_CORE0 + xTaskCreatePinnedToCore(cam_task, "cam_task", 2048, NULL, configMAX_PRIORITIES - 2, &cam_obj->task_handle, 0); +#elif CONFIG_CAMERA_CORE1 + xTaskCreatePinnedToCore(cam_task, "cam_task", 2048, NULL, configMAX_PRIORITIES - 2, &cam_obj->task_handle, 1); +#else + xTaskCreate(cam_task, "cam_task", 2048, NULL, configMAX_PRIORITIES - 2, &cam_obj->task_handle); +#endif + + ESP_LOGI(TAG, "cam config ok"); + return ESP_OK; + +err: + cam_deinit(); + return ESP_FAIL; +} + +esp_err_t cam_deinit(void) +{ + if (!cam_obj) { + return ESP_FAIL; + } + + cam_stop(); + gpio_isr_handler_remove(cam_obj->vsync_pin); + if (cam_obj->task_handle) { + vTaskDelete(cam_obj->task_handle); + } + if (cam_obj->event_queue) { + vQueueDelete(cam_obj->event_queue); + } + if (cam_obj->frame_buffer_queue) { + vQueueDelete(cam_obj->frame_buffer_queue); + } + if (cam_obj->dma) { + free(cam_obj->dma); + } + if (cam_obj->dma_buffer) { + free(cam_obj->dma_buffer); + } + if (cam_obj->frames) { + for (int x = 0; x < cam_obj->frame_cnt; x++) { + free(cam_obj->frames[x].fb.buf - cam_obj->frames[x].fb_offset); + if (cam_obj->frames[x].dma) { + free(cam_obj->frames[x].dma); + } + } + free(cam_obj->frames); + } + + if (cam_obj->cam_intr_handle) { + esp_intr_free(cam_obj->cam_intr_handle); + } + + free(cam_obj); + cam_obj = NULL; + return ESP_OK; +} + +void cam_stop(void) +{ + ll_cam_vsync_intr_enable(cam_obj, false); + ll_cam_stop(cam_obj); +} + +void cam_start(void) +{ + ll_cam_vsync_intr_enable(cam_obj, true); +} + +camera_fb_t *cam_take(TickType_t timeout) +{ + camera_fb_t *dma_buffer = NULL; + TickType_t start = xTaskGetTickCount(); + xQueueReceive(cam_obj->frame_buffer_queue, (void *)&dma_buffer, timeout); + if (dma_buffer) { + if(cam_obj->jpeg_mode){ + // find the end marker for JPEG. Data after that can be discarded + int offset_e = cam_verify_jpeg_eoi(dma_buffer->buf, dma_buffer->len); + if (offset_e >= 0) { + // adjust buffer length + dma_buffer->len = offset_e + sizeof(JPEG_EOI_MARKER); + return dma_buffer; + } else { + ESP_LOGW(TAG, "NO-EOI"); + cam_give(dma_buffer); + return cam_take(timeout - (xTaskGetTickCount() - start));//recurse!!!! + } + } else if(cam_obj->psram_mode && cam_obj->in_bytes_per_pixel != cam_obj->fb_bytes_per_pixel){ + //currently this is used only for YUV to GRAYSCALE + dma_buffer->len = ll_cam_memcpy(cam_obj, dma_buffer->buf, dma_buffer->buf, dma_buffer->len); + } + return dma_buffer; + } else { + ESP_LOGI(TAG, "Failed to get the frame on time!"); + } + return NULL; +} + +void cam_give(camera_fb_t *dma_buffer) +{ + for (int x = 0; x < cam_obj->frame_cnt; x++) { + if (&cam_obj->frames[x].fb == dma_buffer) { + cam_obj->frames[x].en = 1; + break; + } + } +} diff --git a/esp32-cam/cam_hal.h b/esp32-cam/cam_hal.h new file mode 100644 index 0000000..a271865 --- /dev/null +++ b/esp32-cam/cam_hal.h @@ -0,0 +1,60 @@ +// Copyright 2010-2020 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. + +#pragma once + +#include "esp_camera.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Uninitialize the lcd_cam module + * + * @param handle Provide handle pointer to release resources + * + * @return + * - ESP_OK Success + * - ESP_FAIL Uninitialize fail + */ +esp_err_t cam_deinit(void); + +/** + * @brief Initialize the lcd_cam module + * + * @param config Configurations - see lcd_cam_config_t struct + * + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_ERR_NO_MEM No memory to initialize lcd_cam + * - ESP_FAIL Initialize fail + */ +esp_err_t cam_init(const camera_config_t *config); + +esp_err_t cam_config(const camera_config_t *config, framesize_t frame_size, uint8_t sensor_pid); + +void cam_stop(void); + +void cam_start(void); + +camera_fb_t *cam_take(TickType_t timeout); + +void cam_give(camera_fb_t *dma_buffer); + +#ifdef __cplusplus +} +#endif diff --git a/esp32-cam/camera.c b/esp32-cam/camera.c deleted file mode 100644 index f0f3a2e..0000000 --- a/esp32-cam/camera.c +++ /dev/null @@ -1,1592 +0,0 @@ -// 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 -#if CONFIG_NT99141_SUPPORT -#include "nt99141.h" -#endif -#if CONFIG_OV7670_SUPPORT -#include "ov7670.h" -#endif - -typedef enum { - CAMERA_NONE = 0, - CAMERA_UNKNOWN = 1, - CAMERA_OV7725 = 7725, - CAMERA_OV2640 = 2640, - CAMERA_OV3660 = 3660, - CAMERA_OV5640 = 5640, - CAMERA_OV7670 = 7670, - CAMERA_NT99141 = 9141, -} 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_gpio_init(const camera_config_t* config) -{ - // Configure input GPIOs - const 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, - .pin_bit_mask = 0LL - }; - for (int i = 0; i < sizeof(pins) / sizeof(gpio_num_t); ++i) { - if (rtc_gpio_is_valid_gpio(pins[i])) { - rtc_gpio_deinit(pins[i]); - } - conf.pin_bit_mask |= 1LL << pins[i]; - } - gpio_config(&conf); -} - -static void i2s_init() -{ - camera_config_t* config = &s_state->config; - - // Route input GPIOs to I2S peripheral using GPIO matrix - gpio_matrix_in(config->pin_d0, I2S0I_DATA_IN0_IDX, false); - 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) { - ESP_LOGD(TAG,"unexpected JPEG signature 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; - } - - if(config->pin_xclk >= 0) { - ESP_LOGD(TAG, "Enabling XCLK output"); - camera_enable_out_clock(config); - } - - if (config->pin_sscb_sda != -1) { - ESP_LOGD(TAG, "Initializing SSCB"); - SCCB_Init(config->pin_sscb_sda, config->pin_sscb_scl); - } - - if(config->pin_pwdn >= 0) { - ESP_LOGD(TAG, "Resetting camera by power down line"); - 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 -#if CONFIG_NT99141_SUPPORT - if (slv_addr == 0x2a) - { - ESP_LOGD(TAG, "Resetting NT99141"); - SCCB_Write16(0x2a, 0x3008, 0x01);//bank sensor - } -#endif - - s_state->sensor.slv_addr = slv_addr; - s_state->sensor.xclk_freq_hz = config->xclk_freq_hz; - -#if (CONFIG_OV3660_SUPPORT || CONFIG_OV5640_SUPPORT || CONFIG_NT99141_SUPPORT) - if(s_state->sensor.slv_addr == 0x3c){ - id->PID = SCCB_Read16(s_state->sensor.slv_addr, REG16_CHIDH); - id->VER = SCCB_Read16(s_state->sensor.slv_addr, REG16_CHIDL); - vTaskDelay(10 / portTICK_PERIOD_MS); - ESP_LOGD(TAG, "Camera PID=0x%02x VER=0x%02x", id->PID, id->VER); - } else if(s_state->sensor.slv_addr == 0x2a){ - id->PID = SCCB_Read16(s_state->sensor.slv_addr, 0x3000); - id->VER = SCCB_Read16(s_state->sensor.slv_addr, 0x3001); - vTaskDelay(10 / portTICK_PERIOD_MS); - ESP_LOGD(TAG, "Camera PID=0x%02x VER=0x%02x", id->PID, id->VER); - if(config->xclk_freq_hz > 10000000) - { - ESP_LOGE(TAG, "NT99141: only XCLK under 10MHz is supported, and XCLK is now set to 10M"); - s_state->sensor.xclk_freq_hz = 10000000; - } - } else { -#endif - id->PID = SCCB_Read(s_state->sensor.slv_addr, REG_PID); - 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 || CONFIG_NT99141_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 -#if CONFIG_OV7670_SUPPORT - case OV7670_PID: - *out_camera_model = CAMERA_OV7670; - ov7670_init(&s_state->sensor); - break; -#endif -#if CONFIG_NT99141_SUPPORT - case NT99141_PID: - *out_camera_model = CAMERA_NT99141; - NT99141_init(&s_state->sensor); - break; -#endif - default: - id->PID = 0; - *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 -#if CONFIG_OV7670_SUPPORT - case OV7670_PID: - if (frame_size > FRAMESIZE_VGA) { - frame_size = FRAMESIZE_VGA; - } - break; -#endif -#if CONFIG_NT99141_SUPPORT - case NT99141_PID: - if (frame_size > FRAMESIZE_HD) { - frame_size = FRAMESIZE_HD; - } - break; -#endif - default: - return ESP_ERR_CAMERA_NOT_SUPPORTED; - } - - 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 || s_state->sensor.id.PID == NT99141_PID) { - if (is_hs_mode()) { - s_state->sampling_mode = SM_0A00_0B00; - s_state->dma_filter = &dma_filter_yuyv_highspeed; - } 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) { - if(s_state->sensor.id.PID == OV7670_PID) { - s_state->sampling_mode = SM_0A0B_0B0C; - }else{ - s_state->sampling_mode = SM_0A00_0B00; - } - s_state->dma_filter = &dma_filter_yuyv_highspeed; - } else { - s_state->sampling_mode = SM_0A0B_0C0D; - s_state->dma_filter = &dma_filter_yuyv; - } - s_state->in_bytes_per_pixel = 2; // camera sends YU/YV - s_state->fb_bytes_per_pixel = 2; // frame buffer stores YU/YV/RGB565 - } else if (pix_format == PIXFORMAT_RGB888) { - s_state->fb_size = s_state->width * s_state->height * 3; - if (is_hs_mode()) { - if(s_state->sensor.id.PID == OV7670_PID) { - s_state->sampling_mode = SM_0A0B_0B0C; - }else{ - s_state->sampling_mode = SM_0A00_0B00; - } - s_state->dma_filter = &dma_filter_rgb888_highspeed; - } else { - s_state->sampling_mode = SM_0A0B_0C0D; - 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 && s_state->sensor.id.PID != NT99141_PID) { - ESP_LOGE(TAG, "JPEG format is only supported for ov2640, ov3660 and ov5640"); - err = ESP_ERR_NOT_SUPPORTED; - goto fail; - } - 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) { - if (err != ESP_ERR_INVALID_STATE) { - ESP_LOGE(TAG, "gpio_install_isr_service failed (%x)", err); - goto fail; - } - else { - ESP_LOGW(TAG, "gpio_install_isr_service already installed"); - } - } - err = gpio_isr_handler_add(s_state->config.pin_vsync, &vsync_isr, NULL); - if (err != ESP_OK) { - 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; - i2s_gpio_init(config); - esp_err_t err = camera_probe(config, &camera_model); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Camera probe failed with error 0x%x", err); - goto fail; - } - if (camera_model == CAMERA_OV7725) { - ESP_LOGI(TAG, "Detected OV7725 camera"); - if(config->pixel_format == PIXFORMAT_JPEG) { - ESP_LOGE(TAG, "Camera does not support JPEG"); - err = ESP_ERR_CAMERA_NOT_SUPPORTED; - goto fail; - } - } else if (camera_model == CAMERA_OV2640) { - ESP_LOGI(TAG, "Detected OV2640 camera"); - } else if (camera_model == CAMERA_OV3660) { - ESP_LOGI(TAG, "Detected OV3660 camera"); - } else if (camera_model == CAMERA_OV5640) { - ESP_LOGI(TAG, "Detected OV5640 camera"); - } else if (camera_model == CAMERA_OV7670) { - ESP_LOGI(TAG, "Detected OV7670 camera"); - } else if (camera_model == CAMERA_NT99141) { - ESP_LOGI(TAG, "Detected NT99141 camera"); - } else { - ESP_LOGI(TAG, "Camera not supported"); - err = ESP_ERR_CAMERA_NOT_SUPPORTED; - 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(); - - if(s_state->config.pin_xclk >= 0) { - camera_disable_out_clock(); - } - free(s_state); - s_state = NULL; - 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) -{ -//#if ESP_IDF_VERSION_MAJOR > 3 -// 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) -{ -//#if ESP_IDF_VERSION_MAJOR > 3 -// 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 index 8a2db5c..bdc24d2 100644 --- a/esp32-cam/camera_common.h +++ b/esp32-cam/camera_common.h @@ -15,6 +15,10 @@ #if ESP_IDF_VERSION_MAJOR >= 4 // IDF 4+ #if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 #include "esp32/rom/lldesc.h" +#elif CONFIG_IDF_TARGET_ESP32S2 // ESP32-S2 +#include "esp32s2/rom/lldesc.h" +#elif CONFIG_IDF_TARGET_ESP32S3 // ESP32-S3 +#include "esp32s3/rom/lldesc.h" #else #error Target CONFIG_IDF_TARGET is not supported #endif @@ -22,28 +26,3 @@ #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/component.mk b/esp32-cam/component.mk new file mode 100644 index 0000000..8db15eb --- /dev/null +++ b/esp32-cam/component.mk @@ -0,0 +1,4 @@ +COMPONENT_ADD_INCLUDEDIRS := driver/include conversions/include +COMPONENT_PRIV_INCLUDEDIRS := driver/private_include conversions/private_include sensors/private_include target/private_include +COMPONENT_SRCDIRS := driver conversions sensors target target/esp32 +CXXFLAGS += -fno-rtti diff --git a/esp32-cam/esp32-camera-master.zip b/esp32-cam/esp32-camera-master.zip new file mode 100644 index 0000000000000000000000000000000000000000..ce89eab405a80ff153968ab0d81cd844c25eb26c GIT binary patch literal 122571 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 jU}k1zH=t)`VA5yjFk~|_W@Th#)@NWcVlgl@WH +#include +#include +#include "time.h" +#include "sys/time.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/gpio.h" +#include "esp_system.h" +#include "nvs_flash.h" +#include "nvs.h" +#include "sensor.h" +#include "sccb.h" +#include "cam_hal.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 +#if CONFIG_NT99141_SUPPORT +#include "nt99141.h" +#endif +#if CONFIG_OV7670_SUPPORT +#include "ov7670.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 = "camera"; +#endif + +typedef struct { + sensor_t sensor; + camera_fb_t fb; +} camera_state_t; + +static const char* CAMERA_SENSOR_NVS_KEY = "sensor"; +static const char* CAMERA_PIXFORMAT_NVS_KEY = "pixformat"; +static camera_state_t *s_state = NULL; + +#if CONFIG_IDF_TARGET_ESP32S3 // LCD_CAM module of ESP32-S3 will generate xclk +#define CAMERA_ENABLE_OUT_CLOCK(v) +#define CAMERA_DISABLE_OUT_CLOCK() +#else +#define CAMERA_ENABLE_OUT_CLOCK(v) camera_enable_out_clock((v)) +#define CAMERA_DISABLE_OUT_CLOCK() camera_disable_out_clock() +#endif + +static esp_err_t camera_probe(const camera_config_t *config, camera_model_t *out_camera_model) +{ + *out_camera_model = CAMERA_NONE; + if (s_state != NULL) { + return ESP_ERR_INVALID_STATE; + } + + s_state = (camera_state_t *) calloc(sizeof(camera_state_t), 1); + if (!s_state) { + return ESP_ERR_NO_MEM; + } + + if (config->pin_xclk >= 0) { + ESP_LOGD(TAG, "Enabling XCLK output"); + CAMERA_ENABLE_OUT_CLOCK(config); + } + + if (config->pin_sscb_sda != -1) { + ESP_LOGD(TAG, "Initializing SSCB"); + SCCB_Init(config->pin_sscb_sda, config->pin_sscb_scl); + } + + if (config->pin_pwdn >= 0) { + ESP_LOGD(TAG, "Resetting camera by power down line"); + 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) { + CAMERA_DISABLE_OUT_CLOCK(); + return ESP_ERR_NOT_FOUND; + } + + ESP_LOGI(TAG, "Detected camera at address=0x%02x", slv_addr); + s_state->sensor.slv_addr = slv_addr; + s_state->sensor.xclk_freq_hz = config->xclk_freq_hz; + + /** + * Read sensor ID + */ + sensor_id_t *id = &s_state->sensor.id; + + if (slv_addr == OV2640_SCCB_ADDR || slv_addr == OV7725_SCCB_ADDR) { + SCCB_Write(slv_addr, 0xFF, 0x01);//bank sensor + id->PID = SCCB_Read(slv_addr, REG_PID); + id->VER = SCCB_Read(slv_addr, REG_VER); + id->MIDL = SCCB_Read(slv_addr, REG_MIDL); + id->MIDH = SCCB_Read(slv_addr, REG_MIDH); + } else if (slv_addr == OV5640_SCCB_ADDR || slv_addr == OV3660_SCCB_ADDR) { + id->PID = SCCB_Read16(slv_addr, REG16_CHIDH); + id->VER = SCCB_Read16(slv_addr, REG16_CHIDL); + } else if (slv_addr == NT99141_SCCB_ADDR) { + SCCB_Write16(slv_addr, 0x3008, 0x01);//bank sensor + id->PID = SCCB_Read16(slv_addr, 0x3000); + id->VER = SCCB_Read16(slv_addr, 0x3001); + if (config->xclk_freq_hz > 10000000) { + ESP_LOGE(TAG, "NT99141: only XCLK under 10MHz is supported, and XCLK is now set to 10M"); + s_state->sensor.xclk_freq_hz = 10000000; + } + } + vTaskDelay(10 / portTICK_PERIOD_MS); + ESP_LOGI(TAG, "Camera PID=0x%02x VER=0x%02x MIDL=0x%02x MIDH=0x%02x", + id->PID, id->VER, id->MIDH, id->MIDL); + + /** + * Initialize sensor according to sensor ID + */ + 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 +#if CONFIG_OV7670_SUPPORT + case OV7670_PID: + *out_camera_model = CAMERA_OV7670; + ov7670_init(&s_state->sensor); + break; +#endif +#if CONFIG_NT99141_SUPPORT + case NT99141_PID: + *out_camera_model = CAMERA_NT99141; + NT99141_init(&s_state->sensor); + break; +#endif + default: + id->PID = 0; + CAMERA_DISABLE_OUT_CLOCK(); + ESP_LOGE(TAG, "Detected camera not supported."); + return ESP_ERR_NOT_SUPPORTED; + } + + ESP_LOGD(TAG, "Doing SW reset of sensor"); + s_state->sensor.reset(&s_state->sensor); + + return ESP_OK; +} + + +esp_err_t esp_camera_init(const camera_config_t *config) +{ + esp_err_t err; + err = cam_init(config); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Camera init failed with error 0x%x", err); + return err; + } + + camera_model_t camera_model = CAMERA_NONE; + err = camera_probe(config, &camera_model); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Camera probe failed with error 0x%x(%s)", err, esp_err_to_name(err)); + goto fail; + } + if (camera_model == CAMERA_OV7725) { + ESP_LOGI(TAG, "Detected OV7725 camera"); + } else if (camera_model == CAMERA_OV2640) { + ESP_LOGI(TAG, "Detected OV2640 camera"); + } else if (camera_model == CAMERA_OV3660) { + ESP_LOGI(TAG, "Detected OV3660 camera"); + } else if (camera_model == CAMERA_OV5640) { + ESP_LOGI(TAG, "Detected OV5640 camera"); + } else if (camera_model == CAMERA_OV7670) { + ESP_LOGI(TAG, "Detected OV7670 camera"); + } else if (camera_model == CAMERA_NT99141) { + ESP_LOGI(TAG, "Detected NT99141 camera"); + } else { + ESP_LOGI(TAG, "Camera not supported"); + err = ESP_ERR_CAMERA_NOT_SUPPORTED; + goto fail; + } + + framesize_t frame_size = (framesize_t) config->frame_size; + pixformat_t pix_format = (pixformat_t) config->pixel_format; + + if (frame_size > camera_sensor[camera_model].max_size) { + frame_size = camera_sensor[camera_model].max_size; + } + + err = cam_config(config, frame_size, s_state->sensor.id.PID); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Camera config failed with error 0x%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 (pix_format == PIXFORMAT_JPEG) { + (*s_state->sensor.set_quality)(&s_state->sensor, config->jpeg_quality); + } + s_state->sensor.init_status(&s_state->sensor); + + cam_start(); + + return ESP_OK; + +fail: + CAMERA_DISABLE_OUT_CLOCK(); + return err; +} + +esp_err_t esp_camera_deinit() +{ + esp_err_t ret = cam_deinit(); + if (s_state) { + SCCB_Deinit(); + + free(s_state); + s_state = NULL; + } + + return ret; +} + +#define FB_GET_TIMEOUT (4000 / portTICK_PERIOD_MS) + +camera_fb_t *esp_camera_fb_get() +{ + if (s_state == NULL) { + return NULL; + } + camera_fb_t *fb = cam_take(FB_GET_TIMEOUT); + //set the frame properties + if (fb) { + fb->width = resolution[s_state->sensor.status.framesize].width; + fb->height = resolution[s_state->sensor.status.framesize].height; + fb->format = s_state->sensor.pixformat; + } + return fb; +} + +void esp_camera_fb_return(camera_fb_t *fb) +{ + if (s_state == NULL) { + return; + } + cam_give(fb); +} + +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) +{ +#if ESP_IDF_VERSION_MAJOR > 3 + 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) +{ +#if ESP_IDF_VERSION_MAJOR > 3 + 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/esp_camera.h b/esp32-cam/esp_camera.h index dadd0c0..bfb729b 100644 --- a/esp32-cam/esp_camera.h +++ b/esp32-cam/esp_camera.h @@ -38,7 +38,8 @@ .pixel_format = PIXFORMAT_JPEG, .frame_size = FRAMESIZE_SVGA, .jpeg_quality = 10, - .fb_count = 2 + .fb_count = 2, + .grab_mode = CAMERA_GRAB_WHEN_EMPTY }; esp_err_t camera_example_init(){ @@ -74,6 +75,14 @@ extern "C" { #endif +/** + * @brief Configuration structure for camera initialization + */ +typedef enum { + CAMERA_GRAB_WHEN_EMPTY, /*!< Fills buffers when they are empty. Less resources but first 'fb_count' frames might be old */ + CAMERA_GRAB_LATEST /*!< Except when 1 frame buffer is used, queue will always contain the last 'fb_count' frames */ +} camera_grab_mode_t; + /** * @brief Configuration structure for camera initialization */ @@ -95,7 +104,7 @@ typedef struct { 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) */ + int xclk_freq_hz; /*!< Frequency of XCLK signal, in Hz. EXPERIMENTAL: Set to 16MHz on ESP32-S2 or ESP32-S3 to enable EDMA mode */ 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 */ @@ -105,6 +114,7 @@ typedef struct { 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_grab_mode_t grab_mode; /*!< When buffers should be filled */ } camera_config_t; /** diff --git a/esp32-cam/esp_jpg_decode.c b/esp32-cam/esp_jpg_decode.c index d42794f..a9615e3 100644 --- a/esp32-cam/esp_jpg_decode.c +++ b/esp32-cam/esp_jpg_decode.c @@ -17,7 +17,11 @@ #if ESP_IDF_VERSION_MAJOR >= 4 // IDF 4+ #if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 #include "esp32/rom/tjpgd.h" -#else +#elif CONFIG_IDF_TARGET_ESP32S2 +#include "tjpgd.h" +#elif CONFIG_IDF_TARGET_ESP32S3 +#include "esp32s3/rom/tjpgd.h" +#else #error Target CONFIG_IDF_TARGET is not supported #endif #else // ESP32 Before IDF 4.0 diff --git a/esp32-cam/img_converters.h b/esp32-cam/img_converters.h index 330f8db..f736200 100644 --- a/esp32-cam/img_converters.h +++ b/esp32-cam/img_converters.h @@ -22,6 +22,7 @@ extern "C" { #include #include #include "esp_camera.h" +#include "esp_jpg_decode.h" typedef size_t (* jpg_out_cb)(void * arg, size_t index, const void* data, size_t len); @@ -120,6 +121,8 @@ bool frame2bmp(camera_fb_t * fb, uint8_t ** out, size_t * out_len); */ bool fmt2rgb888(const uint8_t *src_buf, size_t src_len, pixformat_t format, uint8_t * rgb_buf); +bool jpg2rgb565(const uint8_t *src, size_t src_len, uint8_t * out, jpg_scale_t scale); + #ifdef __cplusplus } #endif diff --git a/esp32-cam/library.json b/esp32-cam/library.json new file mode 100644 index 0000000..322e932 --- /dev/null +++ b/esp32-cam/library.json @@ -0,0 +1,26 @@ +{ + "name": "esp32-camera", + "version": "1.0.0", + "keywords": "esp32, camera, espressif, esp32-cam", + "description": "ESP32 compatible driver for OV2640, OV3660, OV5640, OV7670 and OV7725 image sensors.", + "repository": { + "type": "git", + "url": "https://github.com/espressif/esp32-camera" + }, + "frameworks": "espidf", + "platforms": "*", + "build": { + "flags": [ + "-Idriver/include", + "-Iconversions/include", + "-Idriver/private_include", + "-Iconversions/private_include", + "-Isensors/private_include", + "-Itarget/private_include", + "-fno-rtti" + ], + "includeDir": ".", + "srcDir": ".", + "srcFilter": ["-<*>", "+", "+", "+"] + } +} diff --git a/esp32-cam/ll_cam.c b/esp32-cam/ll_cam.c new file mode 100644 index 0000000..8dc1fc6 --- /dev/null +++ b/esp32-cam/ll_cam.c @@ -0,0 +1,515 @@ +// Copyright 2010-2020 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 "soc/i2s_struct.h" +#include "esp_idf_version.h" +#if ESP_IDF_VERSION_MAJOR >= 4 +#include "hal/gpio_ll.h" +#else +#include "rom/ets_sys.h" +#include "soc/gpio_periph.h" +#define esp_rom_delay_us ets_delay_us +static inline int gpio_ll_get_level(gpio_dev_t *hw, int gpio_num) +{ + if (gpio_num < 32) { + return (hw->in >> gpio_num) & 0x1; + } else { + return (hw->in1.data >> (gpio_num - 32)) & 0x1; + } +} +#endif +#include "ll_cam.h" +#include "xclk.h" +#include "cam_hal.h" + +static const char *TAG = "esp32 ll_cam"; + +#define I2S_ISR_ENABLE(i) {I2S0.int_clr.i = 1;I2S0.int_ena.i = 1;} +#define I2S_ISR_DISABLE(i) {I2S0.int_ena.i = 0;I2S0.int_clr.i = 1;} + +typedef union { + struct { + uint32_t sample2:8; + uint32_t unused2:8; + uint32_t sample1:8; + uint32_t unused1:8; + }; + 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; + +typedef size_t (*dma_filter_t)(uint8_t* dst, const uint8_t* src, size_t len); + +static i2s_sampling_mode_t sampling_mode = SM_0A00_0B00; + +static size_t ll_cam_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 size_t IRAM_ATTR ll_cam_dma_filter_jpeg(uint8_t* dst, const uint8_t* src, size_t len) +{ + const dma_elem_t* dma_el = (const dma_elem_t*)src; + size_t elements = len / sizeof(dma_elem_t); + size_t end = elements / 4; + // manually unrolling 4 iterations of the loop here + for (size_t i = 0; i < end; ++i) { + dst[0] = dma_el[0].sample1; + dst[1] = dma_el[1].sample1; + dst[2] = dma_el[2].sample1; + dst[3] = dma_el[3].sample1; + dma_el += 4; + dst += 4; + } + return elements; +} + +static size_t IRAM_ATTR ll_cam_dma_filter_grayscale(uint8_t* dst, const uint8_t* src, size_t len) +{ + const dma_elem_t* dma_el = (const dma_elem_t*)src; + size_t elements = len / sizeof(dma_elem_t); + size_t end = elements / 4; + for (size_t i = 0; i < end; ++i) { + // manually unrolling 4 iterations of the loop here + dst[0] = dma_el[0].sample1; + dst[1] = dma_el[1].sample1; + dst[2] = dma_el[2].sample1; + dst[3] = dma_el[3].sample1; + dma_el += 4; + dst += 4; + } + return elements; +} + +static size_t IRAM_ATTR ll_cam_dma_filter_grayscale_highspeed(uint8_t* dst, const uint8_t* src, size_t len) +{ + const dma_elem_t* dma_el = (const dma_elem_t*)src; + size_t elements = len / sizeof(dma_elem_t); + size_t end = elements / 8; + for (size_t i = 0; i < end; ++i) { + // manually unrolling 4 iterations of the loop here + dst[0] = dma_el[0].sample1; + dst[1] = dma_el[2].sample1; + dst[2] = dma_el[4].sample1; + dst[3] = dma_el[6].sample1; + dma_el += 8; + dst += 4; + } + // the final sample of a line in SM_0A0B_0B0C sampling mode needs special handling + if ((elements & 0x7) != 0) { + dst[0] = dma_el[0].sample1; + dst[1] = dma_el[2].sample1; + elements += 1; + } + return elements / 2; +} + +static size_t IRAM_ATTR ll_cam_dma_filter_yuyv(uint8_t* dst, const uint8_t* src, size_t len) +{ + const dma_elem_t* dma_el = (const dma_elem_t*)src; + size_t elements = len / sizeof(dma_elem_t); + size_t end = elements / 4; + for (size_t i = 0; i < end; ++i) { + dst[0] = dma_el[0].sample1;//y0 + dst[1] = dma_el[0].sample2;//u + dst[2] = dma_el[1].sample1;//y1 + dst[3] = dma_el[1].sample2;//v + + dst[4] = dma_el[2].sample1;//y0 + dst[5] = dma_el[2].sample2;//u + dst[6] = dma_el[3].sample1;//y1 + dst[7] = dma_el[3].sample2;//v + dma_el += 4; + dst += 8; + } + return elements * 2; +} + +static size_t IRAM_ATTR ll_cam_dma_filter_yuyv_highspeed(uint8_t* dst, const uint8_t* src, size_t len) +{ + const dma_elem_t* dma_el = (const dma_elem_t*)src; + size_t elements = len / sizeof(dma_elem_t); + size_t end = elements / 8; + for (size_t i = 0; i < end; ++i) { + dst[0] = dma_el[0].sample1;//y0 + dst[1] = dma_el[1].sample1;//u + dst[2] = dma_el[2].sample1;//y1 + dst[3] = dma_el[3].sample1;//v + + dst[4] = dma_el[4].sample1;//y0 + dst[5] = dma_el[5].sample1;//u + dst[6] = dma_el[6].sample1;//y1 + dst[7] = dma_el[7].sample1;//v + dma_el += 8; + dst += 8; + } + if ((elements & 0x7) != 0) { + dst[0] = dma_el[0].sample1;//y0 + dst[1] = dma_el[1].sample1;//u + dst[2] = dma_el[2].sample1;//y1 + dst[3] = dma_el[2].sample2;//v + elements += 4; + } + return elements; +} + +static void IRAM_ATTR ll_cam_vsync_isr(void *arg) +{ + //DBG_PIN_SET(1); + cam_obj_t *cam = (cam_obj_t *)arg; + BaseType_t HPTaskAwoken = pdFALSE; + // filter + esp_rom_delay_us(1); + if (gpio_ll_get_level(&GPIO, cam->vsync_pin) == !cam->vsync_invert) { + ll_cam_send_event(cam, CAM_VSYNC_EVENT, &HPTaskAwoken); + if (HPTaskAwoken == pdTRUE) { + portYIELD_FROM_ISR(); + } + } + //DBG_PIN_SET(0); +} + +static void IRAM_ATTR ll_cam_dma_isr(void *arg) +{ + //DBG_PIN_SET(1); + cam_obj_t *cam = (cam_obj_t *)arg; + BaseType_t HPTaskAwoken = pdFALSE; + + typeof(I2S0.int_st) status = I2S0.int_st; + if (status.val == 0) { + return; + } + + I2S0.int_clr.val = status.val; + + if (status.in_suc_eof) { + ll_cam_send_event(cam, CAM_IN_SUC_EOF_EVENT, &HPTaskAwoken); + } + if (HPTaskAwoken == pdTRUE) { + portYIELD_FROM_ISR(); + } + //DBG_PIN_SET(0); +} + +bool ll_cam_stop(cam_obj_t *cam) +{ + I2S0.conf.rx_start = 0; + I2S_ISR_DISABLE(in_suc_eof); + I2S0.in_link.stop = 1; + return true; +} + +bool ll_cam_start(cam_obj_t *cam, int frame_pos) +{ + I2S0.conf.rx_start = 0; + + I2S_ISR_ENABLE(in_suc_eof); + + I2S0.conf.rx_reset = 1; + I2S0.conf.rx_reset = 0; + I2S0.conf.rx_fifo_reset = 1; + I2S0.conf.rx_fifo_reset = 0; + I2S0.lc_conf.in_rst = 1; + I2S0.lc_conf.in_rst = 0; + I2S0.lc_conf.ahbm_fifo_rst = 1; + I2S0.lc_conf.ahbm_fifo_rst = 0; + I2S0.lc_conf.ahbm_rst = 1; + I2S0.lc_conf.ahbm_rst = 0; + + I2S0.rx_eof_num = cam->dma_half_buffer_size / sizeof(dma_elem_t); + I2S0.in_link.addr = ((uint32_t)&cam->dma[0]) & 0xfffff; + + I2S0.in_link.start = 1; + I2S0.conf.rx_start = 1; + return true; +} + +esp_err_t ll_cam_config(cam_obj_t *cam, const camera_config_t *config) +{ + // Enable and configure I2S peripheral + periph_module_enable(PERIPH_I2S0_MODULE); + + I2S0.conf.rx_reset = 1; + I2S0.conf.rx_reset = 0; + I2S0.conf.rx_fifo_reset = 1; + I2S0.conf.rx_fifo_reset = 0; + I2S0.lc_conf.in_rst = 1; + I2S0.lc_conf.in_rst = 0; + I2S0.lc_conf.ahbm_fifo_rst = 1; + I2S0.lc_conf.ahbm_fifo_rst = 0; + I2S0.lc_conf.ahbm_rst = 1; + I2S0.lc_conf.ahbm_rst = 0; + + I2S0.conf.rx_slave_mod = 1; + 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.conf2.lcd_en = 1; + I2S0.conf2.camera_en = 1; + + // Configure clock divider + I2S0.clkm_conf.clkm_div_a = 0; + I2S0.clkm_conf.clkm_div_b = 0; + I2S0.clkm_conf.clkm_div_num = 2; + + I2S0.fifo_conf.dscr_en = 1; + I2S0.fifo_conf.rx_fifo_mod = sampling_mode; + I2S0.fifo_conf.rx_fifo_mod_force_en = 1; + + I2S0.conf_chan.rx_chan_mod = 1; + I2S0.sample_rate_conf.rx_bits_mod = 0; + I2S0.timing.val = 0; + I2S0.timing.rx_dsync_sw = 1; + + return ESP_OK; +} + +void ll_cam_vsync_intr_enable(cam_obj_t *cam, bool en) +{ + if (en) { + gpio_intr_enable(cam->vsync_pin); + } else { + gpio_intr_disable(cam->vsync_pin); + } +} + +esp_err_t ll_cam_set_pin(cam_obj_t *cam, const camera_config_t *config) +{ + gpio_config_t io_conf = {0}; + io_conf.intr_type = cam->vsync_invert ? GPIO_PIN_INTR_NEGEDGE : GPIO_PIN_INTR_POSEDGE; + io_conf.pin_bit_mask = 1ULL << config->pin_vsync; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pull_up_en = 1; + io_conf.pull_down_en = 0; + gpio_config(&io_conf); + gpio_install_isr_service(ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM); + gpio_isr_handler_add(config->pin_vsync, ll_cam_vsync_isr, cam); + gpio_intr_disable(config->pin_vsync); + + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[config->pin_pclk], PIN_FUNC_GPIO); + gpio_set_direction(config->pin_pclk, GPIO_MODE_INPUT); + gpio_set_pull_mode(config->pin_pclk, GPIO_FLOATING); + gpio_matrix_in(config->pin_pclk, I2S0I_WS_IN_IDX, false); + + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[config->pin_vsync], PIN_FUNC_GPIO); + gpio_set_direction(config->pin_vsync, GPIO_MODE_INPUT); + gpio_set_pull_mode(config->pin_vsync, GPIO_FLOATING); + gpio_matrix_in(config->pin_vsync, I2S0I_V_SYNC_IDX, false); + + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[config->pin_href], PIN_FUNC_GPIO); + gpio_set_direction(config->pin_href, GPIO_MODE_INPUT); + gpio_set_pull_mode(config->pin_href, GPIO_FLOATING); + gpio_matrix_in(config->pin_href, I2S0I_H_SYNC_IDX, false); + + int data_pins[8] = { + config->pin_d0, config->pin_d1, config->pin_d2, config->pin_d3, config->pin_d4, config->pin_d5, config->pin_d6, config->pin_d7, + }; + for (int i = 0; i < 8; i++) { + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[data_pins[i]], PIN_FUNC_GPIO); + gpio_set_direction(data_pins[i], GPIO_MODE_INPUT); + gpio_set_pull_mode(data_pins[i], GPIO_FLOATING); + gpio_matrix_in(data_pins[i], I2S0I_DATA_IN0_IDX + i, false); + } + + gpio_matrix_in(0x38, I2S0I_H_ENABLE_IDX, false); + return ESP_OK; +} + +esp_err_t ll_cam_init_isr(cam_obj_t *cam) +{ + return esp_intr_alloc(ETS_I2S0_INTR_SOURCE, ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM, ll_cam_dma_isr, cam, &cam->cam_intr_handle); +} + +void ll_cam_do_vsync(cam_obj_t *cam) +{ +} + +uint8_t ll_cam_get_dma_align(cam_obj_t *cam) +{ + return 0; +} + +static bool ll_cam_calc_rgb_dma(cam_obj_t *cam){ + size_t dma_half_buffer_max = 16 * 1024 / cam->dma_bytes_per_item; + size_t dma_buffer_max = 2 * dma_half_buffer_max; + size_t node_max = LCD_CAM_DMA_NODE_BUFFER_MAX_SIZE / cam->dma_bytes_per_item; + + size_t line_width = cam->width * cam->in_bytes_per_pixel; + size_t image_size = cam->height * line_width; + if (image_size > (2 * 1024 * 1024) || (line_width > dma_half_buffer_max)) { + ESP_LOGE(TAG, "Resolution too high"); + return 0; + } + + size_t node_size = node_max; + size_t nodes_per_line = 1; + size_t lines_per_node = 1; + size_t lines_per_half_buffer = 1; + size_t dma_half_buffer_min = node_max; + size_t dma_half_buffer = dma_half_buffer_max; + size_t dma_buffer_size = dma_buffer_max; + + // Calculate DMA Node Size so that it's divisable by or divisor of the line width + if(line_width >= node_max){ + // One or more nodes will be requied for one line + for(size_t i = node_max; i > 0; i=i-1){ + if ((line_width % i) == 0) { + node_size = i; + nodes_per_line = line_width / node_size; + break; + } + } + } else { + // One or more lines can fit into one node + for(size_t i = node_max; i > 0; i=i-1){ + if ((i % line_width) == 0) { + node_size = i; + lines_per_node = node_size / line_width; + while((cam->height % lines_per_node) != 0){ + lines_per_node = lines_per_node - 1; + node_size = lines_per_node * line_width; + } + break; + } + } + } + // Calculate minimum EOF size = max(mode_size, line_size) + dma_half_buffer_min = node_size * nodes_per_line; + // Calculate max EOF size divisable by node size + dma_half_buffer = (dma_half_buffer_max / dma_half_buffer_min) * dma_half_buffer_min; + // Adjust EOF size so that height will be divisable by the number of lines in each EOF + lines_per_half_buffer = dma_half_buffer / line_width; + while((cam->height % lines_per_half_buffer) != 0){ + dma_half_buffer = dma_half_buffer - dma_half_buffer_min; + lines_per_half_buffer = dma_half_buffer / line_width; + } + // Calculate DMA size + dma_buffer_size =(dma_buffer_max / dma_half_buffer) * dma_half_buffer; + + ESP_LOGI(TAG, "node_size: %4u, nodes_per_line: %u, lines_per_node: %u, dma_half_buffer_min: %5u, dma_half_buffer: %5u, lines_per_half_buffer: %2u, dma_buffer_size: %5u, image_size: %u", + node_size * cam->dma_bytes_per_item, nodes_per_line, lines_per_node, dma_half_buffer_min * cam->dma_bytes_per_item, dma_half_buffer * cam->dma_bytes_per_item, lines_per_half_buffer, dma_buffer_size * cam->dma_bytes_per_item, image_size); + + cam->dma_buffer_size = dma_buffer_size * cam->dma_bytes_per_item; + cam->dma_half_buffer_size = dma_half_buffer * cam->dma_bytes_per_item; + cam->dma_node_buffer_size = node_size * cam->dma_bytes_per_item; + cam->dma_half_buffer_cnt = cam->dma_buffer_size / cam->dma_half_buffer_size; + return 1; +} + +bool ll_cam_dma_sizes(cam_obj_t *cam) +{ + cam->dma_bytes_per_item = ll_cam_bytes_per_sample(sampling_mode); + if (cam->jpeg_mode) { + cam->dma_half_buffer_cnt = 8; + cam->dma_node_buffer_size = 2048; + cam->dma_half_buffer_size = cam->dma_node_buffer_size * 2; + cam->dma_buffer_size = cam->dma_half_buffer_cnt * cam->dma_half_buffer_size; + } else { + return ll_cam_calc_rgb_dma(cam); + } + return 1; +} + +static dma_filter_t dma_filter = ll_cam_dma_filter_jpeg; + +size_t IRAM_ATTR ll_cam_memcpy(cam_obj_t *cam, uint8_t *out, const uint8_t *in, size_t len) +{ + //DBG_PIN_SET(1); + size_t r = dma_filter(out, in, len); + //DBG_PIN_SET(0); + return r; +} + +esp_err_t ll_cam_set_sample_mode(cam_obj_t *cam, pixformat_t pix_format, uint32_t xclk_freq_hz, uint8_t sensor_pid) +{ + if (pix_format == PIXFORMAT_GRAYSCALE) { + if (sensor_pid == OV3660_PID || sensor_pid == OV5640_PID || sensor_pid == NT99141_PID) { + if (xclk_freq_hz > 10000000) { + sampling_mode = SM_0A00_0B00; + dma_filter = ll_cam_dma_filter_yuyv_highspeed; + } else { + sampling_mode = SM_0A0B_0C0D; + dma_filter = ll_cam_dma_filter_yuyv; + } + cam->in_bytes_per_pixel = 1; // camera sends Y8 + } else { + if (xclk_freq_hz > 10000000 && sensor_pid != OV7725_PID) { + sampling_mode = SM_0A00_0B00; + dma_filter = ll_cam_dma_filter_grayscale_highspeed; + } else { + sampling_mode = SM_0A0B_0C0D; + dma_filter = ll_cam_dma_filter_grayscale; + } + cam->in_bytes_per_pixel = 2; // camera sends YU/YV + } + cam->fb_bytes_per_pixel = 1; // frame buffer stores Y8 + } else if (pix_format == PIXFORMAT_YUV422 || pix_format == PIXFORMAT_RGB565) { + if (xclk_freq_hz > 10000000 && sensor_pid != OV7725_PID) { + if (sensor_pid == OV7670_PID) { + sampling_mode = SM_0A0B_0B0C; + } else { + sampling_mode = SM_0A00_0B00; + } + dma_filter = ll_cam_dma_filter_yuyv_highspeed; + } else { + sampling_mode = SM_0A0B_0C0D; + dma_filter = ll_cam_dma_filter_yuyv; + } + cam->in_bytes_per_pixel = 2; // camera sends YU/YV + cam->fb_bytes_per_pixel = 2; // frame buffer stores YU/YV/RGB565 + } else if (pix_format == PIXFORMAT_JPEG) { + if (sensor_pid != OV2640_PID && sensor_pid != OV3660_PID && sensor_pid != OV5640_PID && sensor_pid != NT99141_PID) { + ESP_LOGE(TAG, "JPEG format is not supported on this sensor"); + return ESP_ERR_NOT_SUPPORTED; + } + cam->in_bytes_per_pixel = 1; + cam->fb_bytes_per_pixel = 1; + dma_filter = ll_cam_dma_filter_jpeg; + sampling_mode = SM_0A00_0B00; + } else { + ESP_LOGE(TAG, "Requested format is not supported"); + return ESP_ERR_NOT_SUPPORTED; + } + I2S0.fifo_conf.rx_fifo_mod = sampling_mode; + return ESP_OK; +} diff --git a/esp32-cam/ll_cam.h b/esp32-cam/ll_cam.h new file mode 100644 index 0000000..0df922d --- /dev/null +++ b/esp32-cam/ll_cam.h @@ -0,0 +1,136 @@ +// Copyright 2010-2020 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. + +#pragma once + +#include +#include "sdkconfig.h" +#if CONFIG_IDF_TARGET_ESP32 +#if ESP_IDF_VERSION_MAJOR >= 4 +#include "esp32/rom/lldesc.h" +#else +#include "rom/lldesc.h" +#endif +#elif CONFIG_IDF_TARGET_ESP32S2 +#include "esp32s2/rom/lldesc.h" +#elif CONFIG_IDF_TARGET_ESP32S3 +#include "esp32s3/rom/lldesc.h" +#endif +#include "esp_log.h" +#include "esp_camera.h" +#include "camera_common.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/task.h" +#include "freertos/semphr.h" + +#define CAMERA_DBG_PIN_ENABLE 0 +#if CAMERA_DBG_PIN_ENABLE + #if CONFIG_IDF_TARGET_ESP32 + #define DBG_PIN_NUM 26 + #else + #define DBG_PIN_NUM 7 + #endif + #include "hal/gpio_ll.h" + #define DBG_PIN_SET(v) gpio_ll_set_level(&GPIO, DBG_PIN_NUM, v) +#else + #define DBG_PIN_SET(v) +#endif + +#define CAM_CHECK(a, str, ret) if (!(a)) { \ + ESP_LOGE(TAG,"%s(%d): %s", __FUNCTION__, __LINE__, str); \ + return (ret); \ + } + +#define CAM_CHECK_GOTO(a, str, lab) if (!(a)) { \ + ESP_LOGE(TAG,"%s(%d): %s", __FUNCTION__, __LINE__, str); \ + goto lab; \ + } + +#define LCD_CAM_DMA_NODE_BUFFER_MAX_SIZE (4092) + +typedef enum { + CAM_IN_SUC_EOF_EVENT = 0, + CAM_VSYNC_EVENT +} cam_event_t; + +typedef enum { + CAM_STATE_IDLE = 0, + CAM_STATE_READ_BUF = 1, +} cam_state_t; + +typedef struct { + camera_fb_t fb; + uint8_t en; + //for RGB/YUV modes + lldesc_t *dma; + size_t fb_offset; +} cam_frame_t; + +typedef struct { + uint32_t dma_bytes_per_item; + uint32_t dma_buffer_size; + uint32_t dma_half_buffer_size; + uint32_t dma_half_buffer_cnt; + uint32_t dma_node_buffer_size; + uint32_t dma_node_cnt; + uint32_t frame_copy_cnt; + + //for JPEG mode + lldesc_t *dma; + uint8_t *dma_buffer; + + cam_frame_t *frames; + + QueueHandle_t event_queue; + QueueHandle_t frame_buffer_queue; + TaskHandle_t task_handle; + intr_handle_t cam_intr_handle; + + uint8_t dma_num;//ESP32-S3 + intr_handle_t dma_intr_handle;//ESP32-S3 + + uint8_t jpeg_mode; + uint8_t vsync_pin; + uint8_t vsync_invert; + uint32_t frame_cnt; + uint32_t recv_size; + bool swap_data; + bool psram_mode; + + //for RGB/YUV modes + uint16_t width; + uint16_t height; + uint8_t in_bytes_per_pixel; + uint8_t fb_bytes_per_pixel; + uint32_t fb_size; + + cam_state_t state; +} cam_obj_t; + + +bool ll_cam_stop(cam_obj_t *cam); +bool ll_cam_start(cam_obj_t *cam, int frame_pos); +esp_err_t ll_cam_config(cam_obj_t *cam, const camera_config_t *config); +void ll_cam_vsync_intr_enable(cam_obj_t *cam, bool en); +esp_err_t ll_cam_set_pin(cam_obj_t *cam, const camera_config_t *config); +esp_err_t ll_cam_init_isr(cam_obj_t *cam); +void ll_cam_do_vsync(cam_obj_t *cam); +uint8_t ll_cam_get_dma_align(cam_obj_t *cam); +bool ll_cam_dma_sizes(cam_obj_t *cam); +size_t ll_cam_memcpy(cam_obj_t *cam, uint8_t *out, const uint8_t *in, size_t len); +esp_err_t ll_cam_set_sample_mode(cam_obj_t *cam, pixformat_t pix_format, uint32_t xclk_freq_hz, uint8_t sensor_pid); + +// implemented in cam_hal +void ll_cam_send_event(cam_obj_t *cam, cam_event_t cam_event, BaseType_t * HPTaskAwoken); diff --git a/esp32-cam/nt99141.c b/esp32-cam/nt99141.c index 07a9cc4..450780b 100644 --- a/esp32-cam/nt99141.c +++ b/esp32-cam/nt99141.c @@ -144,28 +144,6 @@ static int write_addr_reg(uint8_t slv_addr, const uint16_t reg, uint16_t x_value #define write_reg_bits(slv_addr, reg, mask, enable) set_reg_bits(slv_addr, reg, 0, mask, enable?mask:0) -static int calc_sysclk(int xclk, bool pll_bypass, int pll_multiplier, int pll_sys_div, int pll_pre_div, bool pll_root_2x, int pll_seld5, bool pclk_manual, int pclk_div) -{ - const int pll_pre_div2x_map[] = { 2, 3, 4, 6 };//values are multiplied by two to avoid floats - const int pll_seld52x_map[] = { 2, 2, 4, 5 }; - - if (!pll_sys_div) { - pll_sys_div = 1; - } - - int pll_pre_div2x = pll_pre_div2x_map[pll_pre_div]; - int pll_root_div = pll_root_2x ? 2 : 1; - int pll_seld52x = pll_seld52x_map[pll_seld5]; - - int VCO = (xclk / 1000) * pll_multiplier * pll_root_div * 2 / pll_pre_div2x; - int PLLCLK = pll_bypass ? (xclk) : (VCO * 1000 * 2 / pll_sys_div / pll_seld52x); - int PCLK = PLLCLK / 2 / ((pclk_manual && pclk_div) ? pclk_div : 1); - int SYSCLK = PLLCLK / 4; - - ESP_LOGD(TAG, "Calculated VCO: %d Hz, PLLCLK: %d Hz, SYSCLK: %d Hz, PCLK: %d Hz", VCO * 1000, PLLCLK, SYSCLK, PCLK); - return SYSCLK; -} - static int set_pll(sensor_t *sensor, bool bypass, uint8_t multiplier, uint8_t sys_div, uint8_t pre_div, bool root_2x, uint8_t seld5, bool pclk_manual, uint8_t pclk_div) { return -1; @@ -309,7 +287,7 @@ static int set_framesize(sensor_t *sensor, framesize_t framesize) ret = write_regs(sensor->slv_addr, sensor_framesize_VGA); } - return 0; + return ret; } static int set_hmirror(sensor_t *sensor, int enable) @@ -682,7 +660,6 @@ static int set_brightness(sensor_t *sensor, int level) { int ret = 0; uint8_t value = 0; - bool negative = false; switch (level) { case 3: @@ -699,17 +676,14 @@ static int set_brightness(sensor_t *sensor, int level) case -1: value = 0x78; - negative = true; break; case -2: value = 0x70; - negative = true; break; case -3: value = 0x60; - negative = true; break; default: // 0 @@ -730,7 +704,6 @@ static int set_contrast(sensor_t *sensor, int level) { int ret = 0; uint8_t value1 = 0, value2 = 0 ; - bool negative = false; switch (level) { case 3: diff --git a/esp32-cam/ov2640.c b/esp32-cam/ov2640.c index 811023c..e96d4c2 100644 --- a/esp32-cam/ov2640.c +++ b/esp32-cam/ov2640.c @@ -157,26 +157,40 @@ static int set_window(sensor_t *sensor, ov2640_sensor_mode_t mode, int offset_x, {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; + if (sensor->pixformat == PIXFORMAT_JPEG) { + c.clk_2x = 0; + c.clk_div = 0; + c.pclk_auto = 0; + c.pclk_div = 8; + if(mode == OV2640_MODE_UXGA) { + c.pclk_div = 12; + } + // if (sensor->xclk_freq_hz == 16000000) { + // c.pclk_div = c.pclk_div / 2; + // } + } else { +#if CONFIG_IDF_TARGET_ESP32 + c.clk_2x = 0; +#else + c.clk_2x = 1; +#endif c.clk_div = 7; + c.pclk_auto = 1; + c.pclk_div = 8; + if (mode == OV2640_MODE_CIF) { + c.clk_div = 3; + } else if(mode == OV2640_MODE_UXGA) { + c.pclk_div = 12; + } } + ESP_LOGI(TAG, "Set PLL: clk_2x: %u, clk_div: %u, pclk_auto: %u, pclk_div: %u", c.clk_2x, c.clk_div, c.pclk_auto, c.pclk_div); 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); diff --git a/esp32-cam/ov3660.c b/esp32-cam/ov3660.c index 723ec5c..59393b7 100644 --- a/esp32-cam/ov3660.c +++ b/esp32-cam/ov3660.c @@ -142,7 +142,7 @@ static int calc_sysclk(int xclk, bool pll_bypass, int pll_multiplier, int pll_sy 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); + ESP_LOGI(TAG, "Calculated VCO: %d Hz, PLLCLK: %d Hz, SYSCLK: %d Hz, PCLK: %d Hz", VCO*1000, PLLCLK, SYSCLK, PCLK); return SYSCLK; } @@ -310,13 +310,13 @@ static int set_image_options(sensor_t *sensor) 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; + ESP_LOGW(TAG, "Invalid framesize: %u", framesize); + framesize = FRAMESIZE_QXGA; } + framesize_t old_framesize = sensor->status.framesize; + sensor->status.framesize = framesize; uint16_t w = resolution[framesize].width; uint16_t h = resolution[framesize].height; aspect_ratio_t ratio = resolution[sensor->status.framesize].aspect_ratio; @@ -355,7 +355,7 @@ static int set_framesize(sensor_t *sensor, framesize_t framesize) } if (sensor->pixformat == PIXFORMAT_JPEG) { - if (framesize == FRAMESIZE_QXGA) { + if (framesize == FRAMESIZE_QXGA || sensor->xclk_freq_hz == 16000000) { //40MHz SYSCLK and 10MHz PCLK ret = set_pll(sensor, false, 24, 1, 3, false, 0, true, 8); } else { @@ -363,12 +363,16 @@ static int set_framesize(sensor_t *sensor, framesize_t framesize) 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); + //tuned for 16MHz XCLK and 8MHz PCLK + if (framesize > FRAMESIZE_HVGA) { + //8MHz SYSCLK and 8MHz PCLK (4.44 FPS) + ret = set_pll(sensor, false, 4, 1, 0, false, 2, true, 2); + } else if (framesize >= FRAMESIZE_QVGA) { + //16MHz SYSCLK and 8MHz PCLK (10.25 FPS) + ret = set_pll(sensor, false, 8, 1, 0, false, 2, true, 4); } else { - //25MHz SYSCLK and 10MHz PCLK (15.45 FPS) - ret = set_pll(sensor, false, 5, 1, 0, false, 0, true, 5); + //32MHz SYSCLK and 8MHz PCLK (17.77 FPS) + ret = set_pll(sensor, false, 8, 1, 0, false, 0, true, 8); } } diff --git a/esp32-cam/ov5640.c b/esp32-cam/ov5640.c index e7adcf4..a9ab2a8 100644 --- a/esp32-cam/ov5640.c +++ b/esp32-cam/ov5640.c @@ -196,7 +196,7 @@ static int calc_sysclk(int xclk, bool pll_bypass, int pll_multiplier, int pll_sy 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); + ESP_LOGI(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; } @@ -209,6 +209,7 @@ static int set_pll(sensor_t *sensor, bool bypass, uint8_t multiplier, uint8_t sy if(multiplier > 127){ multiplier &= 0xFE;//only even integers above 127 } + ESP_LOGI(TAG, "Set PLL: bypass: %u, multiplier: %u, sys_div: %u, pre_div: %u, root_2x: %u, pclk_root_div: %u, pclk_manual: %u, pclk_div: %u", bypass, multiplier, sys_div, pre_div, root_2x, pclk_root_div, pclk_manual, pclk_div); calc_sysclk(sensor->xclk_freq_hz, bypass, multiplier, sys_div, pre_div, root_2x, pclk_root_div, pclk_manual, pclk_div); @@ -432,14 +433,22 @@ static int set_framesize(sensor_t *sensor, framesize_t framesize) if (sensor->pixformat == PIXFORMAT_JPEG) { //10MHz PCLK uint8_t sys_mul = 200; - if(framesize < FRAMESIZE_QVGA){ + if(framesize < FRAMESIZE_QVGA || sensor->xclk_freq_hz == 16000000){ sys_mul = 160; } else if(framesize < FRAMESIZE_XGA){ sys_mul = 180; } ret = set_pll(sensor, false, sys_mul, 4, 2, false, 2, true, 4); + //Set PLL: bypass: 0, multiplier: sys_mul, sys_div: 4, pre_div: 2, root_2x: 0, pclk_root_div: 2, pclk_manual: 1, pclk_div: 4 } else { - ret = set_pll(sensor, false, 10, 1, 1, false, 1, true, 4); + //ret = set_pll(sensor, false, 8, 1, 1, false, 1, true, 4); + if (framesize > FRAMESIZE_HVGA) { + ret = set_pll(sensor, false, 10, 1, 2, false, 1, true, 2); + } else if (framesize >= FRAMESIZE_QVGA) { + ret = set_pll(sensor, false, 8, 1, 1, false, 1, true, 4); + } else { + ret = set_pll(sensor, false, 20, 1, 1, false, 1, true, 8); + } } if (ret == 0) { diff --git a/esp32-cam/sccb.c b/esp32-cam/sccb.c index cb615bb..1a2c56e 100644 --- a/esp32-cam/sccb.c +++ b/esp32-cam/sccb.c @@ -11,6 +11,7 @@ #include #include #include "sccb.h" +#include "sensor.h" #include #include "sdkconfig.h" #if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) @@ -36,12 +37,10 @@ const int SCCB_I2C_PORT = 1; #else const int SCCB_I2C_PORT = 0; #endif -static uint8_t ESP_SLAVE_ADDR = 0x3c; int SCCB_Init(int pin_sda, int pin_scl) { - ESP_LOGI(TAG, "pin_sda %d pin_scl %d\n", pin_sda, pin_scl); - //log_i("SCCB_Init start"); + ESP_LOGI(TAG, "pin_sda %d pin_scl %d", pin_sda, pin_scl); i2c_config_t conf; memset(&conf, 0, sizeof(i2c_config_t)); conf.mode = I2C_MODE_MASTER; @@ -56,10 +55,30 @@ int SCCB_Init(int pin_sda, int pin_scl) return 0; } -uint8_t SCCB_Probe() +int SCCB_Deinit(void) +{ + return i2c_driver_delete(SCCB_I2C_PORT); +} + +uint8_t SCCB_Probe(void) { uint8_t slave_addr = 0x0; - while(slave_addr < 0x7f) { + // for (size_t i = 1; i < 0x80; i++) { + // i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + // i2c_master_start(cmd); + // i2c_master_write_byte(cmd, ( i << 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_LOGW(TAG, "Found I2C Device at 0x%02X", i); + // } + // } + for (size_t i = 0; i < CAMERA_MODEL_MAX; i++) { + if (slave_addr == camera_sensor[i].sccb_addr) { + continue; + } + slave_addr = camera_sensor[i].sccb_addr; 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); @@ -67,12 +86,10 @@ uint8_t SCCB_Probe() 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; + return slave_addr; } - slave_addr++; } - return ESP_SLAVE_ADDR; + return 0; } uint8_t SCCB_Read(uint8_t slv_addr, uint8_t reg) diff --git a/esp32-cam/sccb.h b/esp32-cam/sccb.h index 4d5b5b4..ace081a 100644 --- a/esp32-cam/sccb.h +++ b/esp32-cam/sccb.h @@ -10,6 +10,7 @@ #define __SCCB_H__ #include int SCCB_Init(int pin_sda, int pin_scl); +int SCCB_Deinit(void); 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); diff --git a/esp32-cam/sensor.c b/esp32-cam/sensor.c index 2e6d111..15714aa 100644 --- a/esp32-cam/sensor.c +++ b/esp32-cam/sensor.c @@ -1,5 +1,14 @@ #include "sensor.h" +const camera_sensor_info_t camera_sensor[CAMERA_MODEL_MAX] = { + {CAMERA_OV7725, OV7725_SCCB_ADDR, OV7725_PID, FRAMESIZE_VGA}, + {CAMERA_OV2640, OV2640_SCCB_ADDR, OV2640_PID, FRAMESIZE_UXGA}, + {CAMERA_OV3660, OV3660_SCCB_ADDR, OV3660_PID, FRAMESIZE_QXGA}, + {CAMERA_OV5640, OV5640_SCCB_ADDR, OV5640_PID, FRAMESIZE_QSXGA}, + {CAMERA_OV7670, OV7670_SCCB_ADDR, OV7670_PID, FRAMESIZE_VGA}, + {CAMERA_NT99141, NT99141_SCCB_ADDR, NT99141_PID, FRAMESIZE_HD}, +}; + const resolution_info_t resolution[FRAMESIZE_INVALID] = { { 96, 96, ASPECT_RATIO_1X1 }, /* 96x96 */ { 160, 120, ASPECT_RATIO_4X3 }, /* QQVGA */ diff --git a/esp32-cam/sensor.h b/esp32-cam/sensor.h index ad6cd89..ac54731 100644 --- a/esp32-cam/sensor.h +++ b/esp32-cam/sensor.h @@ -11,13 +11,45 @@ #include #include -#define NT99141_PID (0x14) -#define OV9650_PID (0x96) -#define OV7725_PID (0x77) -#define OV2640_PID (0x26) -#define OV3660_PID (0x36) -#define OV5640_PID (0x56) -#define OV7670_PID (0x76) +// Chip ID Registers +#define REG_PID 0x0A +#define REG_VER 0x0B +#define REG_MIDH 0x1C +#define REG_MIDL 0x1D + +#define REG16_CHIDH 0x300A +#define REG16_CHIDL 0x300B + +typedef enum { + OV9650_PID = 0x96, + OV7725_PID = 0x77, + OV2640_PID = 0x26, + OV3660_PID = 0x36, + OV5640_PID = 0x56, + OV7670_PID = 0x76, + NT99141_PID = 0x14 +} camera_pid_t; + +typedef enum { + CAMERA_OV7725, + CAMERA_OV2640, + CAMERA_OV3660, + CAMERA_OV5640, + CAMERA_OV7670, + CAMERA_NT99141, + CAMERA_MODEL_MAX, + CAMERA_NONE, + CAMERA_UNKNOWN +} camera_model_t; + +typedef enum { + OV2640_SCCB_ADDR = 0x30, + OV5640_SCCB_ADDR = 0x3C, + OV3660_SCCB_ADDR = 0x3C, + OV7725_SCCB_ADDR = 0x21, + OV7670_SCCB_ADDR = 0x21, + NT99141_SCCB_ADDR = 0x2A, +} camera_sccb_addr_t; typedef enum { PIXFORMAT_RGB565, // 2BPP/RGB565 @@ -58,6 +90,13 @@ typedef enum { FRAMESIZE_INVALID } framesize_t; +typedef struct { + const camera_model_t model; + const camera_sccb_addr_t sccb_addr; + const camera_pid_t pid; + const framesize_t max_size; +} camera_sensor_info_t; + typedef enum { ASPECT_RATIO_4X3, ASPECT_RATIO_3X2, @@ -101,6 +140,8 @@ typedef struct { // Resolution table (in sensor.c) extern const resolution_info_t resolution[]; +// camera sensor table (in sensor.c) +extern const camera_sensor_info_t camera_sensor[]; typedef struct { uint8_t MIDH; diff --git a/esp32-cam/to_bmp.c b/esp32-cam/to_bmp.c index 85f9c88..5a54bdb 100644 --- a/esp32-cam/to_bmp.c +++ b/esp32-cam/to_bmp.c @@ -24,6 +24,10 @@ #if ESP_IDF_VERSION_MAJOR >= 4 // IDF 4+ #if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 #include "esp32/spiram.h" +#elif CONFIG_IDF_TARGET_ESP32S2 +#include "esp32s2/spiram.h" +#elif CONFIG_IDF_TARGET_ESP32S3 +#include "esp32s3/spiram.h" #else #error Target CONFIG_IDF_TARGET is not supported #endif @@ -115,6 +119,54 @@ static bool _rgb_write(void * arg, uint16_t x, uint16_t y, uint16_t w, uint16_t return true; } +static bool _rgb565_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 jw2 = jpeg->width*2; + size_t t = y * jw; + size_t t2 = y * jw2; + size_t b = t + (h * jw); + size_t l = x * 2; + uint8_t *out = jpeg->output+jpeg->data_offset; + uint8_t *o = out; + size_t iy, iy2, ix, ix2; + + w = w * 3; + + for(iy=t, iy2=t2; iy> 3); + o[ix2+1] = c>>8; + o[ix2] = c&0xff; + } + data+=w; + } + return true; +} + //input buffer static uint32_t _jpg_read(void * arg, size_t index, uint8_t *buf, size_t len) { @@ -140,6 +192,21 @@ static bool jpg2rgb888(const uint8_t *src, size_t src_len, uint8_t * out, jpg_sc return true; } +bool jpg2rgb565(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, _rgb565_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) { diff --git a/esp32-cam/to_jpg.cpp b/esp32-cam/to_jpg.cpp index f8987a8..9b8905a 100644 --- a/esp32-cam/to_jpg.cpp +++ b/esp32-cam/to_jpg.cpp @@ -25,6 +25,10 @@ #if ESP_IDF_VERSION_MAJOR >= 4 // IDF 4+ #if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 #include "esp32/spiram.h" +#elif CONFIG_IDF_TARGET_ESP32S2 +#include "esp32s2/spiram.h" +#elif CONFIG_IDF_TARGET_ESP32S3 +#include "esp32s3/spiram.h" #else #error Target CONFIG_IDF_TARGET is not supported #endif @@ -195,7 +199,7 @@ public: return true; } if ((size_t)len > (max_len - index)) { - ESP_LOGW(TAG, "JPG output overflow: %d bytes", len - (max_len - index)); + //ESP_LOGW(TAG, "JPG output overflow: %d bytes (%d,%d,%d)", len - (max_len - index), len, index, max_len); len = max_len - index; } if (len) { @@ -215,7 +219,7 @@ bool fmt2jpg(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixf { //todo: allocate proper buffer for holding JPEG data //this should be enough for CIF frame size - int jpg_buf_len = 64*1024; + int jpg_buf_len = 128*1024; uint8_t * jpg_buf = (uint8_t *)_malloc(jpg_buf_len); diff --git a/esp32-cam/twi.c b/esp32-cam/twi.c deleted file mode 100644 index 25d71fc..0000000 --- a/esp32-cam/twi.c +++ /dev/null @@ -1,432 +0,0 @@ -/* - 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 @@ -41,11 +45,15 @@ esp_err_t camera_enable_out_clock(camera_config_t* config) ledc_channel_config_t ch_conf; ch_conf.gpio_num = config->pin_xclk; +#if CONFIG_IDF_TARGET_ESP32 ch_conf.speed_mode = LEDC_HIGH_SPEED_MODE; +#else + ch_conf.speed_mode = LEDC_LOW_SPEED_MODE; +#endif 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.duty = 1; ch_conf.hpoint = 0; err = ledc_channel_config(&ch_conf); if (err != ESP_OK) {