From 6fb37b35580242b4bbbf64ff97d34c7242c7fe1a Mon Sep 17 00:00:00 2001 From: Anatoli Arkhipenko Date: Thu, 6 Aug 2020 15:45:29 -0400 Subject: [PATCH] various streaming strategies. psram preferred --- README.md | 32 +- esp32-cam-rtos-allframes/Kconfig | 65 + esp32-cam-rtos-allframes/LICENSE | 202 +++ esp32-cam-rtos-allframes/README.md | 16 + esp32-cam-rtos-allframes/camera.c | 1517 +++++++++++++++++ esp32-cam-rtos-allframes/camera_common.h | 49 + esp32-cam-rtos-allframes/camera_pins.h | 99 ++ .../esp32-cam-rtos-allframes.ino | 459 +++++ .../esp32-camera-master.zip | Bin 0 -> 99319 bytes esp32-cam-rtos-allframes/esp_camera.h | 195 +++ esp32-cam-rtos-allframes/esp_jpg_decode.c | 128 ++ esp32-cam-rtos-allframes/esp_jpg_decode.h | 43 + esp32-cam-rtos-allframes/img_converters.h | 126 ++ esp32-cam-rtos-allframes/jpge.cpp | 723 ++++++++ esp32-cam-rtos-allframes/jpge.h | 142 ++ esp32-cam-rtos-allframes/ov2640.c | 580 +++++++ esp32-cam-rtos-allframes/ov2640.h | 13 + esp32-cam-rtos-allframes/ov2640_regs.h | 216 +++ esp32-cam-rtos-allframes/ov2640_settings.h | 485 ++++++ esp32-cam-rtos-allframes/ov3660.c | 1033 +++++++++++ esp32-cam-rtos-allframes/ov3660.h | 16 + esp32-cam-rtos-allframes/ov3660_regs.h | 211 +++ esp32-cam-rtos-allframes/ov3660_settings.h | 318 ++++ esp32-cam-rtos-allframes/ov5640.c | 1105 ++++++++++++ esp32-cam-rtos-allframes/ov5640.h | 9 + esp32-cam-rtos-allframes/ov5640_regs.h | 213 +++ esp32-cam-rtos-allframes/ov5640_settings.h | 334 ++++ esp32-cam-rtos-allframes/ov7725.c | 557 ++++++ esp32-cam-rtos-allframes/ov7725.h | 14 + esp32-cam-rtos-allframes/ov7725_regs.h | 335 ++++ esp32-cam-rtos-allframes/sccb.c | 260 +++ esp32-cam-rtos-allframes/sccb.h | 18 + esp32-cam-rtos-allframes/sensor.c | 28 + esp32-cam-rtos-allframes/sensor.h | 191 +++ esp32-cam-rtos-allframes/to_bmp.c | 315 ++++ esp32-cam-rtos-allframes/to_jpg.cpp | 241 +++ esp32-cam-rtos-allframes/twi.c | 432 +++++ esp32-cam-rtos-allframes/twi.h | 38 + esp32-cam-rtos-allframes/xclk.c | 61 + esp32-cam-rtos-allframes/xclk.h | 7 + esp32-cam-rtos-allframes/yuv.c | 298 ++++ esp32-cam-rtos-allframes/yuv.h | 29 + esp32-cam-rtos/Kconfig | 65 + esp32-cam-rtos/LICENSE | 202 +++ esp32-cam-rtos/README.md | 16 + esp32-cam-rtos/camera.c | 1517 +++++++++++++++++ esp32-cam-rtos/camera_common.h | 49 + esp32-cam-rtos/camera_pins.h | 99 ++ esp32-cam-rtos/esp32-cam-rtos.ino | 519 ++++++ esp32-cam-rtos/esp32-camera-master.zip | Bin 0 -> 99319 bytes esp32-cam-rtos/esp_camera.h | 195 +++ esp32-cam-rtos/esp_jpg_decode.c | 128 ++ esp32-cam-rtos/esp_jpg_decode.h | 43 + esp32-cam-rtos/img_converters.h | 126 ++ esp32-cam-rtos/jpge.cpp | 723 ++++++++ esp32-cam-rtos/jpge.h | 142 ++ esp32-cam-rtos/ov2640.c | 580 +++++++ esp32-cam-rtos/ov2640.h | 13 + esp32-cam-rtos/ov2640_regs.h | 216 +++ esp32-cam-rtos/ov2640_settings.h | 485 ++++++ esp32-cam-rtos/ov3660.c | 1033 +++++++++++ esp32-cam-rtos/ov3660.h | 16 + esp32-cam-rtos/ov3660_regs.h | 211 +++ esp32-cam-rtos/ov3660_settings.h | 318 ++++ esp32-cam-rtos/ov5640.c | 1105 ++++++++++++ esp32-cam-rtos/ov5640.h | 9 + esp32-cam-rtos/ov5640_regs.h | 213 +++ esp32-cam-rtos/ov5640_settings.h | 334 ++++ esp32-cam-rtos/ov7725.c | 557 ++++++ esp32-cam-rtos/ov7725.h | 14 + esp32-cam-rtos/ov7725_regs.h | 335 ++++ esp32-cam-rtos/sccb.c | 260 +++ esp32-cam-rtos/sccb.h | 18 + esp32-cam-rtos/sensor.c | 28 + esp32-cam-rtos/sensor.h | 191 +++ esp32-cam-rtos/to_bmp.c | 315 ++++ esp32-cam-rtos/to_jpg.cpp | 241 +++ esp32-cam-rtos/twi.c | 432 +++++ esp32-cam-rtos/twi.h | 38 + esp32-cam-rtos/xclk.c | 61 + esp32-cam-rtos/xclk.h | 7 + esp32-cam-rtos/yuv.c | 298 ++++ esp32-cam-rtos/yuv.h | 29 + esp32-cam/camera.c | 10 +- esp32-cam/esp32-cam.ino | 16 +- 85 files changed, 22341 insertions(+), 19 deletions(-) create mode 100644 esp32-cam-rtos-allframes/Kconfig create mode 100644 esp32-cam-rtos-allframes/LICENSE create mode 100644 esp32-cam-rtos-allframes/README.md create mode 100644 esp32-cam-rtos-allframes/camera.c create mode 100644 esp32-cam-rtos-allframes/camera_common.h create mode 100644 esp32-cam-rtos-allframes/camera_pins.h create mode 100644 esp32-cam-rtos-allframes/esp32-cam-rtos-allframes.ino create mode 100644 esp32-cam-rtos-allframes/esp32-camera-master.zip create mode 100644 esp32-cam-rtos-allframes/esp_camera.h create mode 100644 esp32-cam-rtos-allframes/esp_jpg_decode.c create mode 100644 esp32-cam-rtos-allframes/esp_jpg_decode.h create mode 100644 esp32-cam-rtos-allframes/img_converters.h create mode 100644 esp32-cam-rtos-allframes/jpge.cpp create mode 100644 esp32-cam-rtos-allframes/jpge.h create mode 100644 esp32-cam-rtos-allframes/ov2640.c create mode 100644 esp32-cam-rtos-allframes/ov2640.h create mode 100644 esp32-cam-rtos-allframes/ov2640_regs.h create mode 100644 esp32-cam-rtos-allframes/ov2640_settings.h create mode 100644 esp32-cam-rtos-allframes/ov3660.c create mode 100644 esp32-cam-rtos-allframes/ov3660.h create mode 100644 esp32-cam-rtos-allframes/ov3660_regs.h create mode 100644 esp32-cam-rtos-allframes/ov3660_settings.h create mode 100644 esp32-cam-rtos-allframes/ov5640.c create mode 100644 esp32-cam-rtos-allframes/ov5640.h create mode 100644 esp32-cam-rtos-allframes/ov5640_regs.h create mode 100644 esp32-cam-rtos-allframes/ov5640_settings.h create mode 100644 esp32-cam-rtos-allframes/ov7725.c create mode 100644 esp32-cam-rtos-allframes/ov7725.h create mode 100644 esp32-cam-rtos-allframes/ov7725_regs.h create mode 100644 esp32-cam-rtos-allframes/sccb.c create mode 100644 esp32-cam-rtos-allframes/sccb.h create mode 100644 esp32-cam-rtos-allframes/sensor.c create mode 100644 esp32-cam-rtos-allframes/sensor.h create mode 100644 esp32-cam-rtos-allframes/to_bmp.c create mode 100644 esp32-cam-rtos-allframes/to_jpg.cpp create mode 100644 esp32-cam-rtos-allframes/twi.c create mode 100644 esp32-cam-rtos-allframes/twi.h create mode 100644 esp32-cam-rtos-allframes/xclk.c create mode 100644 esp32-cam-rtos-allframes/xclk.h create mode 100644 esp32-cam-rtos-allframes/yuv.c create mode 100644 esp32-cam-rtos-allframes/yuv.h create mode 100644 esp32-cam-rtos/Kconfig create mode 100644 esp32-cam-rtos/LICENSE create mode 100644 esp32-cam-rtos/README.md create mode 100644 esp32-cam-rtos/camera.c create mode 100644 esp32-cam-rtos/camera_common.h create mode 100644 esp32-cam-rtos/camera_pins.h create mode 100644 esp32-cam-rtos/esp32-cam-rtos.ino create mode 100644 esp32-cam-rtos/esp32-camera-master.zip create mode 100644 esp32-cam-rtos/esp_camera.h create mode 100644 esp32-cam-rtos/esp_jpg_decode.c create mode 100644 esp32-cam-rtos/esp_jpg_decode.h create mode 100644 esp32-cam-rtos/img_converters.h create mode 100644 esp32-cam-rtos/jpge.cpp create mode 100644 esp32-cam-rtos/jpge.h create mode 100644 esp32-cam-rtos/ov2640.c create mode 100644 esp32-cam-rtos/ov2640.h create mode 100644 esp32-cam-rtos/ov2640_regs.h create mode 100644 esp32-cam-rtos/ov2640_settings.h create mode 100644 esp32-cam-rtos/ov3660.c create mode 100644 esp32-cam-rtos/ov3660.h create mode 100644 esp32-cam-rtos/ov3660_regs.h create mode 100644 esp32-cam-rtos/ov3660_settings.h create mode 100644 esp32-cam-rtos/ov5640.c create mode 100644 esp32-cam-rtos/ov5640.h create mode 100644 esp32-cam-rtos/ov5640_regs.h create mode 100644 esp32-cam-rtos/ov5640_settings.h create mode 100644 esp32-cam-rtos/ov7725.c create mode 100644 esp32-cam-rtos/ov7725.h create mode 100644 esp32-cam-rtos/ov7725_regs.h create mode 100644 esp32-cam-rtos/sccb.c create mode 100644 esp32-cam-rtos/sccb.h create mode 100644 esp32-cam-rtos/sensor.c create mode 100644 esp32-cam-rtos/sensor.h create mode 100644 esp32-cam-rtos/to_bmp.c create mode 100644 esp32-cam-rtos/to_jpg.cpp create mode 100644 esp32-cam-rtos/twi.c create mode 100644 esp32-cam-rtos/twi.h create mode 100644 esp32-cam-rtos/xclk.c create mode 100644 esp32-cam-rtos/xclk.h create mode 100644 esp32-cam-rtos/yuv.c create mode 100644 esp32-cam-rtos/yuv.h diff --git a/README.md b/README.md index de25052..471e2c0 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,35 @@ ## With latest ESP-CAM drivers Full story: https://www.hackster.io/anatoli-arkhipenko/multi-client-mjpeg-streaming-from-esp32-47768f +### Contents: + +#### esp32-cam folder: + +MJPEG Multiclient Streaming Server using RTOS queue to serve video to clients. + +The problem with this approach is that the slowest connected client slows it down for everyone. + +#### esp32-cam-rtos folder: + +MJPEG Multiclient Streaming Server using dedicated RTOS tasks to serve video to clients. + +This solves the problem of slowest client as every client is served independently based on their bandwidth. Slow clients are just not getting all the frames. + +#### esp32-cam-allframes folder: + +MJPEG Multiclient Streaming Server using dedicated RTOS tasks to serve video to clients. + +All captured frames are stored in PSRAM (until you run out of memory) and served to individual clients one after another, so every client is guaranteed to get all frames in order, at their own pace (good for recording without frame drops) + + + ### Procedure: 1. Download latest ZIP file from https://github.com/espressif/esp32-camera.git into the esp32-cam subfolder -2. unzip using `unzip -j esp32-cam-master.zip` command. This will place all files in the same folder +2. Delete `examples` folder from the archive + +3. unzip using `unzip -j esp32-cam-master.zip` command. This will place all files in the same folder @@ -38,14 +62,14 @@ Compile the **esp32-cam.ino** sketch using the following settings: - Flash Freq: 80 - Flash mode: QIO - Flash Size: 4Mb -- Partition: Minimal SPIFFS (or any other that would fit the sketch) -- PSRAM: Enabled +- Partition: Default, Minimal SPIFFS (or any other that would fit the sketch) +- PSRAM: **Enabled** ### Results: -I was able to run multiple browser windows, multiple VLC windows and connect multiple Blynk video widgets (max: 10) to ESP-EYE chip. The delay on the browser window was almost unnoticeable. In VLC you notice a 1 second delay probably due to buffering. Blynk performance all depends on the phone, so no comments there. +I was able to run multiple browser windows, multiple VLC windows and connect multiple Blynk video widgets (max: 10) to ESP-EYE chip. The delay on the browser window was almost unnoticeable. In VLC you notice a 1 second delay due to buffering. Blynk performance all depends on the phone, so no comments there. This is incredible considering the size of this thing! The camera on ESP-EYE is actually quite good. diff --git a/esp32-cam-rtos-allframes/Kconfig b/esp32-cam-rtos-allframes/Kconfig new file mode 100644 index 0000000..78fc607 --- /dev/null +++ b/esp32-cam-rtos-allframes/Kconfig @@ -0,0 +1,65 @@ +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 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-rtos-allframes/LICENSE b/esp32-cam-rtos-allframes/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/esp32-cam-rtos-allframes/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-rtos-allframes/README.md b/esp32-cam-rtos-allframes/README.md new file mode 100644 index 0000000..99a1f88 --- /dev/null +++ b/esp32-cam-rtos-allframes/README.md @@ -0,0 +1,16 @@ +# ESP32 MJPEG Multiclient Streaming All Frames Server + +This is a MJPEG streaming webserver implemented for AI-Thinker ESP32-CAM or ESP-EYE modules. + +This is tested to work with **VLC** and **Blynk** video widget. + + + +**This version uses FreeRTOS tasks to enable streaming to up to 10 connected clients and is buffering and sending every frame to every client** + + + +Inspired by and based on this Instructable: [$9 RTSP Video Streamer Using the ESP32-CAM Board](https://www.instructables.com/id/9-RTSP-Video-Streamer-Using-the-ESP32-CAM-Board/) + + + diff --git a/esp32-cam-rtos-allframes/camera.c b/esp32-cam-rtos-allframes/camera.c new file mode 100644 index 0000000..0fc85f6 --- /dev/null +++ b/esp32-cam-rtos-allframes/camera.c @@ -0,0 +1,1517 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include +#include +#include +#include "time.h" +#include "sys/time.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "soc/soc.h" +#include "soc/gpio_sig_map.h" +#include "soc/i2s_reg.h" +#include "soc/i2s_struct.h" +#include "soc/io_mux_reg.h" +#include "driver/gpio.h" +#include "driver/rtc_io.h" +#include "driver/periph_ctrl.h" +#include "esp_intr_alloc.h" +#include "esp_system.h" +#include "nvs_flash.h" +#include "nvs.h" +#include "sensor.h" +#include "sccb.h" +#include "esp_camera.h" +#include "camera_common.h" +#include "xclk.h" +#if CONFIG_OV2640_SUPPORT +#include "ov2640.h" +#endif +#if CONFIG_OV7725_SUPPORT +#include "ov7725.h" +#endif +#if CONFIG_OV3660_SUPPORT +#include "ov3660.h" +#endif +#if CONFIG_OV5640_SUPPORT +#include "ov5640.h" +#endif + +typedef enum { + CAMERA_NONE = 0, + CAMERA_UNKNOWN = 1, + CAMERA_OV7725 = 7725, + CAMERA_OV2640 = 2640, + CAMERA_OV3660 = 3660, + CAMERA_OV5640 = 5640, +} camera_model_t; + +#define REG_PID 0x0A +#define REG_VER 0x0B +#define REG_MIDH 0x1C +#define REG_MIDL 0x1D + +#define REG16_CHIDH 0x300A +#define REG16_CHIDL 0x300B + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define TAG "" +#else +#include "esp_log.h" +static const char* TAG = "camera"; +#endif +static const char* CAMERA_SENSOR_NVS_KEY = "sensor"; +static const char* CAMERA_PIXFORMAT_NVS_KEY = "pixformat"; + +typedef void (*dma_filter_t)(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst); + +typedef struct camera_fb_s { + uint8_t * buf; + size_t len; + size_t width; + size_t height; + pixformat_t format; + struct timeval timestamp; + size_t size; + uint8_t ref; + uint8_t bad; + struct camera_fb_s * next; +} camera_fb_int_t; + +typedef struct fb_s { + uint8_t * buf; + size_t len; + struct fb_s * next; +} fb_item_t; + +typedef struct { + camera_config_t config; + sensor_t sensor; + + camera_fb_int_t *fb; + size_t fb_size; + size_t data_size; + + size_t width; + size_t height; + size_t in_bytes_per_pixel; + size_t fb_bytes_per_pixel; + + size_t dma_received_count; + size_t dma_filtered_count; + size_t dma_per_line; + size_t dma_buf_width; + size_t dma_sample_count; + + lldesc_t *dma_desc; + dma_elem_t **dma_buf; + size_t dma_desc_count; + size_t dma_desc_cur; + + i2s_sampling_mode_t sampling_mode; + dma_filter_t dma_filter; + intr_handle_t i2s_intr_handle; + QueueHandle_t data_ready; + QueueHandle_t fb_in; + QueueHandle_t fb_out; + + SemaphoreHandle_t frame_ready; + TaskHandle_t dma_filter_task; +} camera_state_t; + +camera_state_t* s_state = NULL; + +static void i2s_init(); +static int i2s_run(); +static void IRAM_ATTR vsync_isr(void* arg); +static void IRAM_ATTR i2s_isr(void* arg); +static esp_err_t dma_desc_init(); +static void dma_desc_deinit(); +static void dma_filter_task(void *pvParameters); +static void dma_filter_grayscale(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst); +static void dma_filter_grayscale_highspeed(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst); +static void dma_filter_yuyv(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst); +static void dma_filter_yuyv_highspeed(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst); +static void dma_filter_jpeg(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst); +static void i2s_stop(bool* need_yield); + +static bool is_hs_mode() +{ + return s_state->config.xclk_freq_hz > 10000000; +} + +static size_t i2s_bytes_per_sample(i2s_sampling_mode_t mode) +{ + switch(mode) { + case SM_0A00_0B00: + return 4; + case SM_0A0B_0B0C: + return 4; + case SM_0A0B_0C0D: + return 2; + default: + assert(0 && "invalid sampling mode"); + return 0; + } +} + +static int IRAM_ATTR _gpio_get_level(gpio_num_t gpio_num) +{ + if (gpio_num < 32) { + return (GPIO.in >> gpio_num) & 0x1; + } else { + return (GPIO.in1.data >> (gpio_num - 32)) & 0x1; + } +} + +static void IRAM_ATTR vsync_intr_disable() +{ + gpio_set_intr_type(s_state->config.pin_vsync, GPIO_INTR_DISABLE); +} + +static void vsync_intr_enable() +{ + gpio_set_intr_type(s_state->config.pin_vsync, GPIO_INTR_NEGEDGE); +} + +static int skip_frame() +{ + if (s_state == NULL) { + return -1; + } + int64_t st_t = esp_timer_get_time(); + while (_gpio_get_level(s_state->config.pin_vsync) == 0) { + if((esp_timer_get_time() - st_t) > 1000000LL){ + goto timeout; + } + } + while (_gpio_get_level(s_state->config.pin_vsync) != 0) { + if((esp_timer_get_time() - st_t) > 1000000LL){ + goto timeout; + } + } + while (_gpio_get_level(s_state->config.pin_vsync) == 0) { + if((esp_timer_get_time() - st_t) > 1000000LL){ + goto timeout; + } + } + return 0; + +timeout: + ESP_LOGE(TAG, "Timeout waiting for VSYNC"); + return -1; +} + +static void camera_fb_deinit() +{ + camera_fb_int_t * _fb1 = s_state->fb, * _fb2 = NULL; + while(s_state->fb) { + _fb2 = s_state->fb; + s_state->fb = _fb2->next; + if(_fb2->next == _fb1) { + s_state->fb = NULL; + } + free(_fb2->buf); + free(_fb2); + } +} + +static esp_err_t camera_fb_init(size_t count) +{ + if(!count) { + return ESP_ERR_INVALID_ARG; + } + + camera_fb_deinit(); + + ESP_LOGI(TAG, "Allocating %u frame buffers (%d KB total)", count, (s_state->fb_size * count) / 1024); + + camera_fb_int_t * _fb = NULL, * _fb1 = NULL, * _fb2 = NULL; + for(size_t i = 0; i < count; i++) { + _fb2 = (camera_fb_int_t *)malloc(sizeof(camera_fb_int_t)); + if(!_fb2) { + goto fail; + } + memset(_fb2, 0, sizeof(camera_fb_int_t)); + _fb2->size = s_state->fb_size; +// _fb2->buf = (uint8_t*) calloc(_fb2->size, 1); +// if(!_fb2->buf) { + ESP_LOGI(TAG, "Allocating %d KB frame buffer in PSRAM", s_state->fb_size/1024); + _fb2->buf = (uint8_t*) heap_caps_calloc(_fb2->size, 1, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); +// } else { +// ESP_LOGI(TAG, "Allocating %d KB frame buffer in OnBoard RAM", s_state->fb_size/1024); +// } + if(!_fb2->buf) { + free(_fb2); + ESP_LOGE(TAG, "Allocating %d KB frame buffer Failed", s_state->fb_size/1024); + goto fail; + } + memset(_fb2->buf, 0, _fb2->size); + _fb2->next = _fb; + _fb = _fb2; + if(!i) { + _fb1 = _fb2; + } + } + if(_fb1) { + _fb1->next = _fb; + } + + s_state->fb = _fb;//load first buffer + + return ESP_OK; + +fail: + while(_fb) { + _fb2 = _fb; + _fb = _fb->next; + free(_fb2->buf); + free(_fb2); + } + return ESP_ERR_NO_MEM; +} + +static esp_err_t dma_desc_init() +{ + assert(s_state->width % 4 == 0); + size_t line_size = s_state->width * s_state->in_bytes_per_pixel * + i2s_bytes_per_sample(s_state->sampling_mode); + ESP_LOGD(TAG, "Line width (for DMA): %d bytes", line_size); + size_t dma_per_line = 1; + size_t buf_size = line_size; + while (buf_size >= 4096) { + buf_size /= 2; + dma_per_line *= 2; + } + size_t dma_desc_count = dma_per_line * 4; + s_state->dma_buf_width = line_size; + s_state->dma_per_line = dma_per_line; + s_state->dma_desc_count = dma_desc_count; + ESP_LOGD(TAG, "DMA buffer size: %d, DMA buffers per line: %d", buf_size, dma_per_line); + ESP_LOGD(TAG, "DMA buffer count: %d", dma_desc_count); + ESP_LOGD(TAG, "DMA buffer total: %d bytes", buf_size * dma_desc_count); + + s_state->dma_buf = (dma_elem_t**) malloc(sizeof(dma_elem_t*) * dma_desc_count); + if (s_state->dma_buf == NULL) { + return ESP_ERR_NO_MEM; + } + s_state->dma_desc = (lldesc_t*) malloc(sizeof(lldesc_t) * dma_desc_count); + if (s_state->dma_desc == NULL) { + return ESP_ERR_NO_MEM; + } + size_t dma_sample_count = 0; + for (int i = 0; i < dma_desc_count; ++i) { + ESP_LOGD(TAG, "Allocating DMA buffer #%d, size=%d", i, buf_size); + dma_elem_t* buf = (dma_elem_t*) malloc(buf_size); + if (buf == NULL) { + return ESP_ERR_NO_MEM; + } + s_state->dma_buf[i] = buf; + ESP_LOGV(TAG, "dma_buf[%d]=%p", i, buf); + + lldesc_t* pd = &s_state->dma_desc[i]; + pd->length = buf_size; + if (s_state->sampling_mode == SM_0A0B_0B0C && + (i + 1) % dma_per_line == 0) { + pd->length -= 4; + } + dma_sample_count += pd->length / 4; + pd->size = pd->length; + pd->owner = 1; + pd->sosf = 1; + pd->buf = (uint8_t*) buf; + pd->offset = 0; + pd->empty = 0; + pd->eof = 1; + pd->qe.stqe_next = &s_state->dma_desc[(i + 1) % dma_desc_count]; + } + s_state->dma_sample_count = dma_sample_count; + return ESP_OK; +} + +static void dma_desc_deinit() +{ + if (s_state->dma_buf) { + for (int i = 0; i < s_state->dma_desc_count; ++i) { + free(s_state->dma_buf[i]); + } + } + free(s_state->dma_buf); + free(s_state->dma_desc); +} + +static inline void IRAM_ATTR i2s_conf_reset() +{ + const uint32_t lc_conf_reset_flags = I2S_IN_RST_M | I2S_AHBM_RST_M + | I2S_AHBM_FIFO_RST_M; + I2S0.lc_conf.val |= lc_conf_reset_flags; + I2S0.lc_conf.val &= ~lc_conf_reset_flags; + + const uint32_t conf_reset_flags = I2S_RX_RESET_M | I2S_RX_FIFO_RESET_M + | I2S_TX_RESET_M | I2S_TX_FIFO_RESET_M; + I2S0.conf.val |= conf_reset_flags; + I2S0.conf.val &= ~conf_reset_flags; + while (I2S0.state.rx_fifo_reset_back) { + ; + } +} + +static void i2s_init() +{ + camera_config_t* config = &s_state->config; + + // Configure input GPIOs + gpio_num_t pins[] = { + config->pin_d7, + config->pin_d6, + config->pin_d5, + config->pin_d4, + config->pin_d3, + config->pin_d2, + config->pin_d1, + config->pin_d0, + config->pin_vsync, + config->pin_href, + config->pin_pclk + }; + gpio_config_t conf = { + .mode = GPIO_MODE_INPUT, + .pull_up_en = GPIO_PULLUP_ENABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE + }; + for (int i = 0; i < sizeof(pins) / sizeof(gpio_num_t); ++i) { + if (rtc_gpio_is_valid_gpio(pins[i])) { + rtc_gpio_deinit(pins[i]); + } + conf.pin_bit_mask = 1LL << pins[i]; + gpio_config(&conf); + } + + // Route input GPIOs to I2S peripheral using GPIO matrix + gpio_matrix_in(config->pin_d0, I2S0I_DATA_IN0_IDX, false); + gpio_matrix_in(config->pin_d1, I2S0I_DATA_IN1_IDX, false); + gpio_matrix_in(config->pin_d2, I2S0I_DATA_IN2_IDX, false); + gpio_matrix_in(config->pin_d3, I2S0I_DATA_IN3_IDX, false); + gpio_matrix_in(config->pin_d4, I2S0I_DATA_IN4_IDX, false); + gpio_matrix_in(config->pin_d5, I2S0I_DATA_IN5_IDX, false); + gpio_matrix_in(config->pin_d6, I2S0I_DATA_IN6_IDX, false); + gpio_matrix_in(config->pin_d7, I2S0I_DATA_IN7_IDX, false); + gpio_matrix_in(config->pin_vsync, I2S0I_V_SYNC_IDX, false); + gpio_matrix_in(0x38, I2S0I_H_SYNC_IDX, false); + gpio_matrix_in(config->pin_href, I2S0I_H_ENABLE_IDX, false); + gpio_matrix_in(config->pin_pclk, I2S0I_WS_IN_IDX, false); + + // Enable and configure I2S peripheral + periph_module_enable(PERIPH_I2S0_MODULE); + // Toggle some reset bits in LC_CONF register + // Toggle some reset bits in CONF register + i2s_conf_reset(); + // Enable slave mode (sampling clock is external) + I2S0.conf.rx_slave_mod = 1; + // Enable parallel mode + I2S0.conf2.lcd_en = 1; + // Use HSYNC/VSYNC/HREF to control sampling + I2S0.conf2.camera_en = 1; + // Configure clock divider + I2S0.clkm_conf.clkm_div_a = 1; + I2S0.clkm_conf.clkm_div_b = 0; + I2S0.clkm_conf.clkm_div_num = 2; + // FIFO will sink data to DMA + I2S0.fifo_conf.dscr_en = 1; + // FIFO configuration + I2S0.fifo_conf.rx_fifo_mod = s_state->sampling_mode; + I2S0.fifo_conf.rx_fifo_mod_force_en = 1; + I2S0.conf_chan.rx_chan_mod = 1; + // Clear flags which are used in I2S serial mode + I2S0.sample_rate_conf.rx_bits_mod = 0; + I2S0.conf.rx_right_first = 0; + I2S0.conf.rx_msb_right = 0; + I2S0.conf.rx_msb_shift = 0; + I2S0.conf.rx_mono = 0; + I2S0.conf.rx_short_sync = 0; + I2S0.timing.val = 0; + I2S0.timing.rx_dsync_sw = 1; + + // Allocate I2S interrupt, keep it disabled + ESP_ERROR_CHECK(esp_intr_alloc(ETS_I2S0_INTR_SOURCE, + ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM, + &i2s_isr, NULL, &s_state->i2s_intr_handle)); +} + +static void IRAM_ATTR i2s_start_bus() +{ + s_state->dma_desc_cur = 0; + s_state->dma_received_count = 0; + //s_state->dma_filtered_count = 0; + esp_intr_disable(s_state->i2s_intr_handle); + i2s_conf_reset(); + + I2S0.rx_eof_num = s_state->dma_sample_count; + I2S0.in_link.addr = (uint32_t) &s_state->dma_desc[0]; + I2S0.in_link.start = 1; + I2S0.int_clr.val = I2S0.int_raw.val; + I2S0.int_ena.val = 0; + I2S0.int_ena.in_done = 1; + + esp_intr_enable(s_state->i2s_intr_handle); + I2S0.conf.rx_start = 1; + if (s_state->config.pixel_format == PIXFORMAT_JPEG) { + vsync_intr_enable(); + } +} + +static int i2s_run() +{ + for (int i = 0; i < s_state->dma_desc_count; ++i) { + lldesc_t* d = &s_state->dma_desc[i]; + ESP_LOGV(TAG, "DMA desc %2d: %u %u %u %u %u %u %p %p", + i, d->length, d->size, d->offset, d->eof, d->sosf, d->owner, d->buf, d->qe.stqe_next); + memset(s_state->dma_buf[i], 0, d->length); + } + + // wait for frame + camera_fb_int_t * fb = s_state->fb; + while(s_state->config.fb_count > 1) { + while(s_state->fb->ref && s_state->fb->next != fb) { + s_state->fb = s_state->fb->next; + } + if(s_state->fb->ref == 0) { + break; + } + vTaskDelay(2); + } + + //todo: wait for vsync + ESP_LOGV(TAG, "Waiting for negative edge on VSYNC"); + + int64_t st_t = esp_timer_get_time(); + while (_gpio_get_level(s_state->config.pin_vsync) != 0) { + if((esp_timer_get_time() - st_t) > 1000000LL){ + ESP_LOGE(TAG, "Timeout waiting for VSYNC"); + return -1; + } + } + ESP_LOGV(TAG, "Got VSYNC"); + i2s_start_bus(); + return 0; +} + +static void IRAM_ATTR i2s_stop_bus() +{ + esp_intr_disable(s_state->i2s_intr_handle); + vsync_intr_disable(); + i2s_conf_reset(); + I2S0.conf.rx_start = 0; +} + +static void IRAM_ATTR i2s_stop(bool* need_yield) +{ + if(s_state->config.fb_count == 1 && !s_state->fb->bad) { + i2s_stop_bus(); + } else { + s_state->dma_received_count = 0; + } + + size_t val = SIZE_MAX; + BaseType_t higher_priority_task_woken; + BaseType_t ret = xQueueSendFromISR(s_state->data_ready, &val, &higher_priority_task_woken); + if(need_yield && !*need_yield) { + *need_yield = (ret == pdTRUE && higher_priority_task_woken == pdTRUE); + } +} + +static void IRAM_ATTR signal_dma_buf_received(bool* need_yield) +{ + size_t dma_desc_filled = s_state->dma_desc_cur; + s_state->dma_desc_cur = (dma_desc_filled + 1) % s_state->dma_desc_count; + s_state->dma_received_count++; + if(!s_state->fb->ref && s_state->fb->bad){ + *need_yield = false; + return; + } + BaseType_t higher_priority_task_woken; + BaseType_t ret = xQueueSendFromISR(s_state->data_ready, &dma_desc_filled, &higher_priority_task_woken); + if (ret != pdTRUE) { + if(!s_state->fb->ref) { + s_state->fb->bad = 1; + } + //ESP_EARLY_LOGW(TAG, "qsf:%d", s_state->dma_received_count); + //ets_printf("qsf:%d\n", s_state->dma_received_count); + //ets_printf("qovf\n"); + } + *need_yield = (ret == pdTRUE && higher_priority_task_woken == pdTRUE); +} + +static void IRAM_ATTR i2s_isr(void* arg) +{ + I2S0.int_clr.val = I2S0.int_raw.val; + bool need_yield = false; + signal_dma_buf_received(&need_yield); + if (s_state->config.pixel_format != PIXFORMAT_JPEG + && s_state->dma_received_count == s_state->height * s_state->dma_per_line) { + i2s_stop(&need_yield); + } + if (need_yield) { + portYIELD_FROM_ISR(); + } +} + +static void IRAM_ATTR vsync_isr(void* arg) +{ + GPIO.status1_w1tc.val = GPIO.status1.val; + GPIO.status_w1tc = GPIO.status; + bool need_yield = false; + //if vsync is low and we have received some data, frame is done + if (_gpio_get_level(s_state->config.pin_vsync) == 0) { + if(s_state->dma_received_count > 0) { + signal_dma_buf_received(&need_yield); + //ets_printf("end_vsync\n"); + if(s_state->dma_filtered_count > 1 || s_state->fb->bad || s_state->config.fb_count > 1) { + i2s_stop(&need_yield); + } + //ets_printf("vs\n"); + } + if(s_state->config.fb_count > 1 || s_state->dma_filtered_count < 2) { + I2S0.conf.rx_start = 0; + I2S0.in_link.start = 0; + I2S0.int_clr.val = I2S0.int_raw.val; + i2s_conf_reset(); + s_state->dma_desc_cur = (s_state->dma_desc_cur + 1) % s_state->dma_desc_count; + //I2S0.rx_eof_num = s_state->dma_sample_count; + I2S0.in_link.addr = (uint32_t) &s_state->dma_desc[s_state->dma_desc_cur]; + I2S0.in_link.start = 1; + I2S0.conf.rx_start = 1; + s_state->dma_received_count = 0; + } + } + if (need_yield) { + portYIELD_FROM_ISR(); + } +} + +static void IRAM_ATTR camera_fb_done() +{ + camera_fb_int_t * fb = NULL, * fb2 = NULL; + BaseType_t taskAwoken = 0; + + if(s_state->config.fb_count == 1) { + xSemaphoreGive(s_state->frame_ready); + return; + } + + fb = s_state->fb; + if(!fb->ref && fb->len) { + //add reference + fb->ref = 1; + + //check if the queue is full + if(xQueueIsQueueFullFromISR(s_state->fb_out) == pdTRUE) { + //pop frame buffer from the queue + if(xQueueReceiveFromISR(s_state->fb_out, &fb2, &taskAwoken) == pdTRUE) { + //free the popped buffer + fb2->ref = 0; + fb2->len = 0; + //push the new frame to the end of the queue + xQueueSendFromISR(s_state->fb_out, &fb, &taskAwoken); + } else { + //queue is full and we could not pop a frame from it + } + } else { + //push the new frame to the end of the queue + xQueueSendFromISR(s_state->fb_out, &fb, &taskAwoken); + } + } else { + //frame was referenced or empty + } + + //return buffers to be filled + while(xQueueReceiveFromISR(s_state->fb_in, &fb2, &taskAwoken) == pdTRUE) { + fb2->ref = 0; + fb2->len = 0; + } + + //advance frame buffer only if the current one has data + if(s_state->fb->len) { + s_state->fb = s_state->fb->next; + } + //try to find the next free frame buffer + while(s_state->fb->ref && s_state->fb->next != fb) { + s_state->fb = s_state->fb->next; + } + //is the found frame buffer free? + if(!s_state->fb->ref) { + //buffer found. make sure it's empty + s_state->fb->len = 0; + *((uint32_t *)s_state->fb->buf) = 0; + } else { + //stay at the previous buffer + s_state->fb = fb; + } +} + +static void IRAM_ATTR dma_finish_frame() +{ + size_t buf_len = s_state->width * s_state->fb_bytes_per_pixel / s_state->dma_per_line; + + if(!s_state->fb->ref) { + // is the frame bad? + if(s_state->fb->bad){ + s_state->fb->bad = 0; + s_state->fb->len = 0; + *((uint32_t *)s_state->fb->buf) = 0; + if(s_state->config.fb_count == 1) { + i2s_start_bus(); + } + //ets_printf("bad\n"); + } else { + s_state->fb->len = s_state->dma_filtered_count * buf_len; + if(s_state->fb->len) { + //find the end marker for JPEG. Data after that can be discarded + if(s_state->fb->format == PIXFORMAT_JPEG){ + uint8_t * dptr = &s_state->fb->buf[s_state->fb->len - 1]; + while(dptr > s_state->fb->buf){ + if(dptr[0] == 0xFF && dptr[1] == 0xD9 && dptr[2] == 0x00 && dptr[3] == 0x00){ + dptr += 2; + s_state->fb->len = dptr - s_state->fb->buf; + if((s_state->fb->len & 0x1FF) == 0){ + s_state->fb->len += 1; + } + if((s_state->fb->len % 100) == 0){ + s_state->fb->len += 1; + } + break; + } + dptr--; + } + } + //send out the frame + camera_fb_done(); + } else if(s_state->config.fb_count == 1){ + //frame was empty? + i2s_start_bus(); + } else { + //ets_printf("empty\n"); + } + } + } else if(s_state->fb->len) { + camera_fb_done(); + } + s_state->dma_filtered_count = 0; +} + +static void IRAM_ATTR dma_filter_buffer(size_t buf_idx) +{ + //no need to process the data if frame is in use or is bad + if(s_state->fb->ref || s_state->fb->bad) { + return; + } + + //check if there is enough space in the frame buffer for the new data + size_t buf_len = s_state->width * s_state->fb_bytes_per_pixel / s_state->dma_per_line; + size_t fb_pos = s_state->dma_filtered_count * buf_len; + if(fb_pos > s_state->fb_size - buf_len) { + //size_t processed = s_state->dma_received_count * buf_len; + //ets_printf("[%s:%u] ovf pos: %u, processed: %u\n", __FUNCTION__, __LINE__, fb_pos, processed); + return; + } + + //convert I2S DMA buffer to pixel data + (*s_state->dma_filter)(s_state->dma_buf[buf_idx], &s_state->dma_desc[buf_idx], s_state->fb->buf + fb_pos); + + //first frame buffer + if(!s_state->dma_filtered_count) { + //check for correct JPEG header + if(s_state->sensor.pixformat == PIXFORMAT_JPEG) { + uint32_t sig = *((uint32_t *)s_state->fb->buf) & 0xFFFFFF; + if(sig != 0xffd8ff) { + ets_printf("bh 0x%08x\n", sig); + s_state->fb->bad = 1; + return; + } + } + //set the frame properties + s_state->fb->width = resolution[s_state->sensor.status.framesize].width; + s_state->fb->height = resolution[s_state->sensor.status.framesize].height; + s_state->fb->format = s_state->sensor.pixformat; + + uint64_t us = (uint64_t)esp_timer_get_time(); + s_state->fb->timestamp.tv_sec = us / 1000000UL; + s_state->fb->timestamp.tv_usec = us % 1000000UL; + } + s_state->dma_filtered_count++; +} + +static void IRAM_ATTR dma_filter_task(void *pvParameters) +{ + s_state->dma_filtered_count = 0; + while (true) { + size_t buf_idx; + if(xQueueReceive(s_state->data_ready, &buf_idx, portMAX_DELAY) == pdTRUE) { + if (buf_idx == SIZE_MAX) { + //this is the end of the frame + dma_finish_frame(); + } else { + dma_filter_buffer(buf_idx); + } + } + } +} + +static void IRAM_ATTR dma_filter_jpeg(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst) +{ + size_t end = dma_desc->length / sizeof(dma_elem_t) / 4; + // manually unrolling 4 iterations of the loop here + for (size_t i = 0; i < end; ++i) { + dst[0] = src[0].sample1; + dst[1] = src[1].sample1; + dst[2] = src[2].sample1; + dst[3] = src[3].sample1; + src += 4; + dst += 4; + } +} + +static void IRAM_ATTR dma_filter_grayscale(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst) +{ + size_t end = dma_desc->length / sizeof(dma_elem_t) / 4; + for (size_t i = 0; i < end; ++i) { + // manually unrolling 4 iterations of the loop here + dst[0] = src[0].sample1; + dst[1] = src[1].sample1; + dst[2] = src[2].sample1; + dst[3] = src[3].sample1; + src += 4; + dst += 4; + } +} + +static void IRAM_ATTR dma_filter_grayscale_highspeed(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst) +{ + size_t end = dma_desc->length / sizeof(dma_elem_t) / 8; + for (size_t i = 0; i < end; ++i) { + // manually unrolling 4 iterations of the loop here + dst[0] = src[0].sample1; + dst[1] = src[2].sample1; + dst[2] = src[4].sample1; + dst[3] = src[6].sample1; + src += 8; + dst += 4; + } + // the final sample of a line in SM_0A0B_0B0C sampling mode needs special handling + if ((dma_desc->length & 0x7) != 0) { + dst[0] = src[0].sample1; + dst[1] = src[2].sample1; + } +} + +static void IRAM_ATTR dma_filter_yuyv(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst) +{ + size_t end = dma_desc->length / sizeof(dma_elem_t) / 4; + for (size_t i = 0; i < end; ++i) { + dst[0] = src[0].sample1;//y0 + dst[1] = src[0].sample2;//u + dst[2] = src[1].sample1;//y1 + dst[3] = src[1].sample2;//v + + dst[4] = src[2].sample1;//y0 + dst[5] = src[2].sample2;//u + dst[6] = src[3].sample1;//y1 + dst[7] = src[3].sample2;//v + src += 4; + dst += 8; + } +} + +static void IRAM_ATTR dma_filter_yuyv_highspeed(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst) +{ + size_t end = dma_desc->length / sizeof(dma_elem_t) / 8; + for (size_t i = 0; i < end; ++i) { + dst[0] = src[0].sample1;//y0 + dst[1] = src[1].sample1;//u + dst[2] = src[2].sample1;//y1 + dst[3] = src[3].sample1;//v + + dst[4] = src[4].sample1;//y0 + dst[5] = src[5].sample1;//u + dst[6] = src[6].sample1;//y1 + dst[7] = src[7].sample1;//v + src += 8; + dst += 8; + } + if ((dma_desc->length & 0x7) != 0) { + dst[0] = src[0].sample1;//y0 + dst[1] = src[1].sample1;//u + dst[2] = src[2].sample1;//y1 + dst[3] = src[2].sample2;//v + } +} + +static void IRAM_ATTR dma_filter_rgb888(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst) +{ + size_t end = dma_desc->length / sizeof(dma_elem_t) / 4; + uint8_t lb, hb; + for (size_t i = 0; i < end; ++i) { + hb = src[0].sample1; + lb = src[0].sample2; + dst[0] = (lb & 0x1F) << 3; + dst[1] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3; + dst[2] = hb & 0xF8; + + hb = src[1].sample1; + lb = src[1].sample2; + dst[3] = (lb & 0x1F) << 3; + dst[4] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3; + dst[5] = hb & 0xF8; + + hb = src[2].sample1; + lb = src[2].sample2; + dst[6] = (lb & 0x1F) << 3; + dst[7] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3; + dst[8] = hb & 0xF8; + + hb = src[3].sample1; + lb = src[3].sample2; + dst[9] = (lb & 0x1F) << 3; + dst[10] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3; + dst[11] = hb & 0xF8; + src += 4; + dst += 12; + } +} + +static void IRAM_ATTR dma_filter_rgb888_highspeed(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst) +{ + size_t end = dma_desc->length / sizeof(dma_elem_t) / 8; + uint8_t lb, hb; + for (size_t i = 0; i < end; ++i) { + hb = src[0].sample1; + lb = src[1].sample1; + dst[0] = (lb & 0x1F) << 3; + dst[1] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3; + dst[2] = hb & 0xF8; + + hb = src[2].sample1; + lb = src[3].sample1; + dst[3] = (lb & 0x1F) << 3; + dst[4] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3; + dst[5] = hb & 0xF8; + + hb = src[4].sample1; + lb = src[5].sample1; + dst[6] = (lb & 0x1F) << 3; + dst[7] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3; + dst[8] = hb & 0xF8; + + hb = src[6].sample1; + lb = src[7].sample1; + dst[9] = (lb & 0x1F) << 3; + dst[10] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3; + dst[11] = hb & 0xF8; + + src += 8; + dst += 12; + } + if ((dma_desc->length & 0x7) != 0) { + hb = src[0].sample1; + lb = src[1].sample1; + dst[0] = (lb & 0x1F) << 3; + dst[1] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3; + dst[2] = hb & 0xF8; + + hb = src[2].sample1; + lb = src[2].sample2; + dst[3] = (lb & 0x1F) << 3; + dst[4] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3; + dst[5] = hb & 0xF8; + } +} + +/* + * Public Methods + * */ + +esp_err_t camera_probe(const camera_config_t* config, camera_model_t* out_camera_model) +{ + if (s_state != NULL) { + return ESP_ERR_INVALID_STATE; + } + + s_state = (camera_state_t*) calloc(sizeof(*s_state), 1); + if (!s_state) { + return ESP_ERR_NO_MEM; + } + + ESP_LOGD(TAG, "Enabling XCLK output"); + camera_enable_out_clock(config); + + ESP_LOGD(TAG, "Initializing SSCB"); + SCCB_Init(config->pin_sscb_sda, config->pin_sscb_scl); + + if(config->pin_pwdn >= 0) { + ESP_LOGD(TAG, "Resetting camera by power down line"); + gpio_config_t conf = { 0 }; + conf.pin_bit_mask = 1LL << config->pin_pwdn; + conf.mode = GPIO_MODE_OUTPUT; + gpio_config(&conf); + + // carefull, logic is inverted compared to reset pin + gpio_set_level(config->pin_pwdn, 1); + vTaskDelay(10 / portTICK_PERIOD_MS); + gpio_set_level(config->pin_pwdn, 0); + vTaskDelay(10 / portTICK_PERIOD_MS); + } + + if(config->pin_reset >= 0) { + ESP_LOGD(TAG, "Resetting camera"); + gpio_config_t conf = { 0 }; + conf.pin_bit_mask = 1LL << config->pin_reset; + conf.mode = GPIO_MODE_OUTPUT; + gpio_config(&conf); + + gpio_set_level(config->pin_reset, 0); + vTaskDelay(10 / portTICK_PERIOD_MS); + gpio_set_level(config->pin_reset, 1); + vTaskDelay(10 / portTICK_PERIOD_MS); + } + + ESP_LOGD(TAG, "Searching for camera address"); + vTaskDelay(10 / portTICK_PERIOD_MS); + uint8_t slv_addr = SCCB_Probe(); + if (slv_addr == 0) { + *out_camera_model = CAMERA_NONE; + camera_disable_out_clock(); + return ESP_ERR_CAMERA_NOT_DETECTED; + } + + //slv_addr = 0x30; + ESP_LOGD(TAG, "Detected camera at address=0x%02x", slv_addr); + sensor_id_t* id = &s_state->sensor.id; + +#if CONFIG_OV2640_SUPPORT + if (slv_addr == 0x30) { + ESP_LOGD(TAG, "Resetting OV2640"); + //camera might be OV2640. try to reset it + SCCB_Write(0x30, 0xFF, 0x01);//bank sensor + SCCB_Write(0x30, 0x12, 0x80);//reset + vTaskDelay(10 / portTICK_PERIOD_MS); + slv_addr = SCCB_Probe(); + } +#endif + + s_state->sensor.slv_addr = slv_addr; + s_state->sensor.xclk_freq_hz = config->xclk_freq_hz; + +#if (CONFIG_OV3660_SUPPORT || CONFIG_OV5640_SUPPORT) + if(s_state->sensor.slv_addr == 0x3c){ + id->PID = SCCB_Read16(s_state->sensor.slv_addr, REG16_CHIDH); + id->VER = SCCB_Read16(s_state->sensor.slv_addr, REG16_CHIDL); + vTaskDelay(10 / portTICK_PERIOD_MS); + ESP_LOGD(TAG, "Camera PID=0x%02x VER=0x%02x", id->PID, id->VER); + } else { +#endif + id->PID = SCCB_Read(s_state->sensor.slv_addr, REG_PID); + id->VER = SCCB_Read(s_state->sensor.slv_addr, REG_VER); + id->MIDL = SCCB_Read(s_state->sensor.slv_addr, REG_MIDL); + id->MIDH = SCCB_Read(s_state->sensor.slv_addr, REG_MIDH); + vTaskDelay(10 / portTICK_PERIOD_MS); + ESP_LOGD(TAG, "Camera PID=0x%02x VER=0x%02x MIDL=0x%02x MIDH=0x%02x", + id->PID, id->VER, id->MIDH, id->MIDL); + +#if (CONFIG_OV3660_SUPPORT || CONFIG_OV5640_SUPPORT) + } +#endif + + + switch (id->PID) { +#if CONFIG_OV2640_SUPPORT + case OV2640_PID: + *out_camera_model = CAMERA_OV2640; + ov2640_init(&s_state->sensor); + break; +#endif +#if CONFIG_OV7725_SUPPORT + case OV7725_PID: + *out_camera_model = CAMERA_OV7725; + ov7725_init(&s_state->sensor); + break; +#endif +#if CONFIG_OV3660_SUPPORT + case OV3660_PID: + *out_camera_model = CAMERA_OV3660; + ov3660_init(&s_state->sensor); + break; +#endif +#if CONFIG_OV5640_SUPPORT + case OV5640_PID: + *out_camera_model = CAMERA_OV5640; + ov5640_init(&s_state->sensor); + break; +#endif + default: + id->PID = 0; + *out_camera_model = CAMERA_UNKNOWN; + camera_disable_out_clock(); + ESP_LOGE(TAG, "Detected camera not supported."); + return ESP_ERR_CAMERA_NOT_SUPPORTED; + } + + ESP_LOGD(TAG, "Doing SW reset of sensor"); + s_state->sensor.reset(&s_state->sensor); + + return ESP_OK; +} + +esp_err_t camera_init(const camera_config_t* config) +{ + if (!s_state) { + return ESP_ERR_INVALID_STATE; + } + if (s_state->sensor.id.PID == 0) { + return ESP_ERR_CAMERA_NOT_SUPPORTED; + } + memcpy(&s_state->config, config, sizeof(*config)); + esp_err_t err = ESP_OK; + framesize_t frame_size = (framesize_t) config->frame_size; + pixformat_t pix_format = (pixformat_t) config->pixel_format; + + switch (s_state->sensor.id.PID) { +#if CONFIG_OV2640_SUPPORT + case OV2640_PID: + if (frame_size > FRAMESIZE_UXGA) { + frame_size = FRAMESIZE_UXGA; + } + break; +#endif +#if CONFIG_OV7725_SUPPORT + case OV7725_PID: + if (frame_size > FRAMESIZE_VGA) { + frame_size = FRAMESIZE_VGA; + } + break; +#endif +#if CONFIG_OV3660_SUPPORT + case OV3660_PID: + if (frame_size > FRAMESIZE_QXGA) { + frame_size = FRAMESIZE_QXGA; + } + break; +#endif +#if CONFIG_OV5640_SUPPORT + case OV5640_PID: + if (frame_size > FRAMESIZE_QSXGA) { + frame_size = FRAMESIZE_QSXGA; + } + break; +#endif + default: + return ESP_ERR_CAMERA_NOT_SUPPORTED; + } + + s_state->width = resolution[frame_size].width; + s_state->height = resolution[frame_size].height; + + if (pix_format == PIXFORMAT_GRAYSCALE) { + s_state->fb_size = s_state->width * s_state->height; + if (s_state->sensor.id.PID == OV3660_PID || s_state->sensor.id.PID == OV5640_PID) { + if (is_hs_mode()) { + s_state->sampling_mode = SM_0A00_0B00; + s_state->dma_filter = &dma_filter_yuyv_highspeed; + } else { + s_state->sampling_mode = SM_0A0B_0C0D; + s_state->dma_filter = &dma_filter_yuyv; + } + s_state->in_bytes_per_pixel = 1; // camera sends Y8 + } else { + if (is_hs_mode() && s_state->sensor.id.PID != OV7725_PID) { + s_state->sampling_mode = SM_0A00_0B00; + s_state->dma_filter = &dma_filter_grayscale_highspeed; + } else { + s_state->sampling_mode = SM_0A0B_0C0D; + s_state->dma_filter = &dma_filter_grayscale; + } + s_state->in_bytes_per_pixel = 2; // camera sends YU/YV + } + s_state->fb_bytes_per_pixel = 1; // frame buffer stores Y8 + } else if (pix_format == PIXFORMAT_YUV422 || pix_format == PIXFORMAT_RGB565) { + s_state->fb_size = s_state->width * s_state->height * 2; + if (is_hs_mode() && s_state->sensor.id.PID != OV7725_PID) { + s_state->sampling_mode = SM_0A00_0B00; + s_state->dma_filter = &dma_filter_yuyv_highspeed; + } else { + s_state->sampling_mode = SM_0A0B_0C0D; + s_state->dma_filter = &dma_filter_yuyv; + } + s_state->in_bytes_per_pixel = 2; // camera sends YU/YV + s_state->fb_bytes_per_pixel = 2; // frame buffer stores YU/YV/RGB565 + } else if (pix_format == PIXFORMAT_RGB888) { + s_state->fb_size = s_state->width * s_state->height * 3; + if (is_hs_mode()) { + s_state->sampling_mode = SM_0A00_0B00; + s_state->dma_filter = &dma_filter_rgb888_highspeed; + } else { + s_state->sampling_mode = SM_0A0B_0C0D; + s_state->dma_filter = &dma_filter_rgb888; + } + s_state->in_bytes_per_pixel = 2; // camera sends RGB565 + s_state->fb_bytes_per_pixel = 3; // frame buffer stores RGB888 + } else if (pix_format == PIXFORMAT_JPEG) { + if (s_state->sensor.id.PID != OV2640_PID && s_state->sensor.id.PID != OV3660_PID && s_state->sensor.id.PID != OV5640_PID) { + ESP_LOGE(TAG, "JPEG format is only supported for ov2640, ov3660 and ov5640"); + err = ESP_ERR_NOT_SUPPORTED; + goto fail; + } + int qp = config->jpeg_quality; + int compression_ratio_bound = 1; + if (qp > 10) { + compression_ratio_bound = 16; + } else if (qp > 5) { + compression_ratio_bound = 10; + } else { + compression_ratio_bound = 4; + } + (*s_state->sensor.set_quality)(&s_state->sensor, qp); + s_state->in_bytes_per_pixel = 2; + s_state->fb_bytes_per_pixel = 2; + s_state->fb_size = (s_state->width * s_state->height * s_state->fb_bytes_per_pixel) / compression_ratio_bound; + s_state->dma_filter = &dma_filter_jpeg; + s_state->sampling_mode = SM_0A00_0B00; + } else { + ESP_LOGE(TAG, "Requested format is not supported"); + err = ESP_ERR_NOT_SUPPORTED; + goto fail; + } + + ESP_LOGD(TAG, "in_bpp: %d, fb_bpp: %d, fb_size: %d, mode: %d, width: %d height: %d", + s_state->in_bytes_per_pixel, s_state->fb_bytes_per_pixel, + s_state->fb_size, s_state->sampling_mode, + s_state->width, s_state->height); + + i2s_init(); + + err = dma_desc_init(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize I2S and DMA"); + goto fail; + } + + //s_state->fb_size = 75 * 1024; + err = camera_fb_init(s_state->config.fb_count); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to allocate frame buffer"); + goto fail; + } + + s_state->data_ready = xQueueCreate(16, sizeof(size_t)); + if (s_state->data_ready == NULL) { + ESP_LOGE(TAG, "Failed to dma queue"); + err = ESP_ERR_NO_MEM; + goto fail; + } + + if(s_state->config.fb_count == 1) { + s_state->frame_ready = xSemaphoreCreateBinary(); + if (s_state->frame_ready == NULL) { + ESP_LOGE(TAG, "Failed to create semaphore"); + err = ESP_ERR_NO_MEM; + goto fail; + } + } else { + s_state->fb_in = xQueueCreate(s_state->config.fb_count, sizeof(camera_fb_t *)); + s_state->fb_out = xQueueCreate(1, sizeof(camera_fb_t *)); + if (s_state->fb_in == NULL || s_state->fb_out == NULL) { + ESP_LOGE(TAG, "Failed to fb queues"); + err = ESP_ERR_NO_MEM; + goto fail; + } + } + + //ToDo: core affinity? +#if CONFIG_CAMERA_CORE0 + if (!xTaskCreatePinnedToCore(&dma_filter_task, "dma_filter", 4096, NULL, 10, &s_state->dma_filter_task, 0)) +#elif CONFIG_CAMERA_CORE1 + if (!xTaskCreatePinnedToCore(&dma_filter_task, "dma_filter", 4096, NULL, 10, &s_state->dma_filter_task, 1)) +#else + if (!xTaskCreate(&dma_filter_task, "dma_filter", 4096, NULL, 10, &s_state->dma_filter_task)) +#endif + { + ESP_LOGE(TAG, "Failed to create DMA filter task"); + err = ESP_ERR_NO_MEM; + goto fail; + } + + vsync_intr_disable(); + err = gpio_install_isr_service(ESP_INTR_FLAG_LEVEL1 | ESP_INTR_FLAG_IRAM); + if (err != ESP_OK) { + 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; + 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 { + 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(); + free(s_state); + s_state = NULL; + camera_disable_out_clock(); + periph_module_disable(PERIPH_I2S0_MODULE); + return ESP_OK; +} + +#define FB_GET_TIMEOUT (4000 / portTICK_PERIOD_MS) + +camera_fb_t* esp_camera_fb_get() +{ + if (s_state == NULL) { + return NULL; + } + if(!I2S0.conf.rx_start) { + if(s_state->config.fb_count > 1) { + ESP_LOGD(TAG, "i2s_run"); + } + if (i2s_run() != 0) { + return NULL; + } + } + bool need_yield = false; + if (s_state->config.fb_count == 1) { + if (xSemaphoreTake(s_state->frame_ready, FB_GET_TIMEOUT) != pdTRUE){ + i2s_stop(&need_yield); + ESP_LOGE(TAG, "Failed to get the frame on time!"); + return NULL; + } + return (camera_fb_t*)s_state->fb; + } + camera_fb_int_t * fb = NULL; + if(s_state->fb_out) { + if (xQueueReceive(s_state->fb_out, &fb, FB_GET_TIMEOUT) != pdTRUE) { + i2s_stop(&need_yield); + ESP_LOGE(TAG, "Failed to get the frame on time!"); + return NULL; + } + } + return (camera_fb_t*)fb; +} + +void esp_camera_fb_return(camera_fb_t * fb) +{ + if(fb == NULL || s_state == NULL || s_state->config.fb_count == 1 || s_state->fb_in == NULL) { + return; + } + xQueueSend(s_state->fb_in, &fb, portMAX_DELAY); +} + +sensor_t * esp_camera_sensor_get() +{ + if (s_state == NULL) { + return NULL; + } + return &s_state->sensor; +} + +esp_err_t esp_camera_save_to_nvs(const char *key) +{ +#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 new file mode 100644 index 0000000..8a2db5c --- /dev/null +++ b/esp32-cam-rtos-allframes/camera_common.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include +#include "esp_err.h" +#include "esp_intr_alloc.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/task.h" +#include "esp_camera.h" +#include "sensor.h" + +#include "esp_system.h" +#if ESP_IDF_VERSION_MAJOR >= 4 // IDF 4+ +#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 +#include "esp32/rom/lldesc.h" +#else +#error Target CONFIG_IDF_TARGET is not supported +#endif +#else // ESP32 Before IDF 4.0 +#include "rom/lldesc.h" +#endif + +typedef union { + struct { + uint8_t sample2; + uint8_t unused2; + uint8_t sample1; + uint8_t unused1; + }; + uint32_t val; +} dma_elem_t; + +typedef enum { + /* camera sends byte sequence: s1, s2, s3, s4, ... + * fifo receives: 00 s1 00 s2, 00 s2 00 s3, 00 s3 00 s4, ... + */ + SM_0A0B_0B0C = 0, + /* camera sends byte sequence: s1, s2, s3, s4, ... + * fifo receives: 00 s1 00 s2, 00 s3 00 s4, ... + */ + SM_0A0B_0C0D = 1, + /* camera sends byte sequence: s1, s2, s3, s4, ... + * fifo receives: 00 s1 00 00, 00 s2 00 00, 00 s3 00 00, ... + */ + SM_0A00_0B00 = 3, +} i2s_sampling_mode_t; + diff --git a/esp32-cam-rtos-allframes/camera_pins.h b/esp32-cam-rtos-allframes/camera_pins.h new file mode 100644 index 0000000..7855722 --- /dev/null +++ b/esp32-cam-rtos-allframes/camera_pins.h @@ -0,0 +1,99 @@ + +#if defined(CAMERA_MODEL_WROVER_KIT) +#define PWDN_GPIO_NUM -1 +#define RESET_GPIO_NUM -1 +#define XCLK_GPIO_NUM 21 +#define SIOD_GPIO_NUM 26 +#define SIOC_GPIO_NUM 27 + +#define Y9_GPIO_NUM 35 +#define Y8_GPIO_NUM 34 +#define Y7_GPIO_NUM 39 +#define Y6_GPIO_NUM 36 +#define Y5_GPIO_NUM 19 +#define Y4_GPIO_NUM 18 +#define Y3_GPIO_NUM 5 +#define Y2_GPIO_NUM 4 +#define VSYNC_GPIO_NUM 25 +#define HREF_GPIO_NUM 23 +#define PCLK_GPIO_NUM 22 + +#elif defined(CAMERA_MODEL_ESP_EYE) +#define PWDN_GPIO_NUM -1 +#define RESET_GPIO_NUM -1 +#define XCLK_GPIO_NUM 4 +#define SIOD_GPIO_NUM 18 +#define SIOC_GPIO_NUM 23 + +#define Y9_GPIO_NUM 36 +#define Y8_GPIO_NUM 37 +#define Y7_GPIO_NUM 38 +#define Y6_GPIO_NUM 39 +#define Y5_GPIO_NUM 35 +#define Y4_GPIO_NUM 14 +#define Y3_GPIO_NUM 13 +#define Y2_GPIO_NUM 34 +#define VSYNC_GPIO_NUM 5 +#define HREF_GPIO_NUM 27 +#define PCLK_GPIO_NUM 25 + +#elif defined(CAMERA_MODEL_M5STACK_PSRAM) +#define PWDN_GPIO_NUM -1 +#define RESET_GPIO_NUM 15 +#define XCLK_GPIO_NUM 27 +#define SIOD_GPIO_NUM 25 +#define SIOC_GPIO_NUM 23 + +#define Y9_GPIO_NUM 19 +#define Y8_GPIO_NUM 36 +#define Y7_GPIO_NUM 18 +#define Y6_GPIO_NUM 39 +#define Y5_GPIO_NUM 5 +#define Y4_GPIO_NUM 34 +#define Y3_GPIO_NUM 35 +#define Y2_GPIO_NUM 32 +#define VSYNC_GPIO_NUM 22 +#define HREF_GPIO_NUM 26 +#define PCLK_GPIO_NUM 21 + +#elif defined(CAMERA_MODEL_M5STACK_WIDE) +#define PWDN_GPIO_NUM -1 +#define RESET_GPIO_NUM 15 +#define XCLK_GPIO_NUM 27 +#define SIOD_GPIO_NUM 22 +#define SIOC_GPIO_NUM 23 + +#define Y9_GPIO_NUM 19 +#define Y8_GPIO_NUM 36 +#define Y7_GPIO_NUM 18 +#define Y6_GPIO_NUM 39 +#define Y5_GPIO_NUM 5 +#define Y4_GPIO_NUM 34 +#define Y3_GPIO_NUM 35 +#define Y2_GPIO_NUM 32 +#define VSYNC_GPIO_NUM 25 +#define HREF_GPIO_NUM 26 +#define PCLK_GPIO_NUM 21 + +#elif defined(CAMERA_MODEL_AI_THINKER) +#define PWDN_GPIO_NUM 32 +#define RESET_GPIO_NUM -1 +#define XCLK_GPIO_NUM 0 +#define SIOD_GPIO_NUM 26 +#define SIOC_GPIO_NUM 27 + +#define Y9_GPIO_NUM 35 +#define Y8_GPIO_NUM 34 +#define Y7_GPIO_NUM 39 +#define Y6_GPIO_NUM 36 +#define Y5_GPIO_NUM 21 +#define Y4_GPIO_NUM 19 +#define Y3_GPIO_NUM 18 +#define Y2_GPIO_NUM 5 +#define VSYNC_GPIO_NUM 25 +#define HREF_GPIO_NUM 23 +#define PCLK_GPIO_NUM 22 + +#else +#error "Camera model not selected" +#endif diff --git a/esp32-cam-rtos-allframes/esp32-cam-rtos-allframes.ino b/esp32-cam-rtos-allframes/esp32-cam-rtos-allframes.ino new file mode 100644 index 0000000..c7e0d50 --- /dev/null +++ b/esp32-cam-rtos-allframes/esp32-cam-rtos-allframes.ino @@ -0,0 +1,459 @@ +/* + + This is a simple MJPEG streaming webserver implemented for AI-Thinker ESP32-CAM + and ESP-EYE modules. + This is tested to work with VLC and Blynk video widget and can support up to 10 + simultaneously connected streaming clients. + Simultaneous streaming is implemented with dedicated FreeRTOS tasks. + + Inspired by and based on this Instructable: $9 RTSP Video Streamer Using the ESP32-CAM Board + (https://www.instructables.com/id/9-RTSP-Video-Streamer-Using-the-ESP32-CAM-Board/) + + Board: AI-Thinker ESP32-CAM or ESP-EYE + Compile as: + ESP32 Dev Module + CPU Freq: 240 + Flash Freq: 80 + Flash mode: QIO + Flash Size: 4Mb + Partrition: Minimal SPIFFS + PSRAM: Enabled +*/ + +// ESP32 has two cores: APPlication core and PROcess core (the one that runs ESP32 SDK stack) +#define APP_CPU 1 +#define PRO_CPU 0 + +#include "esp_camera.h" +#include "ov2640.h" +#include +#include +#include + +#include +#include +#include +#include + +// Select camera model +//#define CAMERA_MODEL_WROVER_KIT +#define CAMERA_MODEL_ESP_EYE +//#define CAMERA_MODEL_M5STACK_PSRAM +//#define CAMERA_MODEL_M5STACK_WIDE +//#define CAMERA_MODEL_AI_THINKER + +#define MAX_CLIENTS 10 + +#include "camera_pins.h" + +/* + Next one is an include with wifi credentials. + This is what you need to do: + + 1. Create a file called "home_wifi_multi.h" in the same folder OR under a separate subfolder of the "libraries" folder of Arduino IDE. (You are creating a "fake" library really - I called it "MySettings"). + 2. Place the following text in the file: + #define SSID1 "replace with your wifi ssid" + #define PWD1 "replace your wifi password" + 3. Save. + + Should work then +*/ +#include "home_wifi_multi.h" + +//OV2640 cam; + +WebServer server(80); + +// ===== rtos task handles ========================= +// Streaming is implemented with 3 tasks: +TaskHandle_t tMjpeg; // handles client connections to the webserver +TaskHandle_t tCam; // handles getting picture frames from the camera and storing them locally + +uint8_t noActiveClients; // number of active clients + +// frameSync semaphore is used to prevent streaming buffer as it is replaced with the next frame +SemaphoreHandle_t frameSync = NULL; + +// We will try to achieve FPS frame rate +const int FPS = 10; + +// We will handle web client requests every 100 ms (10 Hz) +const int WSINTERVAL = 100; + + +// ======== Server Connection Handler Task ========================== +void mjpegCB(void* pvParameters) { + TickType_t xLastWakeTime; + const TickType_t xFrequency = pdMS_TO_TICKS(WSINTERVAL); + + // Creating frame synchronization semaphore and initializing it + frameSync = xSemaphoreCreateBinary(); + xSemaphoreGive( frameSync ); + + //=== setup section ================== + + // Creating RTOS task for grabbing frames from the camera + xTaskCreatePinnedToCore( + camCB, // callback + "cam", // name + 4 * 1024, // stacj size + NULL, // parameters + 2, // priority + &tCam, // RTOS task handle + PRO_CPU); // core + + // Registering webserver handling routines + server.on("/mjpeg/1", HTTP_GET, handleJPGSstream); + server.onNotFound(handleNotFound); + + // Starting webserver + server.begin(); + + noActiveClients = 0; + + Serial.printf("mjpegCB: free heap (start) : %d\n", ESP.getFreeHeap()); + + xLastWakeTime = xTaskGetTickCount(); + + // int ticker = 0; + for (;;) { + server.handleClient(); + + // After every server client handling request, we let other tasks run and then pause + taskYIELD(); + vTaskDelayUntil(&xLastWakeTime, xFrequency); + // if ( (ticker++ % 10) == 0 ) Serial.printf("mjpegCB: main loop tick\n"); + } +} + + +// Current frame information +volatile uint32_t frameNumber; +//volatile size_t camSize; // size of the current frame, byte +//volatile char* camBuf; // pointer to the current frame + + +struct frameChunck { + uint8_t cnt; // served to clients counter. when equal to number of active clients, could be deleted + uint32_t* nxt; // next chunck + uint32_t fnm; // frame number + uint32_t siz; // frame size + uint8_t* dat; // frame pointer +}; + +frameChunck* fstFrame; // first frame +frameChunck* curFrame; // current frame being captured by the camera + + +// ==== RTOS task to grab frames from the camera ========================= +void camCB(void* pvParameters) { + + TickType_t xLastWakeTime; + + // A running interval associated with currently desired frame rate + const TickType_t xFrequency = pdMS_TO_TICKS(1000 / FPS); + + frameNumber = 0; + + xLastWakeTime = xTaskGetTickCount(); + + for (;;) { + + camera_fb_t* fb = NULL; + + // Grab a frame from the camera and allocate frame chunk for it + fb = esp_camera_fb_get(); + frameChunck* f = (frameChunck*) ps_malloc( sizeof(frameChunck) ); + if ( f ) { + char* d = (char*) ps_malloc( fb->len ); + if ( d == NULL ) { + free (f); + } + else { + if ( frameNumber == 0 ) { + fstFrame = f; + } + f->dat = (uint8_t*) d; + f->nxt = NULL; + f->siz = fb->len; + f->cnt = 0; + memcpy(f->dat, (char *)fb->buf, fb->len); + f->fnm = frameNumber; + if ( curFrame ) { + curFrame->nxt = (uint32_t*) f; + } + curFrame = f; + // Serial.printf("Captured frame# %d\n", frameNumber); + frameNumber++; + } + } + esp_camera_fb_return(fb); + + taskYIELD(); + vTaskDelayUntil(&xLastWakeTime, xFrequency); + + if ( noActiveClients == 0 ) { + // we need to drain the cache if there are no more clients connected + while ( fstFrame->nxt ) { + frameChunck* f = (frameChunck*) fstFrame->nxt; + free ( fstFrame->dat ); + free ( fstFrame ); + fstFrame = f; + } + Serial.printf("mjpegCB: free heap : %d\n", ESP.getFreeHeap()); + Serial.printf("mjpegCB: min free heap) : %d\n", ESP.getMinFreeHeap()); + Serial.printf("mjpegCB: max alloc free heap : %d\n", ESP.getMaxAllocHeap()); + Serial.printf("mjpegCB: tCam stack wtrmark : %d\n", uxTaskGetStackHighWaterMark(tCam)); + Serial.flush(); + vTaskSuspend(NULL); // passing NULL means "suspend yourself" + } + } +} + + +// ==== STREAMING ====================================================== +const char HEADER[] = "HTTP/1.1 200 OK\r\n" \ + "Access-Control-Allow-Origin: *\r\n" \ + "Content-Type: multipart/x-mixed-replace; boundary=123456789000000000000987654321\r\n"; +const char BOUNDARY[] = "\r\n--123456789000000000000987654321\r\n"; +const char CTNTTYPE[] = "Content-Type: image/jpeg\r\nContent-Length: "; +const int hdrLen = strlen(HEADER); +const int bdrLen = strlen(BOUNDARY); +const int cntLen = strlen(CTNTTYPE); + + +struct streamInfo { + WiFiClient *client; + TaskHandle_t task; +}; + +// ==== Handle connection request from clients =============================== +void handleJPGSstream(void) +{ + if ( noActiveClients >= MAX_CLIENTS ) return; + Serial.printf("handleJPGSstream start: free heap : %d\n", ESP.getFreeHeap()); + + streamInfo* info = (streamInfo*) malloc( sizeof(streamInfo) ); + info->client = new WiFiClient; + *(info->client) = server.client(); + + // Creating task to push the stream to all connected clients + int rc = xTaskCreatePinnedToCore( + streamCB, + "strmCB", + 3 * 1024, + (void*) info, + 2, + &info->task, + APP_CPU); + if ( rc != pdPASS ) { + Serial.printf("handleJPGSstream: error creating RTOS task. rc = %d\n", rc); + Serial.printf("handleJPGSstream: free heap : %d\n", ESP.getFreeHeap()); + // Serial.printf("stk high wm: %d\n", uxTaskGetStackHighWaterMark(tSend)); + delete info; + } + + noActiveClients++; + + // Wake up streaming tasks, if they were previously suspended: + if ( eTaskGetState( tCam ) == eSuspended ) vTaskResume( tCam ); +} + + +// ==== Actually stream content to all connected clients ======================== +void streamCB(void * pvParameters) { + char buf[16]; + TickType_t xLastWakeTime; + TickType_t xFrequency; + frameChunck* myFrame = fstFrame; + portMUX_TYPE xSemaphore = portMUX_INITIALIZER_UNLOCKED; + + streamInfo* info = (streamInfo*) pvParameters; + + if ( info == NULL ) { + Serial.println("streamCB: a NULL pointer passed"); + } + // Immediately send this client a header + info->client->write(HEADER, hdrLen); + info->client->write(BOUNDARY, bdrLen); + taskYIELD(); + + xLastWakeTime = xTaskGetTickCount(); + xFrequency = pdMS_TO_TICKS(1000 / FPS); + + for (;;) { + // Only bother to send anything if there is someone watching + if ( info->client->connected() ) { + if ( myFrame ) { + + info->client->write(CTNTTYPE, cntLen); + sprintf(buf, "%d\r\n\r\n", fstFrame->siz); + info->client->write(buf, strlen(buf)); + info->client->write((char*) fstFrame->dat, (size_t)fstFrame->siz); + info->client->write(BOUNDARY, bdrLen); + info->client->flush(); + + // Serial.printf("Served frame# %d\n", fstFrame->fnm); + + + if ( myFrame->nxt ) { + frameChunck* f; + f = (frameChunck*) myFrame->nxt; + + portENTER_CRITICAL(&xSemaphore); + if ( ++myFrame->cnt == noActiveClients ) { + assert(myFrame == fstFrame); + free ( fstFrame->dat ); + fstFrame->dat = NULL; + free ( fstFrame ); + fstFrame = f; + } + portEXIT_CRITICAL(&xSemaphore); + myFrame = f; + } + } + else { + myFrame = fstFrame; + } + } + else { + // client disconnected - clean up. + noActiveClients--; + Serial.printf("streamCB: Stream Task stack wtrmark : %d\n", uxTaskGetStackHighWaterMark(info->task)); + Serial.flush(); + info->client->flush(); + info->client->stop(); + delete info->client; + info->client = NULL; + free( info ); + info = NULL; + vTaskDelete(NULL); + } + // Let other tasks run after serving every client + taskYIELD(); + vTaskDelayUntil(&xLastWakeTime, xFrequency); + } +} + + + +// ==== Handle invalid URL requests ============================================ +void handleNotFound() +{ + String message = "Server is running!\n\n"; + message += "URI: "; + message += server.uri(); + message += "\nMethod: "; + message += (server.method() == HTTP_GET) ? "GET" : "POST"; + message += "\nArguments: "; + message += server.args(); + message += "\n"; + server.send(200, "text / plain", message); +} + + + +// ==== SETUP method ================================================================== +void setup() +{ + + // Setup Serial connection: + Serial.begin(115200); + delay(1000); // wait for a second to let Serial connect + Serial.printf("setup: free heap : %d\n", ESP.getFreeHeap()); + + static camera_config_t camera_config = { + .pin_pwdn = PWDN_GPIO_NUM, + .pin_reset = RESET_GPIO_NUM, + .pin_xclk = XCLK_GPIO_NUM, + .pin_sscb_sda = SIOD_GPIO_NUM, + .pin_sscb_scl = SIOC_GPIO_NUM, + .pin_d7 = Y9_GPIO_NUM, + .pin_d6 = Y8_GPIO_NUM, + .pin_d5 = Y7_GPIO_NUM, + .pin_d4 = Y6_GPIO_NUM, + .pin_d3 = Y5_GPIO_NUM, + .pin_d2 = Y4_GPIO_NUM, + .pin_d1 = Y3_GPIO_NUM, + .pin_d0 = Y2_GPIO_NUM, + .pin_vsync = VSYNC_GPIO_NUM, + .pin_href = HREF_GPIO_NUM, + .pin_pclk = PCLK_GPIO_NUM, + + .xclk_freq_hz = 20000000, + .ledc_timer = LEDC_TIMER_0, + .ledc_channel = LEDC_CHANNEL_0, + .pixel_format = PIXFORMAT_JPEG, + /* + FRAMESIZE_96X96, // 96x96 + FRAMESIZE_QQVGA, // 160x120 + FRAMESIZE_QCIF, // 176x144 + FRAMESIZE_HQVGA, // 240x176 + FRAMESIZE_240X240, // 240x240 + FRAMESIZE_QVGA, // 320x240 + FRAMESIZE_CIF, // 400x296 + FRAMESIZE_HVGA, // 480x320 + FRAMESIZE_VGA, // 640x480 + FRAMESIZE_SVGA, // 800x600 + FRAMESIZE_XGA, // 1024x768 + FRAMESIZE_HD, // 1280x720 + FRAMESIZE_SXGA, // 1280x1024 + FRAMESIZE_UXGA, // 1600x1200 + */ + // .frame_size = FRAMESIZE_QVGA, + // .frame_size = FRAMESIZE_UXGA, + // .frame_size = FRAMESIZE_SVGA, + // .frame_size = FRAMESIZE_VGA, + .frame_size = FRAMESIZE_HD, + // .frame_size = FRAMESIZE_UXGA, + .jpeg_quality = 24, + .fb_count = 2 + }; + +#if defined(CAMERA_MODEL_ESP_EYE) + pinMode(13, INPUT_PULLUP); + pinMode(14, INPUT_PULLUP); +#endif + + if (esp_camera_init(&camera_config) != ESP_OK) { + Serial.println("Error initializing the camera"); + delay(10000); + ESP.restart(); + } + + + // Configure and connect to WiFi + IPAddress ip; + + WiFi.mode(WIFI_STA); + WiFi.begin(SSID1, PWD1); + Serial.print("Connecting to WiFi"); + while (WiFi.status() != WL_CONNECTED) + { + delay(500); + Serial.print(F(".")); + } + ip = WiFi.localIP(); + Serial.println(F("WiFi connected")); + Serial.println(""); + Serial.print("\nStream Link: http://"); + Serial.print(ip); + Serial.println("/mjpeg/1\n\n"); + + + // Start mainstreaming RTOS task + xTaskCreatePinnedToCore( + mjpegCB, + "mjpeg", + 3 * 1024, + NULL, + 2, + &tMjpeg, + APP_CPU); + + Serial.printf("setup complete: free heap : %d\n", ESP.getFreeHeap()); +} + +void loop() { + vTaskDelay(1000); +} 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..e8118ae8cbde083aa2d898912ca0840cbb1ba02f GIT binary patch literal 99319 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%0DWVpqK+tQ4{dZyTI7z{pE?+UbMA=x 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

ccC}J~5Ws<*6rFR> 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, fb->height, fb->pixformat, fb->buf, fb->len); + + //return the frame buffer back to be reused + esp_camera_fb_return(fb); + + return ESP_OK; + } +*/ + +#pragma once + +#include "esp_err.h" +#include "driver/ledc.h" +#include "sensor.h" +#include "sys/time.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Configuration structure for camera initialization + */ +typedef struct { + int pin_pwdn; /*!< GPIO pin for camera power down line */ + int pin_reset; /*!< GPIO pin for camera reset line */ + int pin_xclk; /*!< GPIO pin for camera XCLK line */ + int pin_sscb_sda; /*!< GPIO pin for camera SDA line */ + int pin_sscb_scl; /*!< GPIO pin for camera SCL line */ + int pin_d7; /*!< GPIO pin for camera D7 line */ + int pin_d6; /*!< GPIO pin for camera D6 line */ + int pin_d5; /*!< GPIO pin for camera D5 line */ + int pin_d4; /*!< GPIO pin for camera D4 line */ + int pin_d3; /*!< GPIO pin for camera D3 line */ + int pin_d2; /*!< GPIO pin for camera D2 line */ + int pin_d1; /*!< GPIO pin for camera D1 line */ + int pin_d0; /*!< GPIO pin for camera D0 line */ + int pin_vsync; /*!< GPIO pin for camera VSYNC line */ + int pin_href; /*!< GPIO pin for camera HREF line */ + int pin_pclk; /*!< GPIO pin for camera PCLK line */ + + int xclk_freq_hz; /*!< Frequency of XCLK signal, in Hz. Either 20KHz or 10KHz for OV2640 double FPS (Experimental) */ + + ledc_timer_t ledc_timer; /*!< LEDC timer to be used for generating XCLK */ + ledc_channel_t ledc_channel; /*!< LEDC channel to be used for generating XCLK */ + + pixformat_t pixel_format; /*!< Format of the pixel data: PIXFORMAT_ + YUV422|GRAYSCALE|RGB565|JPEG */ + framesize_t frame_size; /*!< Size of the output image: FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA */ + + int jpeg_quality; /*!< Quality of JPEG output. 0-63 lower means higher quality */ + size_t fb_count; /*!< Number of frame buffers to be allocated. If more than one, then each frame will be acquired (double speed) */ +} camera_config_t; + +/** + * @brief Data structure of camera frame buffer + */ +typedef struct { + uint8_t * buf; /*!< Pointer to the pixel data */ + size_t len; /*!< Length of the buffer in bytes */ + size_t width; /*!< Width of the buffer in pixels */ + size_t height; /*!< Height of the buffer in pixels */ + pixformat_t format; /*!< Format of the pixel data */ + struct timeval timestamp; /*!< Timestamp since boot of the first DMA buffer of the frame */ +} camera_fb_t; + +#define ESP_ERR_CAMERA_BASE 0x20000 +#define ESP_ERR_CAMERA_NOT_DETECTED (ESP_ERR_CAMERA_BASE + 1) +#define ESP_ERR_CAMERA_FAILED_TO_SET_FRAME_SIZE (ESP_ERR_CAMERA_BASE + 2) +#define ESP_ERR_CAMERA_FAILED_TO_SET_OUT_FORMAT (ESP_ERR_CAMERA_BASE + 3) +#define ESP_ERR_CAMERA_NOT_SUPPORTED (ESP_ERR_CAMERA_BASE + 4) + +/** + * @brief Initialize the camera driver + * + * @note call camera_probe before calling this function + * + * This function detects and configures camera over I2C interface, + * allocates framebuffer and DMA buffers, + * initializes parallel I2S input, and sets up DMA descriptors. + * + * Currently this function can only be called once and there is + * no way to de-initialize this module. + * + * @param config Camera configuration parameters + * + * @return ESP_OK on success + */ +esp_err_t esp_camera_init(const camera_config_t* config); + +/** + * @brief Deinitialize the camera driver + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if the driver hasn't been initialized yet + */ +esp_err_t esp_camera_deinit(); + +/** + * @brief Obtain pointer to a frame buffer. + * + * @return pointer to the frame buffer + */ +camera_fb_t* esp_camera_fb_get(); + +/** + * @brief Return the frame buffer to be reused again. + * + * @param fb Pointer to the frame buffer + */ +void esp_camera_fb_return(camera_fb_t * fb); + +/** + * @brief Get a pointer to the image sensor control structure + * + * @return pointer to the sensor + */ +sensor_t * esp_camera_sensor_get(); + +/** + * @brief Save camera settings to non-volatile-storage (NVS) + * + * @param key A unique nvs key name for the camera settings + */ +esp_err_t esp_camera_save_to_nvs(const char *key); + +/** + * @brief Load camera settings from non-volatile-storage (NVS) + * + * @param key A unique nvs key name for the camera settings + */ +esp_err_t esp_camera_load_from_nvs(const char *key); + +#ifdef __cplusplus +} +#endif + +#include "img_converters.h" + diff --git a/esp32-cam-rtos-allframes/esp_jpg_decode.c b/esp32-cam-rtos-allframes/esp_jpg_decode.c new file mode 100644 index 0000000..d42794f --- /dev/null +++ b/esp32-cam-rtos-allframes/esp_jpg_decode.c @@ -0,0 +1,128 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "esp_jpg_decode.h" + +#include "esp_system.h" +#if ESP_IDF_VERSION_MAJOR >= 4 // IDF 4+ +#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 +#include "esp32/rom/tjpgd.h" +#else +#error Target CONFIG_IDF_TARGET is not supported +#endif +#else // ESP32 Before IDF 4.0 +#include "rom/tjpgd.h" +#endif + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define TAG "" +#else +#include "esp_log.h" +static const char* TAG = "esp_jpg_decode"; +#endif + +typedef struct { + jpg_scale_t scale; + jpg_reader_cb reader; + jpg_writer_cb writer; + void * arg; + size_t len; + size_t index; +} esp_jpg_decoder_t; + +static const char * jd_errors[] = { + "Succeeded", + "Interrupted by output function", + "Device error or wrong termination of input stream", + "Insufficient memory pool for the image", + "Insufficient stream input buffer", + "Parameter error", + "Data format error", + "Right format but not supported", + "Not supported JPEG standard" +}; + +static uint32_t _jpg_write(JDEC *decoder, void *bitmap, JRECT *rect) +{ + uint16_t x = rect->left; + uint16_t y = rect->top; + uint16_t w = rect->right + 1 - x; + uint16_t h = rect->bottom + 1 - y; + uint8_t *data = (uint8_t *)bitmap; + + esp_jpg_decoder_t * jpeg = (esp_jpg_decoder_t *)decoder->device; + + if (jpeg->writer) { + return jpeg->writer(jpeg->arg, x, y, w, h, data); + } + return 0; +} + +static uint32_t _jpg_read(JDEC *decoder, uint8_t *buf, uint32_t len) +{ + esp_jpg_decoder_t * jpeg = (esp_jpg_decoder_t *)decoder->device; + if (jpeg->len && len > (jpeg->len - jpeg->index)) { + len = jpeg->len - jpeg->index; + } + if (len) { + len = jpeg->reader(jpeg->arg, jpeg->index, buf, len); + if (!len) { + ESP_LOGE(TAG, "Read Fail at %u/%u", jpeg->index, jpeg->len); + } + jpeg->index += len; + } + return len; +} + +esp_err_t esp_jpg_decode(size_t len, jpg_scale_t scale, jpg_reader_cb reader, jpg_writer_cb writer, void * arg) +{ + static uint8_t work[3100]; + JDEC decoder; + esp_jpg_decoder_t jpeg; + + jpeg.len = len; + jpeg.reader = reader; + jpeg.writer = writer; + jpeg.arg = arg; + jpeg.scale = scale; + jpeg.index = 0; + + JRESULT jres = jd_prepare(&decoder, _jpg_read, work, 3100, &jpeg); + if(jres != JDR_OK){ + ESP_LOGE(TAG, "JPG Header Parse Failed! %s", jd_errors[jres]); + return ESP_FAIL; + } + + uint16_t output_width = decoder.width / (1 << (uint8_t)(jpeg.scale)); + uint16_t output_height = decoder.height / (1 << (uint8_t)(jpeg.scale)); + + //output start + writer(arg, 0, 0, output_width, output_height, NULL); + //output write + jres = jd_decomp(&decoder, _jpg_write, (uint8_t)jpeg.scale); + //output end + writer(arg, output_width, output_height, output_width, output_height, NULL); + + if (jres != JDR_OK) { + ESP_LOGE(TAG, "JPG Decompression Failed! %s", jd_errors[jres]); + return ESP_FAIL; + } + //check if all data has been consumed. + if (len && jpeg.index < len) { + _jpg_read(&decoder, NULL, len - jpeg.index); + } + + return ESP_OK; +} + diff --git a/esp32-cam-rtos-allframes/esp_jpg_decode.h b/esp32-cam-rtos-allframes/esp_jpg_decode.h new file mode 100644 index 0000000..f13536e --- /dev/null +++ b/esp32-cam-rtos-allframes/esp_jpg_decode.h @@ -0,0 +1,43 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef _ESP_JPG_DECODE_H_ +#define _ESP_JPG_DECODE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include "esp_err.h" + +typedef enum { + JPG_SCALE_NONE, + JPG_SCALE_2X, + JPG_SCALE_4X, + JPG_SCALE_8X, + JPG_SCALE_MAX = JPG_SCALE_8X +} jpg_scale_t; + +typedef size_t (* jpg_reader_cb)(void * arg, size_t index, uint8_t *buf, size_t len); +typedef bool (* jpg_writer_cb)(void * arg, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t *data); + +esp_err_t esp_jpg_decode(size_t len, jpg_scale_t scale, jpg_reader_cb reader, jpg_writer_cb writer, void * arg); + +#ifdef __cplusplus +} +#endif + +#endif /* _ESP_JPG_DECODE_H_ */ diff --git a/esp32-cam-rtos-allframes/img_converters.h b/esp32-cam-rtos-allframes/img_converters.h new file mode 100644 index 0000000..2b83c4d --- /dev/null +++ b/esp32-cam-rtos-allframes/img_converters.h @@ -0,0 +1,126 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef _IMG_CONVERTERS_H_ +#define _IMG_CONVERTERS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include "esp_camera.h" + +typedef size_t (* jpg_out_cb)(void * arg, size_t index, const void* data, size_t len); + +/** + * @brief Convert image buffer to JPEG + * + * @param src Source buffer in RGB565, RGB888, YUYV or GRAYSCALE format + * @param src_len Length in bytes of the source buffer + * @param width Width in pixels of the source image + * @param height Height in pixels of the source image + * @param format Format of the source image + * @param quality JPEG quality of the resulting image + * @param cp Callback to be called to write the bytes of the output JPEG + * @param arg Pointer to be passed to the callback + * + * @return true on success + */ +bool fmt2jpg_cb(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, jpg_out_cb cb, void * arg); + +/** + * @brief Convert camera frame buffer to JPEG + * + * @param fb Source camera frame buffer + * @param quality JPEG quality of the resulting image + * @param cp Callback to be called to write the bytes of the output JPEG + * @param arg Pointer to be passed to the callback + * + * @return true on success + */ +bool frame2jpg_cb(camera_fb_t * fb, uint8_t quality, jpg_out_cb cb, void * arg); + +/** + * @brief Convert image buffer to JPEG buffer + * + * @param src Source buffer in RGB565, RGB888, YUYV or GRAYSCALE format + * @param src_len Length in bytes of the source buffer + * @param width Width in pixels of the source image + * @param height Height in pixels of the source image + * @param format Format of the source image + * @param quality JPEG quality of the resulting image + * @param out Pointer to be populated with the address of the resulting buffer + * @param out_len Pointer to be populated with the length of the output buffer + * + * @return true on success + */ +bool fmt2jpg(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, uint8_t ** out, size_t * out_len); + +/** + * @brief Convert camera frame buffer to JPEG buffer + * + * @param fb Source camera frame buffer + * @param quality JPEG quality of the resulting image + * @param out Pointer to be populated with the address of the resulting buffer + * @param out_len Pointer to be populated with the length of the output buffer + * + * @return true on success + */ +bool frame2jpg(camera_fb_t * fb, uint8_t quality, uint8_t ** out, size_t * out_len); + +/** + * @brief Convert image buffer to BMP buffer + * + * @param src Source buffer in JPEG, RGB565, RGB888, YUYV or GRAYSCALE format + * @param src_len Length in bytes of the source buffer + * @param width Width in pixels of the source image + * @param height Height in pixels of the source image + * @param format Format of the source image + * @param out Pointer to be populated with the address of the resulting buffer + * @param out_len Pointer to be populated with the length of the output buffer + * + * @return true on success + */ +bool fmt2bmp(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t ** out, size_t * out_len); + +/** + * @brief Convert camera frame buffer to BMP buffer + * + * @param fb Source camera frame buffer + * @param out Pointer to be populated with the address of the resulting buffer + * @param out_len Pointer to be populated with the length of the output buffer + * + * @return true on success + */ +bool frame2bmp(camera_fb_t * fb, uint8_t ** out, size_t * out_len); + +/** + * @brief Convert image buffer to RGB888 buffer (used for face detection) + * + * @param src Source buffer in JPEG, RGB565, RGB888, YUYV or GRAYSCALE format + * @param src_len Length in bytes of the source buffer + * @param format Format of the source image + * @param rgb_buf Pointer to the output buffer (width * height * 3) + * + * @return true on success + */ +bool fmt2rgb888(const uint8_t *src_buf, size_t src_len, pixformat_t format, uint8_t * rgb_buf); + +#ifdef __cplusplus +} +#endif + +#endif /* _IMG_CONVERTERS_H_ */ diff --git a/esp32-cam-rtos-allframes/jpge.cpp b/esp32-cam-rtos-allframes/jpge.cpp new file mode 100644 index 0000000..a8ab93e --- /dev/null +++ b/esp32-cam-rtos-allframes/jpge.cpp @@ -0,0 +1,723 @@ +// jpge.cpp - C++ class for JPEG compression. +// Public domain, Rich Geldreich +// v1.01, Dec. 18, 2010 - Initial release +// v1.02, Apr. 6, 2011 - Removed 2x2 ordered dither in H2V1 chroma subsampling method load_block_16_8_8(). (The rounding factor was 2, when it should have been 1. Either way, it wasn't helping.) +// v1.03, Apr. 16, 2011 - Added support for optimized Huffman code tables, optimized dynamic memory allocation down to only 1 alloc. +// Also from Alex Evans: Added RGBA support, linear memory allocator (no longer needed in v1.03). +// v1.04, May. 19, 2012: Forgot to set m_pFile ptr to NULL in cfile_stream::close(). Thanks to Owen Kaluza for reporting this bug. +// Code tweaks to fix VS2008 static code analysis warnings (all looked harmless). +// Code review revealed method load_block_16_8_8() (used for the non-default H2V1 sampling mode to downsample chroma) somehow didn't get the rounding factor fix from v1.02. + +#include "jpge.h" + +#include +#include +#include +#include +#include +#include +#include +#include "esp_heap_caps.h" + +#define JPGE_MAX(a,b) (((a)>(b))?(a):(b)) +#define JPGE_MIN(a,b) (((a)<(b))?(a):(b)) + +namespace jpge { + + static inline void *jpge_malloc(size_t nSize) { + void * b = malloc(nSize); + if(b){ + return b; + } + return heap_caps_malloc(nSize, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + } + static inline void jpge_free(void *p) { free(p); } + + // Various JPEG enums and tables. + enum { M_SOF0 = 0xC0, M_DHT = 0xC4, M_SOI = 0xD8, M_EOI = 0xD9, M_SOS = 0xDA, M_DQT = 0xDB, M_APP0 = 0xE0 }; + enum { DC_LUM_CODES = 12, AC_LUM_CODES = 256, DC_CHROMA_CODES = 12, AC_CHROMA_CODES = 256, MAX_HUFF_SYMBOLS = 257, MAX_HUFF_CODESIZE = 32 }; + + static const uint8 s_zag[64] = { 0,1,8,16,9,2,3,10,17,24,32,25,18,11,4,5,12,19,26,33,40,48,41,34,27,20,13,6,7,14,21,28,35,42,49,56,57,50,43,36,29,22,15,23,30,37,44,51,58,59,52,45,38,31,39,46,53,60,61,54,47,55,62,63 }; + static const int16 s_std_lum_quant[64] = { 16,11,12,14,12,10,16,14,13,14,18,17,16,19,24,40,26,24,22,22,24,49,35,37,29,40,58,51,61,60,57,51,56,55,64,72,92,78,64,68,87,69,55,56,80,109,81,87,95,98,103,104,103,62,77,113,121,112,100,120,92,101,103,99 }; + static const int16 s_std_croma_quant[64] = { 17,18,18,24,21,24,47,26,26,47,99,66,56,66,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99 }; + static const uint8 s_dc_lum_bits[17] = { 0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0 }; + static const uint8 s_dc_lum_val[DC_LUM_CODES] = { 0,1,2,3,4,5,6,7,8,9,10,11 }; + static const uint8 s_ac_lum_bits[17] = { 0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d }; + static const uint8 s_ac_lum_val[AC_LUM_CODES] = { + 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0, + 0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49, + 0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5, + 0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, + 0xf9,0xfa + }; + static const uint8 s_dc_chroma_bits[17] = { 0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0 }; + static const uint8 s_dc_chroma_val[DC_CHROMA_CODES] = { 0,1,2,3,4,5,6,7,8,9,10,11 }; + static const uint8 s_ac_chroma_bits[17] = { 0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77 }; + static const uint8 s_ac_chroma_val[AC_CHROMA_CODES] = { + 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0, + 0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48, + 0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3, + 0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, + 0xf9,0xfa + }; + + const int YR = 19595, YG = 38470, YB = 7471, CB_R = -11059, CB_G = -21709, CB_B = 32768, CR_R = 32768, CR_G = -27439, CR_B = -5329; + + static int32 m_last_quality = 0; + static int32 m_quantization_tables[2][64]; + + static bool m_huff_initialized = false; + static uint m_huff_codes[4][256]; + static uint8 m_huff_code_sizes[4][256]; + static uint8 m_huff_bits[4][17]; + static uint8 m_huff_val[4][256]; + + static inline uint8 clamp(int i) { + if (i < 0) { + i = 0; + } else if (i > 255){ + i = 255; + } + return static_cast(i); + } + + static void RGB_to_YCC(uint8* pDst, const uint8 *pSrc, int num_pixels) { + for ( ; num_pixels; pDst += 3, pSrc += 3, num_pixels--) { + const int r = pSrc[0], g = pSrc[1], b = pSrc[2]; + pDst[0] = static_cast((r * YR + g * YG + b * YB + 32768) >> 16); + pDst[1] = clamp(128 + ((r * CB_R + g * CB_G + b * CB_B + 32768) >> 16)); + pDst[2] = clamp(128 + ((r * CR_R + g * CR_G + b * CR_B + 32768) >> 16)); + } + } + + static void RGB_to_Y(uint8* pDst, const uint8 *pSrc, int num_pixels) { + for ( ; num_pixels; pDst++, pSrc += 3, num_pixels--) { + pDst[0] = static_cast((pSrc[0] * YR + pSrc[1] * YG + pSrc[2] * YB + 32768) >> 16); + } + } + + static void Y_to_YCC(uint8* pDst, const uint8* pSrc, int num_pixels) { + for( ; num_pixels; pDst += 3, pSrc++, num_pixels--) { + pDst[0] = pSrc[0]; + pDst[1] = 128; + pDst[2] = 128; + } + } + + // Forward DCT - DCT derived from jfdctint. + enum { CONST_BITS = 13, ROW_BITS = 2 }; +#define DCT_DESCALE(x, n) (((x) + (((int32)1) << ((n) - 1))) >> (n)) +#define DCT_MUL(var, c) (static_cast(var) * static_cast(c)) +#define DCT1D(s0, s1, s2, s3, s4, s5, s6, s7) \ + int32 t0 = s0 + s7, t7 = s0 - s7, t1 = s1 + s6, t6 = s1 - s6, t2 = s2 + s5, t5 = s2 - s5, t3 = s3 + s4, t4 = s3 - s4; \ + int32 t10 = t0 + t3, t13 = t0 - t3, t11 = t1 + t2, t12 = t1 - t2; \ + int32 u1 = DCT_MUL(t12 + t13, 4433); \ + s2 = u1 + DCT_MUL(t13, 6270); \ + s6 = u1 + DCT_MUL(t12, -15137); \ + u1 = t4 + t7; \ + int32 u2 = t5 + t6, u3 = t4 + t6, u4 = t5 + t7; \ + int32 z5 = DCT_MUL(u3 + u4, 9633); \ + t4 = DCT_MUL(t4, 2446); t5 = DCT_MUL(t5, 16819); \ + t6 = DCT_MUL(t6, 25172); t7 = DCT_MUL(t7, 12299); \ + u1 = DCT_MUL(u1, -7373); u2 = DCT_MUL(u2, -20995); \ + u3 = DCT_MUL(u3, -16069); u4 = DCT_MUL(u4, -3196); \ + u3 += z5; u4 += z5; \ + s0 = t10 + t11; s1 = t7 + u1 + u4; s3 = t6 + u2 + u3; s4 = t10 - t11; s5 = t5 + u2 + u4; s7 = t4 + u1 + u3; + + static void DCT2D(int32 *p) { + int32 c, *q = p; + for (c = 7; c >= 0; c--, q += 8) { + int32 s0 = q[0], s1 = q[1], s2 = q[2], s3 = q[3], s4 = q[4], s5 = q[5], s6 = q[6], s7 = q[7]; + DCT1D(s0, s1, s2, s3, s4, s5, s6, s7); + q[0] = s0 << ROW_BITS; q[1] = DCT_DESCALE(s1, CONST_BITS-ROW_BITS); q[2] = DCT_DESCALE(s2, CONST_BITS-ROW_BITS); q[3] = DCT_DESCALE(s3, CONST_BITS-ROW_BITS); + q[4] = s4 << ROW_BITS; q[5] = DCT_DESCALE(s5, CONST_BITS-ROW_BITS); q[6] = DCT_DESCALE(s6, CONST_BITS-ROW_BITS); q[7] = DCT_DESCALE(s7, CONST_BITS-ROW_BITS); + } + for (q = p, c = 7; c >= 0; c--, q++) { + int32 s0 = q[0*8], s1 = q[1*8], s2 = q[2*8], s3 = q[3*8], s4 = q[4*8], s5 = q[5*8], s6 = q[6*8], s7 = q[7*8]; + DCT1D(s0, s1, s2, s3, s4, s5, s6, s7); + q[0*8] = DCT_DESCALE(s0, ROW_BITS+3); q[1*8] = DCT_DESCALE(s1, CONST_BITS+ROW_BITS+3); q[2*8] = DCT_DESCALE(s2, CONST_BITS+ROW_BITS+3); q[3*8] = DCT_DESCALE(s3, CONST_BITS+ROW_BITS+3); + q[4*8] = DCT_DESCALE(s4, ROW_BITS+3); q[5*8] = DCT_DESCALE(s5, CONST_BITS+ROW_BITS+3); q[6*8] = DCT_DESCALE(s6, CONST_BITS+ROW_BITS+3); q[7*8] = DCT_DESCALE(s7, CONST_BITS+ROW_BITS+3); + } + } + + // Compute the actual canonical Huffman codes/code sizes given the JPEG huff bits and val arrays. + static void compute_huffman_table(uint *codes, uint8 *code_sizes, uint8 *bits, uint8 *val) + { + int i, l, last_p, si; + static uint8 huff_size[257]; + static uint huff_code[257]; + uint code; + + int p = 0; + for (l = 1; l <= 16; l++) { + for (i = 1; i <= bits[l]; i++) { + huff_size[p++] = (char)l; + } + } + + huff_size[p] = 0; + last_p = p; // write sentinel + + code = 0; si = huff_size[0]; p = 0; + + while (huff_size[p]) { + while (huff_size[p] == si) { + huff_code[p++] = code++; + } + code <<= 1; + si++; + } + + memset(codes, 0, sizeof(codes[0])*256); + memset(code_sizes, 0, sizeof(code_sizes[0])*256); + for (p = 0; p < last_p; p++) { + codes[val[p]] = huff_code[p]; + code_sizes[val[p]] = huff_size[p]; + } + } + + void jpeg_encoder::flush_output_buffer() + { + if (m_out_buf_left != JPGE_OUT_BUF_SIZE) { + m_all_stream_writes_succeeded = m_all_stream_writes_succeeded && m_pStream->put_buf(m_out_buf, JPGE_OUT_BUF_SIZE - m_out_buf_left); + } + m_pOut_buf = m_out_buf; + m_out_buf_left = JPGE_OUT_BUF_SIZE; + } + + void jpeg_encoder::emit_byte(uint8 i) + { + *m_pOut_buf++ = i; + if (--m_out_buf_left == 0) { + flush_output_buffer(); + } + } + + void jpeg_encoder::put_bits(uint bits, uint len) + { + uint8 c = 0; + m_bit_buffer |= ((uint32)bits << (24 - (m_bits_in += len))); + while (m_bits_in >= 8) { + c = (uint8)((m_bit_buffer >> 16) & 0xFF); + emit_byte(c); + if (c == 0xFF) { + emit_byte(0); + } + m_bit_buffer <<= 8; + m_bits_in -= 8; + } + } + + void jpeg_encoder::emit_word(uint i) + { + emit_byte(uint8(i >> 8)); emit_byte(uint8(i & 0xFF)); + } + + // JPEG marker generation. + void jpeg_encoder::emit_marker(int marker) + { + emit_byte(uint8(0xFF)); emit_byte(uint8(marker)); + } + + // Emit JFIF marker + void jpeg_encoder::emit_jfif_app0() + { + emit_marker(M_APP0); + emit_word(2 + 4 + 1 + 2 + 1 + 2 + 2 + 1 + 1); + emit_byte(0x4A); emit_byte(0x46); emit_byte(0x49); emit_byte(0x46); /* Identifier: ASCII "JFIF" */ + emit_byte(0); + emit_byte(1); /* Major version */ + emit_byte(1); /* Minor version */ + emit_byte(0); /* Density unit */ + emit_word(1); + emit_word(1); + emit_byte(0); /* No thumbnail image */ + emit_byte(0); + } + + // Emit quantization tables + void jpeg_encoder::emit_dqt() + { + for (int i = 0; i < ((m_num_components == 3) ? 2 : 1); i++) + { + emit_marker(M_DQT); + emit_word(64 + 1 + 2); + emit_byte(static_cast(i)); + for (int j = 0; j < 64; j++) + emit_byte(static_cast(m_quantization_tables[i][j])); + } + } + + // Emit start of frame marker + void jpeg_encoder::emit_sof() + { + emit_marker(M_SOF0); /* baseline */ + emit_word(3 * m_num_components + 2 + 5 + 1); + emit_byte(8); /* precision */ + emit_word(m_image_y); + emit_word(m_image_x); + emit_byte(m_num_components); + for (int i = 0; i < m_num_components; i++) + { + emit_byte(static_cast(i + 1)); /* component ID */ + emit_byte((m_comp_h_samp[i] << 4) + m_comp_v_samp[i]); /* h and v sampling */ + emit_byte(i > 0); /* quant. table num */ + } + } + + // Emit Huffman table. + void jpeg_encoder::emit_dht(uint8 *bits, uint8 *val, int index, bool ac_flag) + { + emit_marker(M_DHT); + + int length = 0; + for (int i = 1; i <= 16; i++) + length += bits[i]; + + emit_word(length + 2 + 1 + 16); + emit_byte(static_cast(index + (ac_flag << 4))); + + for (int i = 1; i <= 16; i++) + emit_byte(bits[i]); + + for (int i = 0; i < length; i++) + emit_byte(val[i]); + } + + // Emit all Huffman tables. + void jpeg_encoder::emit_dhts() + { + emit_dht(m_huff_bits[0+0], m_huff_val[0+0], 0, false); + emit_dht(m_huff_bits[2+0], m_huff_val[2+0], 0, true); + if (m_num_components == 3) { + emit_dht(m_huff_bits[0+1], m_huff_val[0+1], 1, false); + emit_dht(m_huff_bits[2+1], m_huff_val[2+1], 1, true); + } + } + + // emit start of scan + void jpeg_encoder::emit_sos() + { + emit_marker(M_SOS); + emit_word(2 * m_num_components + 2 + 1 + 3); + emit_byte(m_num_components); + for (int i = 0; i < m_num_components; i++) + { + emit_byte(static_cast(i + 1)); + if (i == 0) + emit_byte((0 << 4) + 0); + else + emit_byte((1 << 4) + 1); + } + emit_byte(0); /* spectral selection */ + emit_byte(63); + emit_byte(0); + } + + void jpeg_encoder::load_block_8_8_grey(int x) + { + uint8 *pSrc; + sample_array_t *pDst = m_sample_array; + x <<= 3; + for (int i = 0; i < 8; i++, pDst += 8) + { + pSrc = m_mcu_lines[i] + x; + pDst[0] = pSrc[0] - 128; pDst[1] = pSrc[1] - 128; pDst[2] = pSrc[2] - 128; pDst[3] = pSrc[3] - 128; + pDst[4] = pSrc[4] - 128; pDst[5] = pSrc[5] - 128; pDst[6] = pSrc[6] - 128; pDst[7] = pSrc[7] - 128; + } + } + + void jpeg_encoder::load_block_8_8(int x, int y, int c) + { + uint8 *pSrc; + sample_array_t *pDst = m_sample_array; + x = (x * (8 * 3)) + c; + y <<= 3; + for (int i = 0; i < 8; i++, pDst += 8) + { + pSrc = m_mcu_lines[y + i] + x; + pDst[0] = pSrc[0 * 3] - 128; pDst[1] = pSrc[1 * 3] - 128; pDst[2] = pSrc[2 * 3] - 128; pDst[3] = pSrc[3 * 3] - 128; + pDst[4] = pSrc[4 * 3] - 128; pDst[5] = pSrc[5 * 3] - 128; pDst[6] = pSrc[6 * 3] - 128; pDst[7] = pSrc[7 * 3] - 128; + } + } + + void jpeg_encoder::load_block_16_8(int x, int c) + { + uint8 *pSrc1, *pSrc2; + sample_array_t *pDst = m_sample_array; + x = (x * (16 * 3)) + c; + int a = 0, b = 2; + for (int i = 0; i < 16; i += 2, pDst += 8) + { + pSrc1 = m_mcu_lines[i + 0] + x; + pSrc2 = m_mcu_lines[i + 1] + x; + pDst[0] = ((pSrc1[ 0 * 3] + pSrc1[ 1 * 3] + pSrc2[ 0 * 3] + pSrc2[ 1 * 3] + a) >> 2) - 128; pDst[1] = ((pSrc1[ 2 * 3] + pSrc1[ 3 * 3] + pSrc2[ 2 * 3] + pSrc2[ 3 * 3] + b) >> 2) - 128; + pDst[2] = ((pSrc1[ 4 * 3] + pSrc1[ 5 * 3] + pSrc2[ 4 * 3] + pSrc2[ 5 * 3] + a) >> 2) - 128; pDst[3] = ((pSrc1[ 6 * 3] + pSrc1[ 7 * 3] + pSrc2[ 6 * 3] + pSrc2[ 7 * 3] + b) >> 2) - 128; + pDst[4] = ((pSrc1[ 8 * 3] + pSrc1[ 9 * 3] + pSrc2[ 8 * 3] + pSrc2[ 9 * 3] + a) >> 2) - 128; pDst[5] = ((pSrc1[10 * 3] + pSrc1[11 * 3] + pSrc2[10 * 3] + pSrc2[11 * 3] + b) >> 2) - 128; + pDst[6] = ((pSrc1[12 * 3] + pSrc1[13 * 3] + pSrc2[12 * 3] + pSrc2[13 * 3] + a) >> 2) - 128; pDst[7] = ((pSrc1[14 * 3] + pSrc1[15 * 3] + pSrc2[14 * 3] + pSrc2[15 * 3] + b) >> 2) - 128; + int temp = a; a = b; b = temp; + } + } + + void jpeg_encoder::load_block_16_8_8(int x, int c) + { + uint8 *pSrc1; + sample_array_t *pDst = m_sample_array; + x = (x * (16 * 3)) + c; + for (int i = 0; i < 8; i++, pDst += 8) + { + pSrc1 = m_mcu_lines[i + 0] + x; + pDst[0] = ((pSrc1[ 0 * 3] + pSrc1[ 1 * 3]) >> 1) - 128; pDst[1] = ((pSrc1[ 2 * 3] + pSrc1[ 3 * 3]) >> 1) - 128; + pDst[2] = ((pSrc1[ 4 * 3] + pSrc1[ 5 * 3]) >> 1) - 128; pDst[3] = ((pSrc1[ 6 * 3] + pSrc1[ 7 * 3]) >> 1) - 128; + pDst[4] = ((pSrc1[ 8 * 3] + pSrc1[ 9 * 3]) >> 1) - 128; pDst[5] = ((pSrc1[10 * 3] + pSrc1[11 * 3]) >> 1) - 128; + pDst[6] = ((pSrc1[12 * 3] + pSrc1[13 * 3]) >> 1) - 128; pDst[7] = ((pSrc1[14 * 3] + pSrc1[15 * 3]) >> 1) - 128; + } + } + + void jpeg_encoder::load_quantized_coefficients(int component_num) + { + int32 *q = m_quantization_tables[component_num > 0]; + int16 *pDst = m_coefficient_array; + for (int i = 0; i < 64; i++) + { + sample_array_t j = m_sample_array[s_zag[i]]; + if (j < 0) + { + if ((j = -j + (*q >> 1)) < *q) + *pDst++ = 0; + else + *pDst++ = static_cast(-(j / *q)); + } + else + { + if ((j = j + (*q >> 1)) < *q) + *pDst++ = 0; + else + *pDst++ = static_cast((j / *q)); + } + q++; + } + } + + void jpeg_encoder::code_coefficients_pass_two(int component_num) + { + int i, j, run_len, nbits, temp1, temp2; + int16 *pSrc = m_coefficient_array; + uint *codes[2]; + uint8 *code_sizes[2]; + + if (component_num == 0) + { + codes[0] = m_huff_codes[0 + 0]; codes[1] = m_huff_codes[2 + 0]; + code_sizes[0] = m_huff_code_sizes[0 + 0]; code_sizes[1] = m_huff_code_sizes[2 + 0]; + } + else + { + codes[0] = m_huff_codes[0 + 1]; codes[1] = m_huff_codes[2 + 1]; + code_sizes[0] = m_huff_code_sizes[0 + 1]; code_sizes[1] = m_huff_code_sizes[2 + 1]; + } + + temp1 = temp2 = pSrc[0] - m_last_dc_val[component_num]; + m_last_dc_val[component_num] = pSrc[0]; + + if (temp1 < 0) + { + temp1 = -temp1; temp2--; + } + + nbits = 0; + while (temp1) + { + nbits++; temp1 >>= 1; + } + + put_bits(codes[0][nbits], code_sizes[0][nbits]); + if (nbits) put_bits(temp2 & ((1 << nbits) - 1), nbits); + + for (run_len = 0, i = 1; i < 64; i++) + { + if ((temp1 = m_coefficient_array[i]) == 0) + run_len++; + else + { + while (run_len >= 16) + { + put_bits(codes[1][0xF0], code_sizes[1][0xF0]); + run_len -= 16; + } + if ((temp2 = temp1) < 0) + { + temp1 = -temp1; + temp2--; + } + nbits = 1; + while (temp1 >>= 1) + nbits++; + j = (run_len << 4) + nbits; + put_bits(codes[1][j], code_sizes[1][j]); + put_bits(temp2 & ((1 << nbits) - 1), nbits); + run_len = 0; + } + } + if (run_len) + put_bits(codes[1][0], code_sizes[1][0]); + } + + void jpeg_encoder::code_block(int component_num) + { + DCT2D(m_sample_array); + load_quantized_coefficients(component_num); + code_coefficients_pass_two(component_num); + } + + void jpeg_encoder::process_mcu_row() + { + if (m_num_components == 1) + { + for (int i = 0; i < m_mcus_per_row; i++) + { + load_block_8_8_grey(i); code_block(0); + } + } + else if ((m_comp_h_samp[0] == 1) && (m_comp_v_samp[0] == 1)) + { + for (int i = 0; i < m_mcus_per_row; i++) + { + load_block_8_8(i, 0, 0); code_block(0); load_block_8_8(i, 0, 1); code_block(1); load_block_8_8(i, 0, 2); code_block(2); + } + } + else if ((m_comp_h_samp[0] == 2) && (m_comp_v_samp[0] == 1)) + { + for (int i = 0; i < m_mcus_per_row; i++) + { + load_block_8_8(i * 2 + 0, 0, 0); code_block(0); load_block_8_8(i * 2 + 1, 0, 0); code_block(0); + load_block_16_8_8(i, 1); code_block(1); load_block_16_8_8(i, 2); code_block(2); + } + } + else if ((m_comp_h_samp[0] == 2) && (m_comp_v_samp[0] == 2)) + { + for (int i = 0; i < m_mcus_per_row; i++) + { + load_block_8_8(i * 2 + 0, 0, 0); code_block(0); load_block_8_8(i * 2 + 1, 0, 0); code_block(0); + load_block_8_8(i * 2 + 0, 1, 0); code_block(0); load_block_8_8(i * 2 + 1, 1, 0); code_block(0); + load_block_16_8(i, 1); code_block(1); load_block_16_8(i, 2); code_block(2); + } + } + } + + void jpeg_encoder::load_mcu(const void *pSrc) + { + const uint8* Psrc = reinterpret_cast(pSrc); + + uint8* pDst = m_mcu_lines[m_mcu_y_ofs]; // OK to write up to m_image_bpl_xlt bytes to pDst + + if (m_num_components == 1) { + if (m_image_bpp == 3) + RGB_to_Y(pDst, Psrc, m_image_x); + else + memcpy(pDst, Psrc, m_image_x); + } else { + if (m_image_bpp == 3) + RGB_to_YCC(pDst, Psrc, m_image_x); + else + Y_to_YCC(pDst, Psrc, m_image_x); + } + + // Possibly duplicate pixels at end of scanline if not a multiple of 8 or 16 + if (m_num_components == 1) + memset(m_mcu_lines[m_mcu_y_ofs] + m_image_bpl_xlt, pDst[m_image_bpl_xlt - 1], m_image_x_mcu - m_image_x); + else + { + const uint8 y = pDst[m_image_bpl_xlt - 3 + 0], cb = pDst[m_image_bpl_xlt - 3 + 1], cr = pDst[m_image_bpl_xlt - 3 + 2]; + uint8 *q = m_mcu_lines[m_mcu_y_ofs] + m_image_bpl_xlt; + for (int i = m_image_x; i < m_image_x_mcu; i++) + { + *q++ = y; *q++ = cb; *q++ = cr; + } + } + + if (++m_mcu_y_ofs == m_mcu_y) + { + process_mcu_row(); + m_mcu_y_ofs = 0; + } + } + + // Quantization table generation. + void jpeg_encoder::compute_quant_table(int32 *pDst, const int16 *pSrc) + { + int32 q; + if (m_params.m_quality < 50) + q = 5000 / m_params.m_quality; + else + q = 200 - m_params.m_quality * 2; + for (int i = 0; i < 64; i++) + { + int32 j = *pSrc++; j = (j * q + 50L) / 100L; + *pDst++ = JPGE_MIN(JPGE_MAX(j, 1), 255); + } + } + + // Higher-level methods. + bool jpeg_encoder::jpg_open(int p_x_res, int p_y_res, int src_channels) + { + m_num_components = 3; + switch (m_params.m_subsampling) + { + case Y_ONLY: + { + m_num_components = 1; + m_comp_h_samp[0] = 1; m_comp_v_samp[0] = 1; + m_mcu_x = 8; m_mcu_y = 8; + break; + } + case H1V1: + { + m_comp_h_samp[0] = 1; m_comp_v_samp[0] = 1; + m_comp_h_samp[1] = 1; m_comp_v_samp[1] = 1; + m_comp_h_samp[2] = 1; m_comp_v_samp[2] = 1; + m_mcu_x = 8; m_mcu_y = 8; + break; + } + case H2V1: + { + m_comp_h_samp[0] = 2; m_comp_v_samp[0] = 1; + m_comp_h_samp[1] = 1; m_comp_v_samp[1] = 1; + m_comp_h_samp[2] = 1; m_comp_v_samp[2] = 1; + m_mcu_x = 16; m_mcu_y = 8; + break; + } + case H2V2: + { + m_comp_h_samp[0] = 2; m_comp_v_samp[0] = 2; + m_comp_h_samp[1] = 1; m_comp_v_samp[1] = 1; + m_comp_h_samp[2] = 1; m_comp_v_samp[2] = 1; + m_mcu_x = 16; m_mcu_y = 16; + } + } + + m_image_x = p_x_res; m_image_y = p_y_res; + m_image_bpp = src_channels; + m_image_bpl = m_image_x * src_channels; + m_image_x_mcu = (m_image_x + m_mcu_x - 1) & (~(m_mcu_x - 1)); + m_image_y_mcu = (m_image_y + m_mcu_y - 1) & (~(m_mcu_y - 1)); + m_image_bpl_xlt = m_image_x * m_num_components; + m_image_bpl_mcu = m_image_x_mcu * m_num_components; + m_mcus_per_row = m_image_x_mcu / m_mcu_x; + + if ((m_mcu_lines[0] = static_cast(jpge_malloc(m_image_bpl_mcu * m_mcu_y))) == NULL) { + return false; + } + for (int i = 1; i < m_mcu_y; i++) + m_mcu_lines[i] = m_mcu_lines[i-1] + m_image_bpl_mcu; + + if(m_last_quality != m_params.m_quality){ + m_last_quality = m_params.m_quality; + compute_quant_table(m_quantization_tables[0], s_std_lum_quant); + compute_quant_table(m_quantization_tables[1], s_std_croma_quant); + } + + if(!m_huff_initialized){ + m_huff_initialized = true; + + memcpy(m_huff_bits[0+0], s_dc_lum_bits, 17); memcpy(m_huff_val[0+0], s_dc_lum_val, DC_LUM_CODES); + memcpy(m_huff_bits[2+0], s_ac_lum_bits, 17); memcpy(m_huff_val[2+0], s_ac_lum_val, AC_LUM_CODES); + memcpy(m_huff_bits[0+1], s_dc_chroma_bits, 17); memcpy(m_huff_val[0+1], s_dc_chroma_val, DC_CHROMA_CODES); + memcpy(m_huff_bits[2+1], s_ac_chroma_bits, 17); memcpy(m_huff_val[2+1], s_ac_chroma_val, AC_CHROMA_CODES); + + compute_huffman_table(&m_huff_codes[0+0][0], &m_huff_code_sizes[0+0][0], m_huff_bits[0+0], m_huff_val[0+0]); + compute_huffman_table(&m_huff_codes[2+0][0], &m_huff_code_sizes[2+0][0], m_huff_bits[2+0], m_huff_val[2+0]); + compute_huffman_table(&m_huff_codes[0+1][0], &m_huff_code_sizes[0+1][0], m_huff_bits[0+1], m_huff_val[0+1]); + compute_huffman_table(&m_huff_codes[2+1][0], &m_huff_code_sizes[2+1][0], m_huff_bits[2+1], m_huff_val[2+1]); + } + + m_out_buf_left = JPGE_OUT_BUF_SIZE; + m_pOut_buf = m_out_buf; + m_bit_buffer = 0; + m_bits_in = 0; + m_mcu_y_ofs = 0; + m_pass_num = 2; + memset(m_last_dc_val, 0, 3 * sizeof(m_last_dc_val[0])); + + // Emit all markers at beginning of image file. + emit_marker(M_SOI); + emit_jfif_app0(); + emit_dqt(); + emit_sof(); + emit_dhts(); + emit_sos(); + + return m_all_stream_writes_succeeded; + } + + bool jpeg_encoder::process_end_of_image() + { + if (m_mcu_y_ofs) { + if (m_mcu_y_ofs < 16) { // check here just to shut up static analysis + for (int i = m_mcu_y_ofs; i < m_mcu_y; i++) { + memcpy(m_mcu_lines[i], m_mcu_lines[m_mcu_y_ofs - 1], m_image_bpl_mcu); + } + } + process_mcu_row(); + } + + put_bits(0x7F, 7); + emit_marker(M_EOI); + flush_output_buffer(); + m_all_stream_writes_succeeded = m_all_stream_writes_succeeded && m_pStream->put_buf(NULL, 0); + m_pass_num++; // purposely bump up m_pass_num, for debugging + return true; + } + + void jpeg_encoder::clear() + { + m_mcu_lines[0] = NULL; + m_pass_num = 0; + m_all_stream_writes_succeeded = true; + } + + jpeg_encoder::jpeg_encoder() + { + clear(); + } + + jpeg_encoder::~jpeg_encoder() + { + deinit(); + } + + bool jpeg_encoder::init(output_stream *pStream, int width, int height, int src_channels, const params &comp_params) + { + deinit(); + if (((!pStream) || (width < 1) || (height < 1)) || ((src_channels != 1) && (src_channels != 3) && (src_channels != 4)) || (!comp_params.check())) return false; + m_pStream = pStream; + m_params = comp_params; + return jpg_open(width, height, src_channels); + } + + void jpeg_encoder::deinit() + { + jpge_free(m_mcu_lines[0]); + clear(); + } + + bool jpeg_encoder::process_scanline(const void* pScanline) + { + if ((m_pass_num < 1) || (m_pass_num > 2)) { + return false; + } + if (m_all_stream_writes_succeeded) { + if (!pScanline) { + if (!process_end_of_image()) { + return false; + } + } else { + load_mcu(pScanline); + } + } + return m_all_stream_writes_succeeded; + } + +} // namespace jpge diff --git a/esp32-cam-rtos-allframes/jpge.h b/esp32-cam-rtos-allframes/jpge.h new file mode 100644 index 0000000..aa295c8 --- /dev/null +++ b/esp32-cam-rtos-allframes/jpge.h @@ -0,0 +1,142 @@ +// jpge.h - C++ class for JPEG compression. +// Public domain, Rich Geldreich +// Alex Evans: Added RGBA support, linear memory allocator. +#ifndef JPEG_ENCODER_H +#define JPEG_ENCODER_H + +namespace jpge +{ + typedef unsigned char uint8; + typedef signed short int16; + typedef signed int int32; + typedef unsigned short uint16; + typedef unsigned int uint32; + typedef unsigned int uint; + + // JPEG chroma subsampling factors. Y_ONLY (grayscale images) and H2V2 (color images) are the most common. + enum subsampling_t { Y_ONLY = 0, H1V1 = 1, H2V1 = 2, H2V2 = 3 }; + + // JPEG compression parameters structure. + struct params { + inline params() : m_quality(85), m_subsampling(H2V2) { } + + inline bool check() const { + if ((m_quality < 1) || (m_quality > 100)) { + return false; + } + if ((uint)m_subsampling > (uint)H2V2) { + return false; + } + return true; + } + + // Quality: 1-100, higher is better. Typical values are around 50-95. + int m_quality; + + // m_subsampling: + // 0 = Y (grayscale) only + // 1 = H1V1 subsampling (YCbCr 1x1x1, 3 blocks per MCU) + // 2 = H2V1 subsampling (YCbCr 2x1x1, 4 blocks per MCU) + // 3 = H2V2 subsampling (YCbCr 4x1x1, 6 blocks per MCU-- very common) + subsampling_t m_subsampling; + }; + + // Output stream abstract class - used by the jpeg_encoder class to write to the output stream. + // put_buf() is generally called with len==JPGE_OUT_BUF_SIZE bytes, but for headers it'll be called with smaller amounts. + class output_stream { + public: + virtual ~output_stream() { }; + virtual bool put_buf(const void* Pbuf, int len) = 0; + virtual uint get_size() const = 0; + }; + + // Lower level jpeg_encoder class - useful if more control is needed than the above helper functions. + class jpeg_encoder { + public: + jpeg_encoder(); + ~jpeg_encoder(); + + // Initializes the compressor. + // pStream: The stream object to use for writing compressed data. + // params - Compression parameters structure, defined above. + // width, height - Image dimensions. + // channels - May be 1, or 3. 1 indicates grayscale, 3 indicates RGB source data. + // Returns false on out of memory or if a stream write fails. + bool init(output_stream *pStream, int width, int height, int src_channels, const params &comp_params = params()); + + // Call this method with each source scanline. + // width * src_channels bytes per scanline is expected (RGB or Y format). + // You must call with NULL after all scanlines are processed to finish compression. + // Returns false on out of memory or if a stream write fails. + bool process_scanline(const void* pScanline); + + // Deinitializes the compressor, freeing any allocated memory. May be called at any time. + void deinit(); + + private: + jpeg_encoder(const jpeg_encoder &); + jpeg_encoder &operator =(const jpeg_encoder &); + + typedef int32 sample_array_t; + enum { JPGE_OUT_BUF_SIZE = 512 }; + + output_stream *m_pStream; + params m_params; + uint8 m_num_components; + uint8 m_comp_h_samp[3], m_comp_v_samp[3]; + int m_image_x, m_image_y, m_image_bpp, m_image_bpl; + int m_image_x_mcu, m_image_y_mcu; + int m_image_bpl_xlt, m_image_bpl_mcu; + int m_mcus_per_row; + int m_mcu_x, m_mcu_y; + uint8 *m_mcu_lines[16]; + uint8 m_mcu_y_ofs; + sample_array_t m_sample_array[64]; + int16 m_coefficient_array[64]; + + int m_last_dc_val[3]; + uint8 m_out_buf[JPGE_OUT_BUF_SIZE]; + uint8 *m_pOut_buf; + uint m_out_buf_left; + uint32 m_bit_buffer; + uint m_bits_in; + uint8 m_pass_num; + bool m_all_stream_writes_succeeded; + + bool jpg_open(int p_x_res, int p_y_res, int src_channels); + + void flush_output_buffer(); + void put_bits(uint bits, uint len); + + void emit_byte(uint8 i); + void emit_word(uint i); + void emit_marker(int marker); + + void emit_jfif_app0(); + void emit_dqt(); + void emit_sof(); + void emit_dht(uint8 *bits, uint8 *val, int index, bool ac_flag); + void emit_dhts(); + void emit_sos(); + + void compute_quant_table(int32 *dst, const int16 *src); + void load_quantized_coefficients(int component_num); + + void load_block_8_8_grey(int x); + void load_block_8_8(int x, int y, int c); + void load_block_16_8(int x, int c); + void load_block_16_8_8(int x, int c); + + void code_coefficients_pass_two(int component_num); + void code_block(int component_num); + + void process_mcu_row(); + bool process_end_of_image(); + void load_mcu(const void* src); + void clear(); + void init(); + }; + +} // namespace jpge + +#endif // JPEG_ENCODER diff --git a/esp32-cam-rtos-allframes/ov2640.c b/esp32-cam-rtos-allframes/ov2640.c new file mode 100644 index 0000000..811023c --- /dev/null +++ b/esp32-cam-rtos-allframes/ov2640.c @@ -0,0 +1,580 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * OV2640 driver. + * + */ +#include +#include +#include +#include "sccb.h" +#include "ov2640.h" +#include "ov2640_regs.h" +#include "ov2640_settings.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#else +#include "esp_log.h" +static const char* TAG = "ov2640"; +#endif + +static volatile ov2640_bank_t reg_bank = BANK_MAX; +static int set_bank(sensor_t *sensor, ov2640_bank_t bank) +{ + int res = 0; + if (bank != reg_bank) { + reg_bank = bank; + res = SCCB_Write(sensor->slv_addr, BANK_SEL, bank); + } + return res; +} + +static int write_regs(sensor_t *sensor, const uint8_t (*regs)[2]) +{ + int i=0, res = 0; + while (regs[i][0]) { + if (regs[i][0] == BANK_SEL) { + res = set_bank(sensor, regs[i][1]); + } else { + res = SCCB_Write(sensor->slv_addr, regs[i][0], regs[i][1]); + } + if (res) { + return res; + } + i++; + } + return res; +} + +static int write_reg(sensor_t *sensor, ov2640_bank_t bank, uint8_t reg, uint8_t value) +{ + int ret = set_bank(sensor, bank); + if(!ret) { + ret = SCCB_Write(sensor->slv_addr, reg, value); + } + return ret; +} + +static int set_reg_bits(sensor_t *sensor, uint8_t bank, uint8_t reg, uint8_t offset, uint8_t mask, uint8_t value) +{ + int ret = 0; + uint8_t c_value, new_value; + + ret = set_bank(sensor, bank); + if(ret) { + return ret; + } + c_value = SCCB_Read(sensor->slv_addr, reg); + new_value = (c_value & ~(mask << offset)) | ((value & mask) << offset); + ret = SCCB_Write(sensor->slv_addr, reg, new_value); + return ret; +} + +static int read_reg(sensor_t *sensor, ov2640_bank_t bank, uint8_t reg) +{ + if(set_bank(sensor, bank)){ + return 0; + } + return SCCB_Read(sensor->slv_addr, reg); +} + +static uint8_t get_reg_bits(sensor_t *sensor, uint8_t bank, uint8_t reg, uint8_t offset, uint8_t mask) +{ + return (read_reg(sensor, bank, reg) >> offset) & mask; +} + +static int write_reg_bits(sensor_t *sensor, uint8_t bank, uint8_t reg, uint8_t mask, int enable) +{ + return set_reg_bits(sensor, bank, reg, 0, mask, enable?mask:0); +} + +#define WRITE_REGS_OR_RETURN(regs) ret = write_regs(sensor, regs); if(ret){return ret;} +#define WRITE_REG_OR_RETURN(bank, reg, val) ret = write_reg(sensor, bank, reg, val); if(ret){return ret;} +#define SET_REG_BITS_OR_RETURN(bank, reg, offset, mask, val) ret = set_reg_bits(sensor, bank, reg, offset, mask, val); if(ret){return ret;} + +static int reset(sensor_t *sensor) +{ + int ret = 0; + WRITE_REG_OR_RETURN(BANK_SENSOR, COM7, COM7_SRST); + vTaskDelay(10 / portTICK_PERIOD_MS); + WRITE_REGS_OR_RETURN(ov2640_settings_cif); + return ret; +} + +static int set_pixformat(sensor_t *sensor, pixformat_t pixformat) +{ + int ret = 0; + sensor->pixformat = pixformat; + switch (pixformat) { + case PIXFORMAT_RGB565: + case PIXFORMAT_RGB888: + WRITE_REGS_OR_RETURN(ov2640_settings_rgb565); + break; + case PIXFORMAT_YUV422: + case PIXFORMAT_GRAYSCALE: + WRITE_REGS_OR_RETURN(ov2640_settings_yuv422); + break; + case PIXFORMAT_JPEG: + WRITE_REGS_OR_RETURN(ov2640_settings_jpeg3); + break; + default: + ret = -1; + break; + } + if(!ret) { + vTaskDelay(10 / portTICK_PERIOD_MS); + } + + return ret; +} + +static int set_window(sensor_t *sensor, ov2640_sensor_mode_t mode, int offset_x, int offset_y, int max_x, int max_y, int w, int h){ + int ret = 0; + const uint8_t (*regs)[2]; + ov2640_clk_t c; + c.reserved = 0; + + max_x /= 4; + max_y /= 4; + w /= 4; + h /= 4; + uint8_t win_regs[][2] = { + {BANK_SEL, BANK_DSP}, + {HSIZE, max_x & 0xFF}, + {VSIZE, max_y & 0xFF}, + {XOFFL, offset_x & 0xFF}, + {YOFFL, offset_y & 0xFF}, + {VHYX, ((max_y >> 1) & 0X80) | ((offset_y >> 4) & 0X70) | ((max_x >> 5) & 0X08) | ((offset_y >> 8) & 0X07)}, + {TEST, (max_x >> 2) & 0X80}, + {ZMOW, (w)&0xFF}, + {ZMOH, (h)&0xFF}, + {ZMHH, ((h>>6)&0x04)|((w>>8)&0x03)}, + {0, 0} + }; + + c.pclk_auto = 0; + c.pclk_div = 8; + c.clk_2x = 0; + c.clk_div = 0; + + if(sensor->pixformat != PIXFORMAT_JPEG){ + c.pclk_auto = 1; + c.clk_div = 7; + } + + if (mode == OV2640_MODE_CIF) { + regs = ov2640_settings_to_cif; + if(sensor->pixformat != PIXFORMAT_JPEG){ + c.clk_div = 3; + } + } else if (mode == OV2640_MODE_SVGA) { + regs = ov2640_settings_to_svga; + } else { + regs = ov2640_settings_to_uxga; + c.pclk_div = 12; + } + + WRITE_REG_OR_RETURN(BANK_DSP, R_BYPASS, R_BYPASS_DSP_BYPAS); + WRITE_REGS_OR_RETURN(regs); + WRITE_REGS_OR_RETURN(win_regs); + WRITE_REG_OR_RETURN(BANK_SENSOR, CLKRC, c.clk); + WRITE_REG_OR_RETURN(BANK_DSP, R_DVP_SP, c.pclk); + WRITE_REG_OR_RETURN(BANK_DSP, R_BYPASS, R_BYPASS_DSP_EN); + + vTaskDelay(10 / portTICK_PERIOD_MS); + //required when changing resolution + set_pixformat(sensor, sensor->pixformat); + + return ret; +} + +static int set_framesize(sensor_t *sensor, framesize_t framesize) +{ + int ret = 0; + uint16_t w = resolution[framesize].width; + uint16_t h = resolution[framesize].height; + aspect_ratio_t ratio = resolution[framesize].aspect_ratio; + uint16_t max_x = ratio_table[ratio].max_x; + uint16_t max_y = ratio_table[ratio].max_y; + uint16_t offset_x = ratio_table[ratio].offset_x; + uint16_t offset_y = ratio_table[ratio].offset_y; + ov2640_sensor_mode_t mode = OV2640_MODE_UXGA; + + sensor->status.framesize = framesize; + + + + if (framesize <= FRAMESIZE_CIF) { + mode = OV2640_MODE_CIF; + max_x /= 4; + max_y /= 4; + offset_x /= 4; + offset_y /= 4; + if(max_y > 296){ + max_y = 296; + } + } else if (framesize <= FRAMESIZE_SVGA) { + mode = OV2640_MODE_SVGA; + max_x /= 2; + max_y /= 2; + offset_x /= 2; + offset_y /= 2; + } + + ret = set_window(sensor, mode, offset_x, offset_y, max_x, max_y, w, h); + return ret; +} + +static int set_contrast(sensor_t *sensor, int level) +{ + int ret=0; + level += 3; + if (level <= 0 || level > NUM_CONTRAST_LEVELS) { + return -1; + } + sensor->status.contrast = level-3; + for (int i=0; i<7; i++) { + WRITE_REG_OR_RETURN(BANK_DSP, contrast_regs[0][i], contrast_regs[level][i]); + } + return ret; +} + +static int set_brightness(sensor_t *sensor, int level) +{ + int ret=0; + level += 3; + if (level <= 0 || level > NUM_BRIGHTNESS_LEVELS) { + return -1; + } + sensor->status.brightness = level-3; + for (int i=0; i<5; i++) { + WRITE_REG_OR_RETURN(BANK_DSP, brightness_regs[0][i], brightness_regs[level][i]); + } + return ret; +} + +static int set_saturation(sensor_t *sensor, int level) +{ + int ret=0; + level += 3; + if (level <= 0 || level > NUM_SATURATION_LEVELS) { + return -1; + } + sensor->status.saturation = level-3; + for (int i=0; i<5; i++) { + WRITE_REG_OR_RETURN(BANK_DSP, saturation_regs[0][i], saturation_regs[level][i]); + } + return ret; +} + +static int set_special_effect(sensor_t *sensor, int effect) +{ + int ret=0; + effect++; + if (effect <= 0 || effect > NUM_SPECIAL_EFFECTS) { + return -1; + } + sensor->status.special_effect = effect-1; + for (int i=0; i<5; i++) { + WRITE_REG_OR_RETURN(BANK_DSP, special_effects_regs[0][i], special_effects_regs[effect][i]); + } + return ret; +} + +static int set_wb_mode(sensor_t *sensor, int mode) +{ + int ret=0; + if (mode < 0 || mode > NUM_WB_MODES) { + return -1; + } + sensor->status.wb_mode = mode; + SET_REG_BITS_OR_RETURN(BANK_DSP, 0XC7, 6, 1, mode?1:0); + if(mode) { + for (int i=0; i<3; i++) { + WRITE_REG_OR_RETURN(BANK_DSP, wb_modes_regs[0][i], wb_modes_regs[mode][i]); + } + } + return ret; +} + +static int set_ae_level(sensor_t *sensor, int level) +{ + int ret=0; + level += 3; + if (level <= 0 || level > NUM_AE_LEVELS) { + return -1; + } + sensor->status.ae_level = level-3; + for (int i=0; i<3; i++) { + WRITE_REG_OR_RETURN(BANK_SENSOR, ae_levels_regs[0][i], ae_levels_regs[level][i]); + } + return ret; +} + +static int set_quality(sensor_t *sensor, int quality) +{ + if(quality < 0) { + quality = 0; + } else if(quality > 63) { + quality = 63; + } + sensor->status.quality = quality; + return write_reg(sensor, BANK_DSP, QS, quality); +} + +static int set_agc_gain(sensor_t *sensor, int gain) +{ + if(gain < 0) { + gain = 0; + } else if(gain > 30) { + gain = 30; + } + sensor->status.agc_gain = gain; + return write_reg(sensor, BANK_SENSOR, GAIN, agc_gain_tbl[gain]); +} + +static int set_gainceiling_sensor(sensor_t *sensor, gainceiling_t gainceiling) +{ + sensor->status.gainceiling = gainceiling; + //return write_reg(sensor, BANK_SENSOR, COM9, COM9_AGC_SET(gainceiling)); + return set_reg_bits(sensor, BANK_SENSOR, COM9, 5, 7, gainceiling); +} + +static int set_aec_value(sensor_t *sensor, int value) +{ + if(value < 0) { + value = 0; + } else if(value > 1200) { + value = 1200; + } + sensor->status.aec_value = value; + return set_reg_bits(sensor, BANK_SENSOR, REG04, 0, 3, value & 0x3) + || write_reg(sensor, BANK_SENSOR, AEC, (value >> 2) & 0xFF) + || set_reg_bits(sensor, BANK_SENSOR, REG45, 0, 0x3F, value >> 10); +} + +static int set_aec2(sensor_t *sensor, int enable) +{ + sensor->status.aec2 = enable; + return set_reg_bits(sensor, BANK_DSP, CTRL0, 6, 1, enable?0:1); +} + +static int set_colorbar(sensor_t *sensor, int enable) +{ + sensor->status.colorbar = enable; + return write_reg_bits(sensor, BANK_SENSOR, COM7, COM7_COLOR_BAR, enable?1:0); +} + +static int set_agc_sensor(sensor_t *sensor, int enable) +{ + sensor->status.agc = enable; + return write_reg_bits(sensor, BANK_SENSOR, COM8, COM8_AGC_EN, enable?1:0); +} + +static int set_aec_sensor(sensor_t *sensor, int enable) +{ + sensor->status.aec = enable; + return write_reg_bits(sensor, BANK_SENSOR, COM8, COM8_AEC_EN, enable?1:0); +} + +static int set_hmirror_sensor(sensor_t *sensor, int enable) +{ + sensor->status.hmirror = enable; + return write_reg_bits(sensor, BANK_SENSOR, REG04, REG04_HFLIP_IMG, enable?1:0); +} + +static int set_vflip_sensor(sensor_t *sensor, int enable) +{ + int ret = 0; + sensor->status.vflip = enable; + ret = write_reg_bits(sensor, BANK_SENSOR, REG04, REG04_VREF_EN, enable?1:0); + return ret & write_reg_bits(sensor, BANK_SENSOR, REG04, REG04_VFLIP_IMG, enable?1:0); +} + +static int set_raw_gma_dsp(sensor_t *sensor, int enable) +{ + sensor->status.raw_gma = enable; + return set_reg_bits(sensor, BANK_DSP, CTRL1, 5, 1, enable?1:0); +} + +static int set_awb_dsp(sensor_t *sensor, int enable) +{ + sensor->status.awb = enable; + return set_reg_bits(sensor, BANK_DSP, CTRL1, 3, 1, enable?1:0); +} + +static int set_awb_gain_dsp(sensor_t *sensor, int enable) +{ + sensor->status.awb_gain = enable; + return set_reg_bits(sensor, BANK_DSP, CTRL1, 2, 1, enable?1:0); +} + +static int set_lenc_dsp(sensor_t *sensor, int enable) +{ + sensor->status.lenc = enable; + return set_reg_bits(sensor, BANK_DSP, CTRL1, 1, 1, enable?1:0); +} + +static int set_dcw_dsp(sensor_t *sensor, int enable) +{ + sensor->status.dcw = enable; + return set_reg_bits(sensor, BANK_DSP, CTRL2, 5, 1, enable?1:0); +} + +static int set_bpc_dsp(sensor_t *sensor, int enable) +{ + sensor->status.bpc = enable; + return set_reg_bits(sensor, BANK_DSP, CTRL3, 7, 1, enable?1:0); +} + +static int set_wpc_dsp(sensor_t *sensor, int enable) +{ + sensor->status.wpc = enable; + return set_reg_bits(sensor, BANK_DSP, CTRL3, 6, 1, enable?1:0); +} + +//unsupported +static int set_sharpness(sensor_t *sensor, int level) +{ + return -1; +} + +static int set_denoise(sensor_t *sensor, int level) +{ + return -1; +} + +static int get_reg(sensor_t *sensor, int reg, int mask) +{ + int ret = read_reg(sensor, (reg >> 8) & 0x01, reg & 0xFF); + if(ret > 0){ + ret &= mask; + } + return ret; +} + +static int set_reg(sensor_t *sensor, int reg, int mask, int value) +{ + int ret = 0; + ret = read_reg(sensor, (reg >> 8) & 0x01, reg & 0xFF); + if(ret < 0){ + return ret; + } + value = (ret & ~mask) | (value & mask); + ret = write_reg(sensor, (reg >> 8) & 0x01, reg & 0xFF, value); + return ret; +} + +static int set_res_raw(sensor_t *sensor, int startX, int startY, int endX, int endY, int offsetX, int offsetY, int totalX, int totalY, int outputX, int outputY, bool scale, bool binning) +{ + return set_window(sensor, (ov2640_sensor_mode_t)startX, offsetX, offsetY, totalX, totalY, outputX, outputY); +} + +static int _set_pll(sensor_t *sensor, int bypass, int multiplier, int sys_div, int root_2x, int pre_div, int seld5, int pclk_manual, int pclk_div) +{ + return -1; +} + +esp_err_t xclk_timer_conf(int ledc_timer, int xclk_freq_hz); +static int set_xclk(sensor_t *sensor, int timer, int xclk) +{ + int ret = 0; + sensor->xclk_freq_hz = xclk * 1000000U; + ret = xclk_timer_conf(timer, sensor->xclk_freq_hz); + return ret; +} + +static int init_status(sensor_t *sensor){ + sensor->status.brightness = 0; + sensor->status.contrast = 0; + sensor->status.saturation = 0; + sensor->status.ae_level = 0; + sensor->status.special_effect = 0; + sensor->status.wb_mode = 0; + + sensor->status.agc_gain = 30; + int agc_gain = read_reg(sensor, BANK_SENSOR, GAIN); + for (int i=0; i<30; i++){ + if(agc_gain >= agc_gain_tbl[i] && agc_gain < agc_gain_tbl[i+1]){ + sensor->status.agc_gain = i; + break; + } + } + + sensor->status.aec_value = ((uint16_t)get_reg_bits(sensor, BANK_SENSOR, REG45, 0, 0x3F) << 10) + | ((uint16_t)read_reg(sensor, BANK_SENSOR, AEC) << 2) + | get_reg_bits(sensor, BANK_SENSOR, REG04, 0, 3);//0 - 1200 + sensor->status.quality = read_reg(sensor, BANK_DSP, QS); + sensor->status.gainceiling = get_reg_bits(sensor, BANK_SENSOR, COM9, 5, 7); + + sensor->status.awb = get_reg_bits(sensor, BANK_DSP, CTRL1, 3, 1); + sensor->status.awb_gain = get_reg_bits(sensor, BANK_DSP, CTRL1, 2, 1); + sensor->status.aec = get_reg_bits(sensor, BANK_SENSOR, COM8, 0, 1); + sensor->status.aec2 = get_reg_bits(sensor, BANK_DSP, CTRL0, 6, 1); + sensor->status.agc = get_reg_bits(sensor, BANK_SENSOR, COM8, 2, 1); + sensor->status.bpc = get_reg_bits(sensor, BANK_DSP, CTRL3, 7, 1); + sensor->status.wpc = get_reg_bits(sensor, BANK_DSP, CTRL3, 6, 1); + sensor->status.raw_gma = get_reg_bits(sensor, BANK_DSP, CTRL1, 5, 1); + sensor->status.lenc = get_reg_bits(sensor, BANK_DSP, CTRL1, 1, 1); + sensor->status.hmirror = get_reg_bits(sensor, BANK_SENSOR, REG04, 7, 1); + sensor->status.vflip = get_reg_bits(sensor, BANK_SENSOR, REG04, 6, 1); + sensor->status.dcw = get_reg_bits(sensor, BANK_DSP, CTRL2, 5, 1); + sensor->status.colorbar = get_reg_bits(sensor, BANK_SENSOR, COM7, 1, 1); + + sensor->status.sharpness = 0;//not supported + sensor->status.denoise = 0; + return 0; +} + +int ov2640_init(sensor_t *sensor) +{ + sensor->reset = reset; + sensor->init_status = init_status; + sensor->set_pixformat = set_pixformat; + sensor->set_framesize = set_framesize; + sensor->set_contrast = set_contrast; + sensor->set_brightness= set_brightness; + sensor->set_saturation= set_saturation; + + sensor->set_quality = set_quality; + sensor->set_colorbar = set_colorbar; + + sensor->set_gainceiling = set_gainceiling_sensor; + sensor->set_gain_ctrl = set_agc_sensor; + sensor->set_exposure_ctrl = set_aec_sensor; + sensor->set_hmirror = set_hmirror_sensor; + sensor->set_vflip = set_vflip_sensor; + + sensor->set_whitebal = set_awb_dsp; + sensor->set_aec2 = set_aec2; + sensor->set_aec_value = set_aec_value; + sensor->set_special_effect = set_special_effect; + sensor->set_wb_mode = set_wb_mode; + sensor->set_ae_level = set_ae_level; + + sensor->set_dcw = set_dcw_dsp; + sensor->set_bpc = set_bpc_dsp; + sensor->set_wpc = set_wpc_dsp; + sensor->set_awb_gain = set_awb_gain_dsp; + sensor->set_agc_gain = set_agc_gain; + + sensor->set_raw_gma = set_raw_gma_dsp; + sensor->set_lenc = set_lenc_dsp; + + //not supported + sensor->set_sharpness = set_sharpness; + sensor->set_denoise = set_denoise; + + sensor->get_reg = get_reg; + sensor->set_reg = set_reg; + sensor->set_res_raw = set_res_raw; + sensor->set_pll = _set_pll; + sensor->set_xclk = set_xclk; + ESP_LOGD(TAG, "OV2640 Attached"); + return 0; +} diff --git a/esp32-cam-rtos-allframes/ov2640.h b/esp32-cam-rtos-allframes/ov2640.h new file mode 100644 index 0000000..a890499 --- /dev/null +++ b/esp32-cam-rtos-allframes/ov2640.h @@ -0,0 +1,13 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * OV2640 driver. + * + */ +#ifndef __OV2640_H__ +#define __OV2640_H__ +#include "sensor.h" +int ov2640_init(sensor_t *sensor); +#endif // __OV2640_H__ diff --git a/esp32-cam-rtos-allframes/ov2640_regs.h b/esp32-cam-rtos-allframes/ov2640_regs.h new file mode 100644 index 0000000..eb096b4 --- /dev/null +++ b/esp32-cam-rtos-allframes/ov2640_regs.h @@ -0,0 +1,216 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * OV2640 register definitions. + */ +#ifndef __REG_REGS_H__ +#define __REG_REGS_H__ +/* DSP register bank FF=0x00*/ +#define R_BYPASS 0x05 +#define QS 0x44 +#define CTRLI 0x50 +#define HSIZE 0x51 +#define VSIZE 0x52 +#define XOFFL 0x53 +#define YOFFL 0x54 +#define VHYX 0x55 +#define DPRP 0x56 +#define TEST 0x57 +#define ZMOW 0x5A +#define ZMOH 0x5B +#define ZMHH 0x5C +#define BPADDR 0x7C +#define BPDATA 0x7D +#define CTRL2 0x86 +#define CTRL3 0x87 +#define SIZEL 0x8C +#define HSIZE8 0xC0 +#define VSIZE8 0xC1 +#define CTRL0 0xC2 +#define CTRL1 0xC3 +#define R_DVP_SP 0xD3 +#define IMAGE_MODE 0xDA +#define RESET 0xE0 +#define MS_SP 0xF0 +#define SS_ID 0xF7 +#define SS_CTRL 0xF7 +#define MC_BIST 0xF9 +#define MC_AL 0xFA +#define MC_AH 0xFB +#define MC_D 0xFC +#define P_CMD 0xFD +#define P_STATUS 0xFE +#define BANK_SEL 0xFF + +#define CTRLI_LP_DP 0x80 +#define CTRLI_ROUND 0x40 + +#define CTRL0_AEC_EN 0x80 +#define CTRL0_AEC_SEL 0x40 +#define CTRL0_STAT_SEL 0x20 +#define CTRL0_VFIRST 0x10 +#define CTRL0_YUV422 0x08 +#define CTRL0_YUV_EN 0x04 +#define CTRL0_RGB_EN 0x02 +#define CTRL0_RAW_EN 0x01 + +#define CTRL2_DCW_EN 0x20 +#define CTRL2_SDE_EN 0x10 +#define CTRL2_UV_ADJ_EN 0x08 +#define CTRL2_UV_AVG_EN 0x04 +#define CTRL2_CMX_EN 0x01 + +#define CTRL3_BPC_EN 0x80 +#define CTRL3_WPC_EN 0x40 + +#define R_DVP_SP_AUTO_MODE 0x80 + +#define R_BYPASS_DSP_EN 0x00 +#define R_BYPASS_DSP_BYPAS 0x01 + +#define IMAGE_MODE_Y8_DVP_EN 0x40 +#define IMAGE_MODE_JPEG_EN 0x10 +#define IMAGE_MODE_YUV422 0x00 +#define IMAGE_MODE_RAW10 0x04 +#define IMAGE_MODE_RGB565 0x08 +#define IMAGE_MODE_HREF_VSYNC 0x02 +#define IMAGE_MODE_LBYTE_FIRST 0x01 + +#define RESET_MICROC 0x40 +#define RESET_SCCB 0x20 +#define RESET_JPEG 0x10 +#define RESET_DVP 0x04 +#define RESET_IPU 0x02 +#define RESET_CIF 0x01 + +#define MC_BIST_RESET 0x80 +#define MC_BIST_BOOT_ROM_SEL 0x40 +#define MC_BIST_12KB_SEL 0x20 +#define MC_BIST_12KB_MASK 0x30 +#define MC_BIST_512KB_SEL 0x08 +#define MC_BIST_512KB_MASK 0x0C +#define MC_BIST_BUSY_BIT_R 0x02 +#define MC_BIST_MC_RES_ONE_SH_W 0x02 +#define MC_BIST_LAUNCH 0x01 + + +typedef enum { + BANK_DSP, BANK_SENSOR, BANK_MAX +} ov2640_bank_t; + +/* Sensor register bank FF=0x01*/ +#define GAIN 0x00 +#define COM1 0x03 +#define REG04 0x04 +#define REG08 0x08 +#define COM2 0x09 +#define REG_PID 0x0A +#define REG_VER 0x0B +#define COM3 0x0C +#define COM4 0x0D +#define AEC 0x10 +#define CLKRC 0x11 +#define COM7 0x12 +#define COM8 0x13 +#define COM9 0x14 /* AGC gain ceiling */ +#define COM10 0x15 +#define HSTART 0x17 +#define HSTOP 0x18 +#define VSTART 0x19 +#define VSTOP 0x1A +#define MIDH 0x1C +#define MIDL 0x1D +#define AEW 0x24 +#define AEB 0x25 +#define VV 0x26 +#define REG2A 0x2A +#define FRARL 0x2B +#define ADDVSL 0x2D +#define ADDVSH 0x2E +#define YAVG 0x2F +#define HSDY 0x30 +#define HEDY 0x31 +#define REG32 0x32 +#define ARCOM2 0x34 +#define REG45 0x45 +#define FLL 0x46 +#define FLH 0x47 +#define COM19 0x48 +#define ZOOMS 0x49 +#define COM22 0x4B +#define COM25 0x4E +#define BD50 0x4F +#define BD60 0x50 +#define REG5D 0x5D +#define REG5E 0x5E +#define REG5F 0x5F +#define REG60 0x60 +#define HISTO_LOW 0x61 +#define HISTO_HIGH 0x62 + +#define REG04_DEFAULT 0x28 +#define REG04_HFLIP_IMG 0x80 +#define REG04_VFLIP_IMG 0x40 +#define REG04_VREF_EN 0x10 +#define REG04_HREF_EN 0x08 +#define REG04_SET(x) (REG04_DEFAULT|x) + +#define COM2_STDBY 0x10 +#define COM2_OUT_DRIVE_1x 0x00 +#define COM2_OUT_DRIVE_2x 0x01 +#define COM2_OUT_DRIVE_3x 0x02 +#define COM2_OUT_DRIVE_4x 0x03 + +#define COM3_DEFAULT 0x38 +#define COM3_BAND_50Hz 0x04 +#define COM3_BAND_60Hz 0x00 +#define COM3_BAND_AUTO 0x02 +#define COM3_BAND_SET(x) (COM3_DEFAULT|x) + +#define COM7_SRST 0x80 +#define COM7_RES_UXGA 0x00 /* UXGA */ +#define COM7_RES_SVGA 0x40 /* SVGA */ +#define COM7_RES_CIF 0x20 /* CIF */ +#define COM7_ZOOM_EN 0x04 /* Enable Zoom */ +#define COM7_COLOR_BAR 0x02 /* Enable Color Bar Test */ + +#define COM8_DEFAULT 0xC0 +#define COM8_BNDF_EN 0x20 /* Enable Banding filter */ +#define COM8_AGC_EN 0x04 /* AGC Auto/Manual control selection */ +#define COM8_AEC_EN 0x01 /* Auto/Manual Exposure control */ +#define COM8_SET(x) (COM8_DEFAULT|x) + +#define COM9_DEFAULT 0x08 +#define COM9_AGC_GAIN_2x 0x00 /* AGC: 2x */ +#define COM9_AGC_GAIN_4x 0x01 /* AGC: 4x */ +#define COM9_AGC_GAIN_8x 0x02 /* AGC: 8x */ +#define COM9_AGC_GAIN_16x 0x03 /* AGC: 16x */ +#define COM9_AGC_GAIN_32x 0x04 /* AGC: 32x */ +#define COM9_AGC_GAIN_64x 0x05 /* AGC: 64x */ +#define COM9_AGC_GAIN_128x 0x06 /* AGC: 128x */ +#define COM9_AGC_SET(x) (COM9_DEFAULT|(x<<5)) + +#define COM10_HREF_EN 0x80 /* HSYNC changes to HREF */ +#define COM10_HSYNC_EN 0x40 /* HREF changes to HSYNC */ +#define COM10_PCLK_FREE 0x20 /* PCLK output option: free running PCLK */ +#define COM10_PCLK_EDGE 0x10 /* Data is updated at the rising edge of PCLK */ +#define COM10_HREF_NEG 0x08 /* HREF negative */ +#define COM10_VSYNC_NEG 0x02 /* VSYNC negative */ +#define COM10_HSYNC_NEG 0x01 /* HSYNC negative */ + +#define CTRL1_AWB 0x08 /* Enable AWB */ + +#define VV_AGC_TH_SET(h,l) ((h<<4)|(l&0x0F)) + +#define REG32_UXGA 0x36 +#define REG32_SVGA 0x09 +#define REG32_CIF 0x89 + +#define CLKRC_2X 0x80 +#define CLKRC_2X_UXGA (0x01 | CLKRC_2X) +#define CLKRC_2X_SVGA CLKRC_2X +#define CLKRC_2X_CIF CLKRC_2X + +#endif //__REG_REGS_H__ diff --git a/esp32-cam-rtos-allframes/ov2640_settings.h b/esp32-cam-rtos-allframes/ov2640_settings.h new file mode 100644 index 0000000..f151f0a --- /dev/null +++ b/esp32-cam-rtos-allframes/ov2640_settings.h @@ -0,0 +1,485 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef _OV2640_SETTINGS_H_ +#define _OV2640_SETTINGS_H_ + +#include +#include +#include "esp_attr.h" +#include "ov2640_regs.h" + +typedef enum { + OV2640_MODE_UXGA, OV2640_MODE_SVGA, OV2640_MODE_CIF, OV2640_MODE_MAX +} ov2640_sensor_mode_t; + +typedef struct { + union { + struct { + uint8_t pclk_div:7; + uint8_t pclk_auto:1; + }; + uint8_t pclk; + }; + union { + struct { + uint8_t clk_div:6; + uint8_t reserved:1; + uint8_t clk_2x:1; + }; + uint8_t clk; + }; +} ov2640_clk_t; + +typedef struct { + uint16_t offset_x; + uint16_t offset_y; + uint16_t max_x; + uint16_t max_y; +} ov2640_ratio_settings_t; + +static const DRAM_ATTR ov2640_ratio_settings_t ratio_table[] = { + // ox, oy, mx, my + { 0, 0, 1600, 1200 }, //4x3 + { 8, 72, 1584, 1056 }, //3x2 + { 0, 100, 1600, 1000 }, //16x10 + { 0, 120, 1600, 960 }, //5x3 + { 0, 150, 1600, 900 }, //16x9 + { 2, 258, 1596, 684 }, //21x9 + { 50, 0, 1500, 1200 }, //5x4 + { 200, 0, 1200, 1200 }, //1x1 + { 462, 0, 676, 1200 } //9x16 +}; + +// 30fps@24MHz +const DRAM_ATTR uint8_t ov2640_settings_cif[][2] = { + {BANK_SEL, BANK_DSP}, + {0x2c, 0xff}, + {0x2e, 0xdf}, + {BANK_SEL, BANK_SENSOR}, + {0x3c, 0x32}, + {CLKRC, 0x01}, + {COM2, COM2_OUT_DRIVE_3x}, + {REG04, REG04_DEFAULT}, + {COM8, COM8_DEFAULT | COM8_BNDF_EN | COM8_AGC_EN | COM8_AEC_EN}, + {COM9, COM9_AGC_SET(COM9_AGC_GAIN_8x)}, + {0x2c, 0x0c}, + {0x33, 0x78}, + {0x3a, 0x33}, + {0x3b, 0xfB}, + {0x3e, 0x00}, + {0x43, 0x11}, + {0x16, 0x10}, + {0x39, 0x92}, + {0x35, 0xda}, + {0x22, 0x1a}, + {0x37, 0xc3}, + {0x23, 0x00}, + {ARCOM2, 0xc0}, + {0x06, 0x88}, + {0x07, 0xc0}, + {COM4, 0x87}, + {0x0e, 0x41}, + {0x4c, 0x00}, + {0x4a, 0x81}, + {0x21, 0x99}, + {AEW, 0x40}, + {AEB, 0x38}, + {VV, VV_AGC_TH_SET(8,2)}, + {0x5c, 0x00}, + {0x63, 0x00}, + {HISTO_LOW, 0x70}, + {HISTO_HIGH, 0x80}, + {0x7c, 0x05}, + {0x20, 0x80}, + {0x28, 0x30}, + {0x6c, 0x00}, + {0x6d, 0x80}, + {0x6e, 0x00}, + {0x70, 0x02}, + {0x71, 0x94}, + {0x73, 0xc1}, + {0x3d, 0x34}, + {0x5a, 0x57}, + {BD50, 0xbb}, + {BD60, 0x9c}, + {COM7, COM7_RES_CIF}, + {HSTART, 0x11}, + {HSTOP, 0x43}, + {VSTART, 0x00}, + {VSTOP, 0x25}, + {REG32, 0x89}, + {0x37, 0xc0}, + {BD50, 0xca}, + {BD60, 0xa8}, + {0x6d, 0x00}, + {0x3d, 0x38}, + {BANK_SEL, BANK_DSP}, + {0xe5, 0x7f}, + {MC_BIST, MC_BIST_RESET | MC_BIST_BOOT_ROM_SEL}, + {0x41, 0x24}, + {RESET, RESET_JPEG | RESET_DVP}, + {0x76, 0xff}, + {0x33, 0xa0}, + {0x42, 0x20}, + {0x43, 0x18}, + {0x4c, 0x00}, + {CTRL3, CTRL3_WPC_EN | 0x10 }, + {0x88, 0x3f}, + {0xd7, 0x03}, + {0xd9, 0x10}, + {R_DVP_SP, R_DVP_SP_AUTO_MODE | 0x02}, + {0xc8, 0x08}, + {0xc9, 0x80}, + {BPADDR, 0x00}, + {BPDATA, 0x00}, + {BPADDR, 0x03}, + {BPDATA, 0x48}, + {BPDATA, 0x48}, + {BPADDR, 0x08}, + {BPDATA, 0x20}, + {BPDATA, 0x10}, + {BPDATA, 0x0e}, + {0x90, 0x00}, + {0x91, 0x0e}, + {0x91, 0x1a}, + {0x91, 0x31}, + {0x91, 0x5a}, + {0x91, 0x69}, + {0x91, 0x75}, + {0x91, 0x7e}, + {0x91, 0x88}, + {0x91, 0x8f}, + {0x91, 0x96}, + {0x91, 0xa3}, + {0x91, 0xaf}, + {0x91, 0xc4}, + {0x91, 0xd7}, + {0x91, 0xe8}, + {0x91, 0x20}, + {0x92, 0x00}, + {0x93, 0x06}, + {0x93, 0xe3}, + {0x93, 0x05}, + {0x93, 0x05}, + {0x93, 0x00}, + {0x93, 0x04}, + {0x93, 0x00}, + {0x93, 0x00}, + {0x93, 0x00}, + {0x93, 0x00}, + {0x93, 0x00}, + {0x93, 0x00}, + {0x93, 0x00}, + {0x96, 0x00}, + {0x97, 0x08}, + {0x97, 0x19}, + {0x97, 0x02}, + {0x97, 0x0c}, + {0x97, 0x24}, + {0x97, 0x30}, + {0x97, 0x28}, + {0x97, 0x26}, + {0x97, 0x02}, + {0x97, 0x98}, + {0x97, 0x80}, + {0x97, 0x00}, + {0x97, 0x00}, + {0xa4, 0x00}, + {0xa8, 0x00}, + {0xc5, 0x11}, + {0xc6, 0x51}, + {0xbf, 0x80}, + {0xc7, 0x10}, + {0xb6, 0x66}, + {0xb8, 0xA5}, + {0xb7, 0x64}, + {0xb9, 0x7C}, + {0xb3, 0xaf}, + {0xb4, 0x97}, + {0xb5, 0xFF}, + {0xb0, 0xC5}, + {0xb1, 0x94}, + {0xb2, 0x0f}, + {0xc4, 0x5c}, + {CTRL1, 0xfd}, + {0x7f, 0x00}, + {0xe5, 0x1f}, + {0xe1, 0x67}, + {0xdd, 0x7f}, + {IMAGE_MODE, 0x00}, + {RESET, 0x00}, + {R_BYPASS, R_BYPASS_DSP_EN}, + {0, 0} +}; + +const DRAM_ATTR uint8_t ov2640_settings_to_cif[][2] = { + {BANK_SEL, BANK_SENSOR}, + {COM7, COM7_RES_CIF}, + + //Set the sensor output window + {COM1, 0x0A}, + {REG32, REG32_CIF}, + {HSTART, 0x11}, + {HSTOP, 0x43}, + {VSTART, 0x00}, + {VSTOP, 0x25}, + + //{CLKRC, 0x00}, + {BD50, 0xca}, + {BD60, 0xa8}, + {0x5a, 0x23}, + {0x6d, 0x00}, + {0x3d, 0x38}, + {0x39, 0x92}, + {0x35, 0xda}, + {0x22, 0x1a}, + {0x37, 0xc3}, + {0x23, 0x00}, + {ARCOM2, 0xc0}, + {0x06, 0x88}, + {0x07, 0xc0}, + {COM4, 0x87}, + {0x0e, 0x41}, + {0x4c, 0x00}, + {BANK_SEL, BANK_DSP}, + {RESET, RESET_DVP}, + + //Set the sensor resolution (UXGA, SVGA, CIF) + {HSIZE8, 0x32}, + {VSIZE8, 0x25}, + {SIZEL, 0x00}, + + //Set the image window size >= output size + {HSIZE, 0x64}, + {VSIZE, 0x4a}, + {XOFFL, 0x00}, + {YOFFL, 0x00}, + {VHYX, 0x00}, + {TEST, 0x00}, + + {CTRL2, CTRL2_DCW_EN | 0x1D}, + {CTRLI, CTRLI_LP_DP | 0x00}, + //{R_DVP_SP, 0x08}, + {0, 0} +}; + +const DRAM_ATTR uint8_t ov2640_settings_to_svga[][2] = { + {BANK_SEL, BANK_SENSOR}, + {COM7, COM7_RES_SVGA}, + + //Set the sensor output window + {COM1, 0x0A}, + {REG32, REG32_SVGA}, + {HSTART, 0x11}, + {HSTOP, 0x43}, + {VSTART, 0x00}, + {VSTOP, 0x4b}, + + //{CLKRC, 0x00}, + {0x37, 0xc0}, + {BD50, 0xca}, + {BD60, 0xa8}, + {0x5a, 0x23}, + {0x6d, 0x00}, + {0x3d, 0x38}, + {0x39, 0x92}, + {0x35, 0xda}, + {0x22, 0x1a}, + {0x37, 0xc3}, + {0x23, 0x00}, + {ARCOM2, 0xc0}, + {0x06, 0x88}, + {0x07, 0xc0}, + {COM4, 0x87}, + {0x0e, 0x41}, + {0x42, 0x03}, + {0x4c, 0x00}, + {BANK_SEL, BANK_DSP}, + {RESET, RESET_DVP}, + + //Set the sensor resolution (UXGA, SVGA, CIF) + {HSIZE8, 0x64}, + {VSIZE8, 0x4B}, + {SIZEL, 0x00}, + + //Set the image window size >= output size + {HSIZE, 0xC8}, + {VSIZE, 0x96}, + {XOFFL, 0x00}, + {YOFFL, 0x00}, + {VHYX, 0x00}, + {TEST, 0x00}, + + {CTRL2, CTRL2_DCW_EN | 0x1D}, + {CTRLI, CTRLI_LP_DP | 0x00}, + //{R_DVP_SP, 0x08}, + {0, 0} +}; + +const DRAM_ATTR uint8_t ov2640_settings_to_uxga[][2] = { + {BANK_SEL, BANK_SENSOR}, + {COM7, COM7_RES_UXGA}, + + //Set the sensor output window + {COM1, 0x0F}, + {REG32, REG32_UXGA}, + {HSTART, 0x11}, + {HSTOP, 0x75}, + {VSTART, 0x01}, + {VSTOP, 0x97}, + + //{CLKRC, 0x00}, + {0x3d, 0x34}, + {BD50, 0xbb}, + {BD60, 0x9c}, + {0x5a, 0x57}, + {0x6d, 0x80}, + {0x39, 0x82}, + {0x23, 0x00}, + {0x07, 0xc0}, + {0x4c, 0x00}, + {0x35, 0x88}, + {0x22, 0x0a}, + {0x37, 0x40}, + {ARCOM2, 0xa0}, + {0x06, 0x02}, + {COM4, 0xb7}, + {0x0e, 0x01}, + {0x42, 0x83}, + {BANK_SEL, BANK_DSP}, + {RESET, RESET_DVP}, + + //Set the sensor resolution (UXGA, SVGA, CIF) + {HSIZE8, 0xc8}, + {VSIZE8, 0x96}, + {SIZEL, 0x00}, + + //Set the image window size >= output size + {HSIZE, 0x90}, + {VSIZE, 0x2c}, + {XOFFL, 0x00}, + {YOFFL, 0x00}, + {VHYX, 0x88}, + {TEST, 0x00}, + + {CTRL2, CTRL2_DCW_EN | 0x1d}, + {CTRLI, 0x00}, + //{R_DVP_SP, 0x06}, + {0, 0} +}; + +const DRAM_ATTR uint8_t ov2640_settings_jpeg3[][2] = { + {BANK_SEL, BANK_DSP}, + {RESET, RESET_JPEG | RESET_DVP}, + {IMAGE_MODE, IMAGE_MODE_JPEG_EN | IMAGE_MODE_HREF_VSYNC}, + {0xD7, 0x03}, + {0xE1, 0x77}, + {0xE5, 0x1F}, + {0xD9, 0x10}, + {0xDF, 0x80}, + {0x33, 0x80}, + {0x3C, 0x10}, + {0xEB, 0x30}, + {0xDD, 0x7F}, + {RESET, 0x00}, + {0, 0} +}; + +static const uint8_t ov2640_settings_yuv422[][2] = { + {BANK_SEL, BANK_DSP}, + {RESET, RESET_DVP}, + {IMAGE_MODE, IMAGE_MODE_YUV422}, + {0xD7, 0x01}, + {0xE1, 0x67}, + {RESET, 0x00}, + {0, 0}, +}; + +static const uint8_t ov2640_settings_rgb565[][2] = { + {BANK_SEL, BANK_DSP}, + {RESET, RESET_DVP}, + {IMAGE_MODE, IMAGE_MODE_RGB565}, + {0xD7, 0x03}, + {0xE1, 0x77}, + {RESET, 0x00}, + {0, 0}, +}; + +#define NUM_BRIGHTNESS_LEVELS (5) +static const uint8_t brightness_regs[NUM_BRIGHTNESS_LEVELS + 1][5] = { + {BPADDR, BPDATA, BPADDR, BPDATA, BPDATA }, + {0x00, 0x04, 0x09, 0x00, 0x00 }, /* -2 */ + {0x00, 0x04, 0x09, 0x10, 0x00 }, /* -1 */ + {0x00, 0x04, 0x09, 0x20, 0x00 }, /* 0 */ + {0x00, 0x04, 0x09, 0x30, 0x00 }, /* +1 */ + {0x00, 0x04, 0x09, 0x40, 0x00 }, /* +2 */ +}; + +#define NUM_CONTRAST_LEVELS (5) +static const uint8_t contrast_regs[NUM_CONTRAST_LEVELS + 1][7] = { + {BPADDR, BPDATA, BPADDR, BPDATA, BPDATA, BPDATA, BPDATA }, + {0x00, 0x04, 0x07, 0x20, 0x18, 0x34, 0x06 }, /* -2 */ + {0x00, 0x04, 0x07, 0x20, 0x1c, 0x2a, 0x06 }, /* -1 */ + {0x00, 0x04, 0x07, 0x20, 0x20, 0x20, 0x06 }, /* 0 */ + {0x00, 0x04, 0x07, 0x20, 0x24, 0x16, 0x06 }, /* +1 */ + {0x00, 0x04, 0x07, 0x20, 0x28, 0x0c, 0x06 }, /* +2 */ +}; + +#define NUM_SATURATION_LEVELS (5) +static const uint8_t saturation_regs[NUM_SATURATION_LEVELS + 1][5] = { + {BPADDR, BPDATA, BPADDR, BPDATA, BPDATA }, + {0x00, 0x02, 0x03, 0x28, 0x28 }, /* -2 */ + {0x00, 0x02, 0x03, 0x38, 0x38 }, /* -1 */ + {0x00, 0x02, 0x03, 0x48, 0x48 }, /* 0 */ + {0x00, 0x02, 0x03, 0x58, 0x58 }, /* +1 */ + {0x00, 0x02, 0x03, 0x68, 0x68 }, /* +2 */ +}; + +#define NUM_SPECIAL_EFFECTS (7) +static const uint8_t special_effects_regs[NUM_SPECIAL_EFFECTS + 1][5] = { + {BPADDR, BPDATA, BPADDR, BPDATA, BPDATA }, + {0x00, 0X00, 0x05, 0X80, 0X80 }, /* no effect */ + {0x00, 0X40, 0x05, 0X80, 0X80 }, /* negative */ + {0x00, 0X18, 0x05, 0X80, 0X80 }, /* black and white */ + {0x00, 0X18, 0x05, 0X40, 0XC0 }, /* reddish */ + {0x00, 0X18, 0x05, 0X40, 0X40 }, /* greenish */ + {0x00, 0X18, 0x05, 0XA0, 0X40 }, /* blue */ + {0x00, 0X18, 0x05, 0X40, 0XA6 }, /* retro */ +}; + +#define NUM_WB_MODES (4) +static const uint8_t wb_modes_regs[NUM_WB_MODES + 1][3] = { + {0XCC, 0XCD, 0XCE }, + {0x5E, 0X41, 0x54 }, /* sunny */ + {0x65, 0X41, 0x4F }, /* cloudy */ + {0x52, 0X41, 0x66 }, /* office */ + {0x42, 0X3F, 0x71 }, /* home */ +}; + +#define NUM_AE_LEVELS (5) +static const uint8_t ae_levels_regs[NUM_AE_LEVELS + 1][3] = { + { AEW, AEB, VV }, + {0x20, 0X18, 0x60 }, + {0x34, 0X1C, 0x00 }, + {0x3E, 0X38, 0x81 }, + {0x48, 0X40, 0x81 }, + {0x58, 0X50, 0x92 }, +}; + +const uint8_t agc_gain_tbl[31] = { + 0x00, 0x10, 0x18, 0x30, 0x34, 0x38, 0x3C, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7A, 0x7C, 0x7E, 0xF0, + 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF +}; + +#endif /* _OV2640_SETTINGS_H_ */ diff --git a/esp32-cam-rtos-allframes/ov3660.c b/esp32-cam-rtos-allframes/ov3660.c new file mode 100644 index 0000000..723ec5c --- /dev/null +++ b/esp32-cam-rtos-allframes/ov3660.c @@ -0,0 +1,1033 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * OV3660 driver. + * + */ +#include +#include +#include +#include "sccb.h" +#include "ov3660.h" +#include "ov3660_regs.h" +#include "ov3660_settings.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#else +#include "esp_log.h" +static const char *TAG = "ov3660"; +#endif + +//#define REG_DEBUG_ON + +static int read_reg(uint8_t slv_addr, const uint16_t reg){ + int ret = SCCB_Read16(slv_addr, reg); +#ifdef REG_DEBUG_ON + if (ret < 0) { + ESP_LOGE(TAG, "READ REG 0x%04x FAILED: %d", reg, ret); + } +#endif + return ret; +} + +static int check_reg_mask(uint8_t slv_addr, uint16_t reg, uint8_t mask){ + return (read_reg(slv_addr, reg) & mask) == mask; +} + +static int read_reg16(uint8_t slv_addr, const uint16_t reg){ + int ret = 0, ret2 = 0; + ret = read_reg(slv_addr, reg); + if (ret >= 0) { + ret = (ret & 0xFF) << 8; + ret2 = read_reg(slv_addr, reg+1); + if (ret2 < 0) { + ret = ret2; + } else { + ret |= ret2 & 0xFF; + } + } + return ret; +} + + +static int write_reg(uint8_t slv_addr, const uint16_t reg, uint8_t value){ + int ret = 0; +#ifndef REG_DEBUG_ON + ret = SCCB_Write16(slv_addr, reg, value); +#else + int old_value = read_reg(slv_addr, reg); + if (old_value < 0) { + return old_value; + } + if ((uint8_t)old_value != value) { + ESP_LOGI(TAG, "NEW REG 0x%04x: 0x%02x to 0x%02x", reg, (uint8_t)old_value, value); + ret = SCCB_Write16(slv_addr, reg, value); + } else { + ESP_LOGD(TAG, "OLD REG 0x%04x: 0x%02x", reg, (uint8_t)old_value); + ret = SCCB_Write16(slv_addr, reg, value);//maybe not? + } + if (ret < 0) { + ESP_LOGE(TAG, "WRITE REG 0x%04x FAILED: %d", reg, ret); + } +#endif + return ret; +} + +static int set_reg_bits(uint8_t slv_addr, uint16_t reg, uint8_t offset, uint8_t mask, uint8_t value) +{ + int ret = 0; + uint8_t c_value, new_value; + ret = read_reg(slv_addr, reg); + if(ret < 0) { + return ret; + } + c_value = ret; + new_value = (c_value & ~(mask << offset)) | ((value & mask) << offset); + ret = write_reg(slv_addr, reg, new_value); + return ret; +} + +static int write_regs(uint8_t slv_addr, const uint16_t (*regs)[2]) +{ + int i = 0, ret = 0; + while (!ret && regs[i][0] != REGLIST_TAIL) { + if (regs[i][0] == REG_DLY) { + vTaskDelay(regs[i][1] / portTICK_PERIOD_MS); + } else { + ret = write_reg(slv_addr, regs[i][0], regs[i][1]); + } + i++; + } + return ret; +} + +static int write_reg16(uint8_t slv_addr, const uint16_t reg, uint16_t value) +{ + if (write_reg(slv_addr, reg, value >> 8) || write_reg(slv_addr, reg + 1, value)) { + return -1; + } + return 0; +} + +static int write_addr_reg(uint8_t slv_addr, const uint16_t reg, uint16_t x_value, uint16_t y_value) +{ + if (write_reg16(slv_addr, reg, x_value) || write_reg16(slv_addr, reg + 2, y_value)) { + return -1; + } + return 0; +} + +#define write_reg_bits(slv_addr, reg, mask, enable) set_reg_bits(slv_addr, reg, 0, mask, enable?mask:0) + +static int calc_sysclk(int xclk, bool pll_bypass, int pll_multiplier, int pll_sys_div, int pll_pre_div, bool pll_root_2x, int pll_seld5, bool pclk_manual, int pclk_div) +{ + const int pll_pre_div2x_map[] = { 2, 3, 4, 6 };//values are multiplied by two to avoid floats + const int pll_seld52x_map[] = { 2, 2, 4, 5 }; + + if(!pll_sys_div) { + pll_sys_div = 1; + } + + int pll_pre_div2x = pll_pre_div2x_map[pll_pre_div]; + int pll_root_div = pll_root_2x?2:1; + int pll_seld52x = pll_seld52x_map[pll_seld5]; + + int VCO = (xclk / 1000) * pll_multiplier * pll_root_div * 2 / pll_pre_div2x; + int PLLCLK = pll_bypass?(xclk):(VCO * 1000 * 2 / pll_sys_div / pll_seld52x); + int PCLK = PLLCLK / 2 / ((pclk_manual && pclk_div)?pclk_div:1); + int SYSCLK = PLLCLK / 4; + + ESP_LOGD(TAG, "Calculated VCO: %d Hz, PLLCLK: %d Hz, SYSCLK: %d Hz, PCLK: %d Hz", VCO*1000, PLLCLK, SYSCLK, PCLK); + return SYSCLK; +} + +static int set_pll(sensor_t *sensor, bool bypass, uint8_t multiplier, uint8_t sys_div, uint8_t pre_div, bool root_2x, uint8_t seld5, bool pclk_manual, uint8_t pclk_div){ + int ret = 0; + if(multiplier > 31 || sys_div > 15 || pre_div > 3 || pclk_div > 31 || seld5 > 3){ + ESP_LOGE(TAG, "Invalid arguments"); + return -1; + } + + calc_sysclk(sensor->xclk_freq_hz, bypass, multiplier, sys_div, pre_div, root_2x, seld5, pclk_manual, pclk_div); + + ret = write_reg(sensor->slv_addr, SC_PLLS_CTRL0, bypass?0x80:0x00); + if (ret == 0) { + ret = write_reg(sensor->slv_addr, SC_PLLS_CTRL1, multiplier & 0x1f); + } + if (ret == 0) { + ret = write_reg(sensor->slv_addr, SC_PLLS_CTRL2, 0x10 | (sys_div & 0x0f)); + } + if (ret == 0) { + ret = write_reg(sensor->slv_addr, SC_PLLS_CTRL3, (pre_div & 0x3) << 4 | seld5 | (root_2x?0x40:0x00)); + } + if (ret == 0) { + ret = write_reg(sensor->slv_addr, PCLK_RATIO, pclk_div & 0x1f); + } + if (ret == 0) { + ret = write_reg(sensor->slv_addr, VFIFO_CTRL0C, pclk_manual?0x22:0x20); + } + if(ret){ + ESP_LOGE(TAG, "set_sensor_pll FAILED!"); + } + return ret; +} + +static int set_ae_level(sensor_t *sensor, int level); + +static int reset(sensor_t *sensor) +{ + int ret = 0; + // Software Reset: clear all registers and reset them to their default values + ret = write_reg(sensor->slv_addr, SYSTEM_CTROL0, 0x82); + if(ret){ + ESP_LOGE(TAG, "Software Reset FAILED!"); + return ret; + } + vTaskDelay(100 / portTICK_PERIOD_MS); + ret = write_regs(sensor->slv_addr, sensor_default_regs); + if (ret == 0) { + ESP_LOGD(TAG, "Camera defaults loaded"); + ret = set_ae_level(sensor, 0); + vTaskDelay(100 / portTICK_PERIOD_MS); + } + return ret; +} + +static int set_pixformat(sensor_t *sensor, pixformat_t pixformat) +{ + int ret = 0; + const uint16_t (*regs)[2]; + + switch (pixformat) { + case PIXFORMAT_YUV422: + regs = sensor_fmt_yuv422; + break; + + case PIXFORMAT_GRAYSCALE: + regs = sensor_fmt_grayscale; + break; + + case PIXFORMAT_RGB565: + case PIXFORMAT_RGB888: + regs = sensor_fmt_rgb565; + break; + + case PIXFORMAT_JPEG: + regs = sensor_fmt_jpeg; + break; + + case PIXFORMAT_RAW: + regs = sensor_fmt_raw; + break; + + default: + ESP_LOGE(TAG, "Unsupported pixformat: %u", pixformat); + return -1; + } + + ret = write_regs(sensor->slv_addr, regs); + if(ret == 0) { + sensor->pixformat = pixformat; + ESP_LOGD(TAG, "Set pixformat to: %u", pixformat); + } + return ret; +} + +static int set_image_options(sensor_t *sensor) +{ + int ret = 0; + uint8_t reg20 = 0; + uint8_t reg21 = 0; + uint8_t reg4514 = 0; + uint8_t reg4514_test = 0; + + // compression + if (sensor->pixformat == PIXFORMAT_JPEG) { + reg21 |= 0x20; + } + + // binning + if (sensor->status.binning) { + reg20 |= 0x01; + reg21 |= 0x01; + reg4514_test |= 4; + } else { + reg20 |= 0x40; + } + + // V-Flip + if (sensor->status.vflip) { + reg20 |= 0x06; + reg4514_test |= 1; + } + + // H-Mirror + if (sensor->status.hmirror) { + reg21 |= 0x06; + reg4514_test |= 2; + } + + switch (reg4514_test) { + //no binning + case 0: reg4514 = 0x88; break;//normal + case 1: reg4514 = 0x88; break;//v-flip + case 2: reg4514 = 0xbb; break;//h-mirror + case 3: reg4514 = 0xbb; break;//v-flip+h-mirror + //binning + case 4: reg4514 = 0xaa; break;//normal + case 5: reg4514 = 0xbb; break;//v-flip + case 6: reg4514 = 0xbb; break;//h-mirror + case 7: reg4514 = 0xaa; break;//v-flip+h-mirror + } + + if(write_reg(sensor->slv_addr, TIMING_TC_REG20, reg20) + || write_reg(sensor->slv_addr, TIMING_TC_REG21, reg21) + || write_reg(sensor->slv_addr, 0x4514, reg4514)){ + ESP_LOGE(TAG, "Setting Image Options Failed"); + ret = -1; + } + + if (sensor->status.binning) { + ret = write_reg(sensor->slv_addr, 0x4520, 0x0b) + || write_reg(sensor->slv_addr, X_INCREMENT, 0x31)//odd:3, even: 1 + || write_reg(sensor->slv_addr, Y_INCREMENT, 0x31);//odd:3, even: 1 + } else { + ret = write_reg(sensor->slv_addr, 0x4520, 0xb0) + || write_reg(sensor->slv_addr, X_INCREMENT, 0x11)//odd:1, even: 1 + || write_reg(sensor->slv_addr, Y_INCREMENT, 0x11);//odd:1, even: 1 + } + + ESP_LOGD(TAG, "Set Image Options: Compression: %u, Binning: %u, V-Flip: %u, H-Mirror: %u, Reg-4514: 0x%02x", + sensor->pixformat == PIXFORMAT_JPEG, sensor->status.binning, sensor->status.vflip, sensor->status.hmirror, reg4514); + return ret; +} + +static int set_framesize(sensor_t *sensor, framesize_t framesize) +{ + int ret = 0; + framesize_t old_framesize = sensor->status.framesize; + sensor->status.framesize = framesize; + + if(framesize > FRAMESIZE_QXGA){ + ESP_LOGE(TAG, "Invalid framesize: %u", framesize); + return -1; + } + uint16_t w = resolution[framesize].width; + uint16_t h = resolution[framesize].height; + aspect_ratio_t ratio = resolution[sensor->status.framesize].aspect_ratio; + ratio_settings_t settings = ratio_table[ratio]; + + sensor->status.binning = (w <= (settings.max_width / 2) && h <= (settings.max_height / 2)); + sensor->status.scale = !((w == settings.max_width && h == settings.max_height) + || (w == (settings.max_width / 2) && h == (settings.max_height / 2))); + + ret = write_addr_reg(sensor->slv_addr, X_ADDR_ST_H, settings.start_x, settings.start_y) + || write_addr_reg(sensor->slv_addr, X_ADDR_END_H, settings.end_x, settings.end_y) + || write_addr_reg(sensor->slv_addr, X_OUTPUT_SIZE_H, w, h); + + if (ret) { + goto fail; + } + + if (sensor->status.binning) { + ret = write_addr_reg(sensor->slv_addr, X_TOTAL_SIZE_H, settings.total_x, (settings.total_y / 2) + 1) + || write_addr_reg(sensor->slv_addr, X_OFFSET_H, 8, 2); + } else { + ret = write_addr_reg(sensor->slv_addr, X_TOTAL_SIZE_H, settings.total_x, settings.total_y) + || write_addr_reg(sensor->slv_addr, X_OFFSET_H, 16, 6); + } + + if (ret == 0) { + ret = write_reg_bits(sensor->slv_addr, ISP_CONTROL_01, 0x20, sensor->status.scale); + } + + if (ret == 0) { + ret = set_image_options(sensor); + } + + if (ret) { + goto fail; + } + + if (sensor->pixformat == PIXFORMAT_JPEG) { + if (framesize == FRAMESIZE_QXGA) { + //40MHz SYSCLK and 10MHz PCLK + ret = set_pll(sensor, false, 24, 1, 3, false, 0, true, 8); + } else { + //50MHz SYSCLK and 10MHz PCLK + ret = set_pll(sensor, false, 30, 1, 3, false, 0, true, 10); + } + } else { + if (framesize > FRAMESIZE_CIF) { + //10MHz SYSCLK and 10MHz PCLK (6.19 FPS) + ret = set_pll(sensor, false, 2, 1, 0, false, 0, true, 2); + } else { + //25MHz SYSCLK and 10MHz PCLK (15.45 FPS) + ret = set_pll(sensor, false, 5, 1, 0, false, 0, true, 5); + } + } + + if (ret == 0) { + ESP_LOGD(TAG, "Set framesize to: %ux%u", w, h); + } + return ret; + +fail: + sensor->status.framesize = old_framesize; + ESP_LOGE(TAG, "Setting framesize to: %ux%u failed", w, h); + return ret; +} + +static int set_hmirror(sensor_t *sensor, int enable) +{ + int ret = 0; + sensor->status.hmirror = enable; + ret = set_image_options(sensor); + if (ret == 0) { + ESP_LOGD(TAG, "Set h-mirror to: %d", enable); + } + return ret; +} + +static int set_vflip(sensor_t *sensor, int enable) +{ + int ret = 0; + sensor->status.vflip = enable; + ret = set_image_options(sensor); + if (ret == 0) { + ESP_LOGD(TAG, "Set v-flip to: %d", enable); + } + return ret; +} + +static int set_quality(sensor_t *sensor, int qs) +{ + int ret = 0; + ret = write_reg(sensor->slv_addr, COMPRESSION_CTRL07, qs & 0x3f); + if (ret == 0) { + sensor->status.quality = qs; + ESP_LOGD(TAG, "Set quality to: %d", qs); + } + return ret; +} + +static int set_colorbar(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, PRE_ISP_TEST_SETTING_1, TEST_COLOR_BAR, enable); + if (ret == 0) { + sensor->status.colorbar = enable; + ESP_LOGD(TAG, "Set colorbar to: %d", enable); + } + return ret; +} + +static int set_gain_ctrl(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, AEC_PK_MANUAL, AEC_PK_MANUAL_AGC_MANUALEN, !enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set gain_ctrl to: %d", enable); + sensor->status.agc = enable; + } + return ret; +} + +static int set_exposure_ctrl(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, AEC_PK_MANUAL, AEC_PK_MANUAL_AEC_MANUALEN, !enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set exposure_ctrl to: %d", enable); + sensor->status.aec = enable; + } + return ret; +} + +static int set_whitebal(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, ISP_CONTROL_01, 0x01, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set awb to: %d", enable); + sensor->status.awb = enable; + } + return ret; +} + +//Advanced AWB +static int set_dcw_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, 0x5183, 0x80, !enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set dcw to: %d", enable); + sensor->status.dcw = enable; + } + return ret; +} + +//night mode enable +static int set_aec2(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, 0x3a00, 0x04, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set aec2 to: %d", enable); + sensor->status.aec2 = enable; + } + return ret; +} + +static int set_bpc_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, 0x5000, 0x04, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set bpc to: %d", enable); + sensor->status.bpc = enable; + } + return ret; +} + +static int set_wpc_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, 0x5000, 0x02, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set wpc to: %d", enable); + sensor->status.wpc = enable; + } + return ret; +} + +//Gamma enable +static int set_raw_gma_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, 0x5000, 0x20, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set raw_gma to: %d", enable); + sensor->status.raw_gma = enable; + } + return ret; +} + +static int set_lenc_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, 0x5000, 0x80, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set lenc to: %d", enable); + sensor->status.lenc = enable; + } + return ret; +} + +static int get_agc_gain(sensor_t *sensor) +{ + int ra = read_reg(sensor->slv_addr, 0x350a); + if (ra < 0) { + return 0; + } + int rb = read_reg(sensor->slv_addr, 0x350b); + if (rb < 0) { + return 0; + } + int res = (rb & 0xF0) >> 4 | (ra & 0x03) << 4; + if (rb & 0x0F) { + res += 1; + } + return res; +} + +//real gain +static int set_agc_gain(sensor_t *sensor, int gain) +{ + int ret = 0; + if(gain < 0) { + gain = 0; + } else if(gain > 64) { + gain = 64; + } + + //gain value is 6.4 bits float + //in order to use the max range, we deduct 1/16 + int gainv = gain << 4; + if(gainv){ + gainv -= 1; + } + + ret = write_reg(sensor->slv_addr, 0x350a, gainv >> 8) || write_reg(sensor->slv_addr, 0x350b, gainv & 0xff); + if (ret == 0) { + ESP_LOGD(TAG, "Set agc_gain to: %d", gain); + sensor->status.agc_gain = gain; + } + return ret; +} + +static int get_aec_value(sensor_t *sensor) +{ + int ra = read_reg(sensor->slv_addr, 0x3500); + if (ra < 0) { + return 0; + } + int rb = read_reg(sensor->slv_addr, 0x3501); + if (rb < 0) { + return 0; + } + int rc = read_reg(sensor->slv_addr, 0x3502); + if (rc < 0) { + return 0; + } + int res = (ra & 0x0F) << 12 | (rb & 0xFF) << 4 | (rc & 0xF0) >> 4; + return res; +} + +static int set_aec_value(sensor_t *sensor, int value) +{ + int ret = 0, max_val = 0; + max_val = read_reg16(sensor->slv_addr, 0x380e); + if (max_val < 0) { + ESP_LOGE(TAG, "Could not read max aec_value"); + return -1; + } + if (value > max_val) { + value =max_val; + } + + ret = write_reg(sensor->slv_addr, 0x3500, (value >> 12) & 0x0F) + || write_reg(sensor->slv_addr, 0x3501, (value >> 4) & 0xFF) + || write_reg(sensor->slv_addr, 0x3502, (value << 4) & 0xF0); + + if (ret == 0) { + ESP_LOGD(TAG, "Set aec_value to: %d / %d", value, max_val); + sensor->status.aec_value = value; + } + return ret; +} + +static int set_ae_level(sensor_t *sensor, int level) +{ + int ret = 0; + if (level < -5 || level > 5) { + return -1; + } + //good targets are between 5 and 115 + int target_level = ((level + 5) * 10) + 5; + + int level_high, level_low; + int fast_high, fast_low; + + level_low = target_level * 23 / 25; //0.92 (0.46) + level_high = target_level * 27 / 25; //1.08 (2.08) + + fast_low = level_low >> 1; + fast_high = level_high << 1; + + if(fast_high>255) { + fast_high = 255; + } + + ret = write_reg(sensor->slv_addr, 0x3a0f, level_high) + || write_reg(sensor->slv_addr, 0x3a10, level_low) + || write_reg(sensor->slv_addr, 0x3a1b, level_high) + || write_reg(sensor->slv_addr, 0x3a1e, level_low) + || write_reg(sensor->slv_addr, 0x3a11, fast_high) + || write_reg(sensor->slv_addr, 0x3a1f, fast_low); + + if (ret == 0) { + ESP_LOGD(TAG, "Set ae_level to: %d", level); + sensor->status.ae_level = level; + } + return ret; +} + +static int set_wb_mode(sensor_t *sensor, int mode) +{ + int ret = 0; + if (mode < 0 || mode > 4) { + return -1; + } + + ret = write_reg(sensor->slv_addr, 0x3406, (mode != 0)); + if (ret) { + return ret; + } + switch (mode) { + case 1://Sunny + ret = write_reg16(sensor->slv_addr, 0x3400, 0x5e0) //AWB R GAIN + || write_reg16(sensor->slv_addr, 0x3402, 0x410) //AWB G GAIN + || write_reg16(sensor->slv_addr, 0x3404, 0x540);//AWB B GAIN + break; + case 2://Cloudy + ret = write_reg16(sensor->slv_addr, 0x3400, 0x650) //AWB R GAIN + || write_reg16(sensor->slv_addr, 0x3402, 0x410) //AWB G GAIN + || write_reg16(sensor->slv_addr, 0x3404, 0x4f0);//AWB B GAIN + break; + case 3://Office + ret = write_reg16(sensor->slv_addr, 0x3400, 0x520) //AWB R GAIN + || write_reg16(sensor->slv_addr, 0x3402, 0x410) //AWB G GAIN + || write_reg16(sensor->slv_addr, 0x3404, 0x660);//AWB B GAIN + break; + case 4://HOME + ret = write_reg16(sensor->slv_addr, 0x3400, 0x420) //AWB R GAIN + || write_reg16(sensor->slv_addr, 0x3402, 0x3f0) //AWB G GAIN + || write_reg16(sensor->slv_addr, 0x3404, 0x710);//AWB B GAIN + break; + default://AUTO + break; + } + + if (ret == 0) { + ESP_LOGD(TAG, "Set wb_mode to: %d", mode); + sensor->status.wb_mode = mode; + } + return ret; +} + +static int set_awb_gain_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + int old_mode = sensor->status.wb_mode; + int mode = enable?old_mode:0; + + ret = set_wb_mode(sensor, mode); + + if (ret == 0) { + sensor->status.wb_mode = old_mode; + ESP_LOGD(TAG, "Set awb_gain to: %d", enable); + sensor->status.awb_gain = enable; + } + return ret; +} + +static int set_special_effect(sensor_t *sensor, int effect) +{ + int ret=0; + if (effect < 0 || effect > 6) { + return -1; + } + + uint8_t * regs = (uint8_t *)sensor_special_effects[effect]; + ret = write_reg(sensor->slv_addr, 0x5580, regs[0]) + || write_reg(sensor->slv_addr, 0x5583, regs[1]) + || write_reg(sensor->slv_addr, 0x5584, regs[2]) + || write_reg(sensor->slv_addr, 0x5003, regs[3]); + + if (ret == 0) { + ESP_LOGD(TAG, "Set special_effect to: %d", effect); + sensor->status.special_effect = effect; + } + return ret; +} + +static int set_brightness(sensor_t *sensor, int level) +{ + int ret = 0; + uint8_t value = 0; + bool negative = false; + + switch (level) { + case 3: + value = 0x30; + break; + case 2: + value = 0x20; + break; + case 1: + value = 0x10; + break; + case -1: + value = 0x10; + negative = true; + break; + case -2: + value = 0x20; + negative = true; + break; + case -3: + value = 0x30; + negative = true; + break; + default: // 0 + break; + } + + ret = write_reg(sensor->slv_addr, 0x5587, value); + if (ret == 0) { + ret = write_reg_bits(sensor->slv_addr, 0x5588, 0x08, negative); + } + + if (ret == 0) { + ESP_LOGD(TAG, "Set brightness to: %d", level); + sensor->status.brightness = level; + } + return ret; +} + +static int set_contrast(sensor_t *sensor, int level) +{ + int ret = 0; + if(level > 3 || level < -3) { + return -1; + } + ret = write_reg(sensor->slv_addr, 0x5586, (level + 4) << 3); + + if (ret == 0) { + ESP_LOGD(TAG, "Set contrast to: %d", level); + sensor->status.contrast = level; + } + return ret; +} + +static int set_saturation(sensor_t *sensor, int level) +{ + int ret = 0; + if(level > 4 || level < -4) { + return -1; + } + + uint8_t * regs = (uint8_t *)sensor_saturation_levels[level+4]; + for(int i=0; i<11; i++) { + ret = write_reg(sensor->slv_addr, 0x5381 + i, regs[i]); + if (ret) { + break; + } + } + + if (ret == 0) { + ESP_LOGD(TAG, "Set saturation to: %d", level); + sensor->status.saturation = level; + } + return ret; +} + +static int set_sharpness(sensor_t *sensor, int level) +{ + int ret = 0; + if(level > 3 || level < -3) { + return -1; + } + + uint8_t mt_offset_2 = (level + 3) * 8; + uint8_t mt_offset_1 = mt_offset_2 + 1; + + ret = write_reg_bits(sensor->slv_addr, 0x5308, 0x40, false)//0x40 means auto + || write_reg(sensor->slv_addr, 0x5300, 0x10) + || write_reg(sensor->slv_addr, 0x5301, 0x10) + || write_reg(sensor->slv_addr, 0x5302, mt_offset_1) + || write_reg(sensor->slv_addr, 0x5303, mt_offset_2) + || write_reg(sensor->slv_addr, 0x5309, 0x10) + || write_reg(sensor->slv_addr, 0x530a, 0x10) + || write_reg(sensor->slv_addr, 0x530b, 0x04) + || write_reg(sensor->slv_addr, 0x530c, 0x06); + + if (ret == 0) { + ESP_LOGD(TAG, "Set sharpness to: %d", level); + sensor->status.sharpness = level; + } + return ret; +} + +static int set_gainceiling(sensor_t *sensor, gainceiling_t level) +{ + int ret = 0, l = (int)level; + + ret = write_reg(sensor->slv_addr, 0x3A18, (l >> 8) & 3) + || write_reg(sensor->slv_addr, 0x3A19, l & 0xFF); + + if (ret == 0) { + ESP_LOGD(TAG, "Set gainceiling to: %d", l); + sensor->status.gainceiling = l; + } + return ret; +} + +static int get_denoise(sensor_t *sensor) +{ + if (!check_reg_mask(sensor->slv_addr, 0x5308, 0x10)) { + return 0; + } + return (read_reg(sensor->slv_addr, 0x5306) / 4) + 1; +} + +static int set_denoise(sensor_t *sensor, int level) +{ + int ret = 0; + if (level < 0 || level > 8) { + return -1; + } + + ret = write_reg_bits(sensor->slv_addr, 0x5308, 0x10, level > 0); + if (ret == 0 && level > 0) { + ret = write_reg(sensor->slv_addr, 0x5306, (level - 1) * 4); + } + + if (ret == 0) { + ESP_LOGD(TAG, "Set denoise to: %d", level); + sensor->status.denoise = level; + } + return ret; +} + +static int get_reg(sensor_t *sensor, int reg, int mask) +{ + int ret = 0, ret2 = 0; + if(mask > 0xFF){ + ret = read_reg16(sensor->slv_addr, reg); + if(ret >= 0 && mask > 0xFFFF){ + ret2 = read_reg(sensor->slv_addr, reg+2); + if(ret2 >= 0){ + ret = (ret << 8) | ret2 ; + } else { + ret = ret2; + } + } + } else { + ret = read_reg(sensor->slv_addr, reg); + } + if(ret > 0){ + ret &= mask; + } + return ret; +} + +static int set_reg(sensor_t *sensor, int reg, int mask, int value) +{ + int ret = 0, ret2 = 0; + if(mask > 0xFF){ + ret = read_reg16(sensor->slv_addr, reg); + if(ret >= 0 && mask > 0xFFFF){ + ret2 = read_reg(sensor->slv_addr, reg+2); + if(ret2 >= 0){ + ret = (ret << 8) | ret2 ; + } else { + ret = ret2; + } + } + } else { + ret = read_reg(sensor->slv_addr, reg); + } + if(ret < 0){ + return ret; + } + value = (ret & ~mask) | (value & mask); + if(mask > 0xFFFF){ + ret = write_reg16(sensor->slv_addr, reg, value >> 8); + if(ret >= 0){ + ret = write_reg(sensor->slv_addr, reg+2, value & 0xFF); + } + } else if(mask > 0xFF){ + ret = write_reg16(sensor->slv_addr, reg, value); + } else { + ret = write_reg(sensor->slv_addr, reg, value); + } + return ret; +} + +static int set_res_raw(sensor_t *sensor, int startX, int startY, int endX, int endY, int offsetX, int offsetY, int totalX, int totalY, int outputX, int outputY, bool scale, bool binning) +{ + int ret = 0; + ret = write_addr_reg(sensor->slv_addr, X_ADDR_ST_H, startX, startY) + || write_addr_reg(sensor->slv_addr, X_ADDR_END_H, endX, endY) + || write_addr_reg(sensor->slv_addr, X_OFFSET_H, offsetX, offsetY) + || write_addr_reg(sensor->slv_addr, X_TOTAL_SIZE_H, totalX, totalY) + || write_addr_reg(sensor->slv_addr, X_OUTPUT_SIZE_H, outputX, outputY) + || write_reg_bits(sensor->slv_addr, ISP_CONTROL_01, 0x20, scale); + if(!ret){ + sensor->status.scale = scale; + sensor->status.binning = binning; + ret = set_image_options(sensor); + } + return ret; +} + +static int _set_pll(sensor_t *sensor, int bypass, int multiplier, int sys_div, int root_2x, int pre_div, int seld5, int pclk_manual, int pclk_div) +{ + return set_pll(sensor, bypass > 0, multiplier, sys_div, pre_div, root_2x > 0, seld5, pclk_manual > 0, pclk_div); +} + +esp_err_t xclk_timer_conf(int ledc_timer, int xclk_freq_hz); +static int set_xclk(sensor_t *sensor, int timer, int xclk) +{ + int ret = 0; + sensor->xclk_freq_hz = xclk * 1000000U; + ret = xclk_timer_conf(timer, sensor->xclk_freq_hz); + return ret; +} + +static int init_status(sensor_t *sensor) +{ + sensor->status.brightness = 0; + sensor->status.contrast = 0; + sensor->status.saturation = 0; + sensor->status.sharpness = (read_reg(sensor->slv_addr, 0x5303) / 8) - 3; + sensor->status.denoise = get_denoise(sensor); + sensor->status.ae_level = 0; + sensor->status.gainceiling = read_reg16(sensor->slv_addr, 0x3A18) & 0x3FF; + sensor->status.awb = check_reg_mask(sensor->slv_addr, ISP_CONTROL_01, 0x01); + sensor->status.dcw = !check_reg_mask(sensor->slv_addr, 0x5183, 0x80); + sensor->status.agc = !check_reg_mask(sensor->slv_addr, AEC_PK_MANUAL, AEC_PK_MANUAL_AGC_MANUALEN); + sensor->status.aec = !check_reg_mask(sensor->slv_addr, AEC_PK_MANUAL, AEC_PK_MANUAL_AEC_MANUALEN); + sensor->status.hmirror = check_reg_mask(sensor->slv_addr, TIMING_TC_REG21, TIMING_TC_REG21_HMIRROR); + sensor->status.vflip = check_reg_mask(sensor->slv_addr, TIMING_TC_REG20, TIMING_TC_REG20_VFLIP); + sensor->status.colorbar = check_reg_mask(sensor->slv_addr, PRE_ISP_TEST_SETTING_1, TEST_COLOR_BAR); + sensor->status.bpc = check_reg_mask(sensor->slv_addr, 0x5000, 0x04); + sensor->status.wpc = check_reg_mask(sensor->slv_addr, 0x5000, 0x02); + sensor->status.raw_gma = check_reg_mask(sensor->slv_addr, 0x5000, 0x20); + sensor->status.lenc = check_reg_mask(sensor->slv_addr, 0x5000, 0x80); + sensor->status.quality = read_reg(sensor->slv_addr, COMPRESSION_CTRL07) & 0x3f; + sensor->status.special_effect = 0; + sensor->status.wb_mode = 0; + sensor->status.awb_gain = check_reg_mask(sensor->slv_addr, 0x3406, 0x01); + sensor->status.agc_gain = get_agc_gain(sensor); + sensor->status.aec_value = get_aec_value(sensor); + sensor->status.aec2 = check_reg_mask(sensor->slv_addr, 0x3a00, 0x04); + return 0; +} + +int ov3660_init(sensor_t *sensor) +{ + sensor->reset = reset; + sensor->set_pixformat = set_pixformat; + sensor->set_framesize = set_framesize; + sensor->set_contrast = set_contrast; + sensor->set_brightness = set_brightness; + sensor->set_saturation = set_saturation; + sensor->set_sharpness = set_sharpness; + sensor->set_gainceiling = set_gainceiling; + sensor->set_quality = set_quality; + sensor->set_colorbar = set_colorbar; + sensor->set_gain_ctrl = set_gain_ctrl; + sensor->set_exposure_ctrl = set_exposure_ctrl; + sensor->set_whitebal = set_whitebal; + sensor->set_hmirror = set_hmirror; + sensor->set_vflip = set_vflip; + sensor->init_status = init_status; + sensor->set_aec2 = set_aec2; + sensor->set_aec_value = set_aec_value; + sensor->set_special_effect = set_special_effect; + sensor->set_wb_mode = set_wb_mode; + sensor->set_ae_level = set_ae_level; + sensor->set_dcw = set_dcw_dsp; + sensor->set_bpc = set_bpc_dsp; + sensor->set_wpc = set_wpc_dsp; + sensor->set_awb_gain = set_awb_gain_dsp; + sensor->set_agc_gain = set_agc_gain; + sensor->set_raw_gma = set_raw_gma_dsp; + sensor->set_lenc = set_lenc_dsp; + sensor->set_denoise = set_denoise; + + sensor->get_reg = get_reg; + sensor->set_reg = set_reg; + sensor->set_res_raw = set_res_raw; + sensor->set_pll = _set_pll; + sensor->set_xclk = set_xclk; + return 0; +} diff --git a/esp32-cam-rtos-allframes/ov3660.h b/esp32-cam-rtos-allframes/ov3660.h new file mode 100644 index 0000000..8e5ae3c --- /dev/null +++ b/esp32-cam-rtos-allframes/ov3660.h @@ -0,0 +1,16 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * OV3660 driver. + * + */ +#ifndef __OV3660_H__ +#define __OV3660_H__ + +#include "sensor.h" + +int ov3660_init(sensor_t *sensor); + +#endif // __OV3660_H__ diff --git a/esp32-cam-rtos-allframes/ov3660_regs.h b/esp32-cam-rtos-allframes/ov3660_regs.h new file mode 100644 index 0000000..b5cf30a --- /dev/null +++ b/esp32-cam-rtos-allframes/ov3660_regs.h @@ -0,0 +1,211 @@ +/* + * OV3660 register definitions. + */ +#ifndef __OV3660_REG_REGS_H__ +#define __OV3660_REG_REGS_H__ + +/* system control registers */ +#define SYSTEM_CTROL0 0x3008 // Bit[7]: Software reset + // Bit[6]: Software power down + // Bit[5]: Reserved + // Bit[4]: SRB clock SYNC enable + // Bit[3]: Isolation suspend select + // Bit[2:0]: Not used + +/* output format control registers */ +#define FORMAT_CTRL 0x501F // Format select + // Bit[2:0]: + // 000: YUV422 + // 001: RGB + // 010: Dither + // 011: RAW after DPC + // 101: RAW after CIP + +/* format control registers */ +#define FORMAT_CTRL00 0x4300 + +/* frame control registers */ +#define FRAME_CTRL01 0x4201 // Control Passed Frame Number When both ON and OFF number set to 0x00,frame control is in bypass mode + // Bit[7:4]: Not used + // Bit[3:0]: Frame ON number +#define FRAME_CTRL02 0x4202 // Control Masked Frame Number When both ON and OFF number set to 0x00,frame control is in bypass mode + // Bit[7:4]: Not used + // BIT[3:0]: Frame OFF number + +/* ISP top control registers */ +#define PRE_ISP_TEST_SETTING_1 0x503D // Bit[7]: Test enable + // 0: Test disable + // 1: Color bar enable + // Bit[6]: Rolling + // Bit[5]: Transparent + // Bit[4]: Square black and white + // Bit[3:2]: Color bar style + // 00: Standard 8 color bar + // 01: Gradual change at vertical mode 1 + // 10: Gradual change at horizontal + // 11: Gradual change at vertical mode 2 + // Bit[1:0]: Test select + // 00: Color bar + // 01: Random data + // 10: Square data + // 11: Black image + +//exposure = {0x3500[3:0], 0x3501[7:0], 0x3502[7:0]} / 16 × tROW + +/* AEC/AGC control functions */ +#define AEC_PK_MANUAL 0x3503 // AEC Manual Mode Control + // Bit[7:6]: Reserved + // Bit[5]: Gain delay option + // Valid when 0x3503[4]=1’b0 + // 0: Delay one frame latch + // 1: One frame latch + // Bit[4:2]: Reserved + // Bit[1]: AGC manual + // 0: Auto enable + // 1: Manual enable + // Bit[0]: AEC manual + // 0: Auto enable + // 1: Manual enable + +//gain = {0x350A[1:0], 0x350B[7:0]} / 16 + +/* mirror and flip registers */ +#define TIMING_TC_REG20 0x3820 // Timing Control Register + // Bit[2:1]: Vertical flip enable + // 00: Normal + // 11: Vertical flip + // Bit[0]: Vertical binning enable +#define TIMING_TC_REG21 0x3821 // Timing Control Register + // Bit[5]: Compression Enable + // Bit[2:1]: Horizontal mirror enable + // 00: Normal + // 11: Horizontal mirror + // Bit[0]: Horizontal binning enable + +#define CLOCK_POL_CONTROL 0x4740// Bit[5]: PCLK polarity 0: active low + // 1: active high + // Bit[3]: Gate PCLK under VSYNC + // Bit[2]: Gate PCLK under HREF + // Bit[1]: HREF polarity + // 0: active low + // 1: active high + // Bit[0] VSYNC polarity + // 0: active low + // 1: active high +#define DRIVE_CAPABILITY 0x302c // Bit[7:6]: + // 00: 1x + // 01: 2x + // 10: 3x + // 11: 4x + + +#define X_ADDR_ST_H 0x3800 //Bit[3:0]: X address start[11:8] +#define X_ADDR_ST_L 0x3801 //Bit[7:0]: X address start[7:0] +#define Y_ADDR_ST_H 0x3802 //Bit[2:0]: Y address start[10:8] +#define Y_ADDR_ST_L 0x3803 //Bit[7:0]: Y address start[7:0] +#define X_ADDR_END_H 0x3804 //Bit[3:0]: X address end[11:8] +#define X_ADDR_END_L 0x3805 //Bit[7:0]: +#define Y_ADDR_END_H 0x3806 //Bit[2:0]: Y address end[10:8] +#define Y_ADDR_END_L 0x3807 //Bit[7:0]: +// Size after scaling +#define X_OUTPUT_SIZE_H 0x3808 //Bit[3:0]: DVP output horizontal width[11:8] +#define X_OUTPUT_SIZE_L 0x3809 //Bit[7:0]: +#define Y_OUTPUT_SIZE_H 0x380a //Bit[2:0]: DVP output vertical height[10:8] +#define Y_OUTPUT_SIZE_L 0x380b //Bit[7:0]: +#define X_TOTAL_SIZE_H 0x380c //Bit[3:0]: Total horizontal size[11:8] +#define X_TOTAL_SIZE_L 0x380d //Bit[7:0]: +#define Y_TOTAL_SIZE_H 0x380e //Bit[7:0]: Total vertical size[15:8] +#define Y_TOTAL_SIZE_L 0x380f //Bit[7:0]: +#define X_OFFSET_H 0x3810 //Bit[3:0]: ISP horizontal offset[11:8] +#define X_OFFSET_L 0x3811 //Bit[7:0]: +#define Y_OFFSET_H 0x3812 //Bit[2:0]: ISP vertical offset[10:8] +#define Y_OFFSET_L 0x3813 //Bit[7:0]: +#define X_INCREMENT 0x3814 //Bit[7:4]: Horizontal odd subsample increment + //Bit[3:0]: Horizontal even subsample increment +#define Y_INCREMENT 0x3815 //Bit[7:4]: Vertical odd subsample increment + //Bit[3:0]: Vertical even subsample increment +// Size before scaling +//#define X_INPUT_SIZE (X_ADDR_END - X_ADDR_ST + 1 - (2 * X_OFFSET)) +//#define Y_INPUT_SIZE (Y_ADDR_END - Y_ADDR_ST + 1 - (2 * Y_OFFSET)) + +#define ISP_CONTROL_01 0x5001 // Bit[5]: Scale enable + // 0: Disable + // 1: Enable + +#define SCALE_CTRL_1 0x5601 // Bit[6:4]: HDIV RW + // DCW scale times + // 000: DCW 1 time + // 001: DCW 2 times + // 010: DCW 4 times + // 100: DCW 8 times + // 101: DCW 16 times + // Others: DCW 16 times + // Bit[2:0]: VDIV RW + // DCW scale times + // 000: DCW 1 time + // 001: DCW 2 times + // 010: DCW 4 times + // 100: DCW 8 times + // 101: DCW 16 times + // Others: DCW 16 times + +#define SCALE_CTRL_2 0x5602 // X_SCALE High Bits +#define SCALE_CTRL_3 0x5603 // X_SCALE Low Bits +#define SCALE_CTRL_4 0x5604 // Y_SCALE High Bits +#define SCALE_CTRL_5 0x5605 // Y_SCALE Low Bits +#define SCALE_CTRL_6 0x5606 // Bit[3:0]: V Offset + +#define PCLK_RATIO 0x3824 // Bit[4:0]: PCLK ratio manual +#define VFIFO_CTRL0C 0x460C // Bit[1]: PCLK manual enable + // 0: Auto + // 1: Manual by PCLK_RATIO + +#define VFIFO_X_SIZE_H 0x4602 +#define VFIFO_X_SIZE_L 0x4603 +#define VFIFO_Y_SIZE_H 0x4604 +#define VFIFO_Y_SIZE_L 0x4605 + +#define SC_PLLS_CTRL0 0x303a // Bit[7]: PLLS bypass +#define SC_PLLS_CTRL1 0x303b // Bit[4:0]: PLLS multiplier +#define SC_PLLS_CTRL2 0x303c // Bit[6:4]: PLLS charge pump control + // Bit[3:0]: PLLS system divider +#define SC_PLLS_CTRL3 0x303d // Bit[5:4]: PLLS pre-divider + // 00: 1 + // 01: 1.5 + // 10: 2 + // 11: 3 + // Bit[2]: PLLS root-divider - 1 + // Bit[1:0]: PLLS seld5 + // 00: 1 + // 01: 1 + // 10: 2 + // 11: 2.5 + +#define COMPRESSION_CTRL00 0x4400 // +#define COMPRESSION_CTRL01 0x4401 // +#define COMPRESSION_CTRL02 0x4402 // +#define COMPRESSION_CTRL03 0x4403 // +#define COMPRESSION_CTRL04 0x4404 // +#define COMPRESSION_CTRL05 0x4405 // +#define COMPRESSION_CTRL06 0x4406 // +#define COMPRESSION_CTRL07 0x4407 // Bit[5:0]: QS +#define COMPRESSION_ISI_CTRL 0x4408 // +#define COMPRESSION_CTRL09 0x4409 // +#define COMPRESSION_CTRL0a 0x440a // +#define COMPRESSION_CTRL0b 0x440b // +#define COMPRESSION_CTRL0c 0x440c // +#define COMPRESSION_CTRL0d 0x440d // +#define COMPRESSION_CTRL0E 0x440e // + +/** + * @brief register value + */ +#define TEST_COLOR_BAR 0xC0 /* Enable Color Bar roling Test */ + +#define AEC_PK_MANUAL_AGC_MANUALEN 0x02 /* Enable AGC Manual enable */ +#define AEC_PK_MANUAL_AEC_MANUALEN 0x01 /* Enable AEC Manual enable */ + +#define TIMING_TC_REG20_VFLIP 0x06 /* Vertical flip enable */ +#define TIMING_TC_REG21_HMIRROR 0x06 /* Horizontal mirror enable */ + +#endif // __OV3660_REG_REGS_H__ diff --git a/esp32-cam-rtos-allframes/ov3660_settings.h b/esp32-cam-rtos-allframes/ov3660_settings.h new file mode 100644 index 0000000..97c4e03 --- /dev/null +++ b/esp32-cam-rtos-allframes/ov3660_settings.h @@ -0,0 +1,318 @@ +#ifndef _OV3660_SETTINGS_H_ +#define _OV3660_SETTINGS_H_ + +#include +#include +#include "esp_attr.h" +#include "ov3660_regs.h" + +static const ratio_settings_t ratio_table[] = { + // mw, mh, sx, sy, ex, ey, ox, oy, tx, ty + { 2048, 1536, 0, 0, 2079, 1547, 16, 6, 2300, 1564 }, //4x3 + { 1920, 1280, 64, 128, 2015, 1419, 16, 6, 2172, 1436 }, //3x2 + { 2048, 1280, 0, 128, 2079, 1419, 16, 6, 2300, 1436 }, //16x10 + { 1920, 1152, 64, 192, 2015, 1355, 16, 6, 2172, 1372 }, //5x3 + { 1920, 1080, 64, 242, 2015, 1333, 16, 6, 2172, 1322 }, //16x9 + { 2048, 880, 0, 328, 2079, 1219, 16, 6, 2300, 1236 }, //21x9 + { 1920, 1536, 64, 0, 2015, 1547, 16, 6, 2172, 1564 }, //5x4 + { 1536, 1536, 256, 0, 1823, 1547, 16, 6, 2044, 1564 }, //1x1 + { 864, 1536, 592, 0, 1487, 1547, 16, 6, 2044, 1564 } //9x16 +}; + +#define REG_DLY 0xffff +#define REGLIST_TAIL 0x0000 + +static const DRAM_ATTR uint16_t sensor_default_regs[][2] = { + {SYSTEM_CTROL0, 0x82}, // software reset + {REG_DLY, 10}, // delay 10ms + + {0x3103, 0x13}, + {SYSTEM_CTROL0, 0x42}, + {0x3017, 0xff}, + {0x3018, 0xff}, + {DRIVE_CAPABILITY, 0xc3}, + {CLOCK_POL_CONTROL, 0x21}, + + {0x3611, 0x01}, + {0x3612, 0x2d}, + + {0x3032, 0x00}, + {0x3614, 0x80}, + {0x3618, 0x00}, + {0x3619, 0x75}, + {0x3622, 0x80}, + {0x3623, 0x00}, + {0x3624, 0x03}, + {0x3630, 0x52}, + {0x3632, 0x07}, + {0x3633, 0xd2}, + {0x3704, 0x80}, + {0x3708, 0x66}, + {0x3709, 0x12}, + {0x370b, 0x12}, + {0x3717, 0x00}, + {0x371b, 0x60}, + {0x371c, 0x00}, + {0x3901, 0x13}, + + {0x3600, 0x08}, + {0x3620, 0x43}, + {0x3702, 0x20}, + {0x3739, 0x48}, + {0x3730, 0x20}, + {0x370c, 0x0c}, + + {0x3a18, 0x00}, + {0x3a19, 0xf8}, + + {0x3000, 0x10}, + {0x3004, 0xef}, + + {0x6700, 0x05}, + {0x6701, 0x19}, + {0x6702, 0xfd}, + {0x6703, 0xd1}, + {0x6704, 0xff}, + {0x6705, 0xff}, + + {0x3c01, 0x80}, + {0x3c00, 0x04}, + {0x3a08, 0x00}, {0x3a09, 0x62}, //50Hz Band Width Step (10bit) + {0x3a0e, 0x08}, //50Hz Max Bands in One Frame (6 bit) + {0x3a0a, 0x00}, {0x3a0b, 0x52}, //60Hz Band Width Step (10bit) + {0x3a0d, 0x09}, //60Hz Max Bands in One Frame (6 bit) + + {0x3a00, 0x3a},//night mode off + {0x3a14, 0x09}, + {0x3a15, 0x30}, + {0x3a02, 0x09}, + {0x3a03, 0x30}, + + {COMPRESSION_CTRL0E, 0x08}, + {0x4520, 0x0b}, + {0x460b, 0x37}, + {0x4713, 0x02}, + {0x471c, 0xd0}, + {0x5086, 0x00}, + + {0x5002, 0x00}, + {0x501f, 0x00}, + + {SYSTEM_CTROL0, 0x02}, + + {0x5180, 0xff}, + {0x5181, 0xf2}, + {0x5182, 0x00}, + {0x5183, 0x14}, + {0x5184, 0x25}, + {0x5185, 0x24}, + {0x5186, 0x16}, + {0x5187, 0x16}, + {0x5188, 0x16}, + {0x5189, 0x68}, + {0x518a, 0x60}, + {0x518b, 0xe0}, + {0x518c, 0xb2}, + {0x518d, 0x42}, + {0x518e, 0x35}, + {0x518f, 0x56}, + {0x5190, 0x56}, + {0x5191, 0xf8}, + {0x5192, 0x04}, + {0x5193, 0x70}, + {0x5194, 0xf0}, + {0x5195, 0xf0}, + {0x5196, 0x03}, + {0x5197, 0x01}, + {0x5198, 0x04}, + {0x5199, 0x12}, + {0x519a, 0x04}, + {0x519b, 0x00}, + {0x519c, 0x06}, + {0x519d, 0x82}, + {0x519e, 0x38}, + + {0x5381, 0x1d}, + {0x5382, 0x60}, + {0x5383, 0x03}, + {0x5384, 0x0c}, + {0x5385, 0x78}, + {0x5386, 0x84}, + {0x5387, 0x7d}, + {0x5388, 0x6b}, + {0x5389, 0x12}, + {0x538a, 0x01}, + {0x538b, 0x98}, + + {0x5480, 0x01}, +// {0x5481, 0x05}, +// {0x5482, 0x09}, +// {0x5483, 0x10}, +// {0x5484, 0x3a}, +// {0x5485, 0x4c}, +// {0x5486, 0x5a}, +// {0x5487, 0x68}, +// {0x5488, 0x74}, +// {0x5489, 0x80}, +// {0x548a, 0x8e}, +// {0x548b, 0xa4}, +// {0x548c, 0xb4}, +// {0x548d, 0xc8}, +// {0x548e, 0xde}, +// {0x548f, 0xf0}, +// {0x5490, 0x15}, + + {0x5000, 0xa7}, + {0x5800, 0x0C}, + {0x5801, 0x09}, + {0x5802, 0x0C}, + {0x5803, 0x0C}, + {0x5804, 0x0D}, + {0x5805, 0x17}, + {0x5806, 0x06}, + {0x5807, 0x05}, + {0x5808, 0x04}, + {0x5809, 0x06}, + {0x580a, 0x09}, + {0x580b, 0x0E}, + {0x580c, 0x05}, + {0x580d, 0x01}, + {0x580e, 0x01}, + {0x580f, 0x01}, + {0x5810, 0x05}, + {0x5811, 0x0D}, + {0x5812, 0x05}, + {0x5813, 0x01}, + {0x5814, 0x01}, + {0x5815, 0x01}, + {0x5816, 0x05}, + {0x5817, 0x0D}, + {0x5818, 0x08}, + {0x5819, 0x06}, + {0x581a, 0x05}, + {0x581b, 0x07}, + {0x581c, 0x0B}, + {0x581d, 0x0D}, + {0x581e, 0x12}, + {0x581f, 0x0D}, + {0x5820, 0x0E}, + {0x5821, 0x10}, + {0x5822, 0x10}, + {0x5823, 0x1E}, + {0x5824, 0x53}, + {0x5825, 0x15}, + {0x5826, 0x05}, + {0x5827, 0x14}, + {0x5828, 0x54}, + {0x5829, 0x25}, + {0x582a, 0x33}, + {0x582b, 0x33}, + {0x582c, 0x34}, + {0x582d, 0x16}, + {0x582e, 0x24}, + {0x582f, 0x41}, + {0x5830, 0x50}, + {0x5831, 0x42}, + {0x5832, 0x15}, + {0x5833, 0x25}, + {0x5834, 0x34}, + {0x5835, 0x33}, + {0x5836, 0x24}, + {0x5837, 0x26}, + {0x5838, 0x54}, + {0x5839, 0x25}, + {0x583a, 0x15}, + {0x583b, 0x25}, + {0x583c, 0x53}, + {0x583d, 0xCF}, + + {0x3a0f, 0x30}, + {0x3a10, 0x28}, + {0x3a1b, 0x30}, + {0x3a1e, 0x28}, + {0x3a11, 0x60}, + {0x3a1f, 0x14}, + + {0x5302, 0x28}, + {0x5303, 0x20}, + + {0x5306, 0x1c}, //de-noise offset 1 + {0x5307, 0x28}, //de-noise offset 2 + + {0x4002, 0xc5}, + {0x4003, 0x81}, + {0x4005, 0x12}, + + {0x5688, 0x11}, + {0x5689, 0x11}, + {0x568a, 0x11}, + {0x568b, 0x11}, + {0x568c, 0x11}, + {0x568d, 0x11}, + {0x568e, 0x11}, + {0x568f, 0x11}, + + {0x5580, 0x06}, + {0x5588, 0x00}, + {0x5583, 0x40}, + {0x5584, 0x2c}, + + {ISP_CONTROL_01, 0x83}, // turn color matrix, awb and SDE + {REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_fmt_jpeg[][2] = { + {FORMAT_CTRL, 0x00}, // YUV422 + {FORMAT_CTRL00, 0x30}, // YUYV + {0x3002, 0x00},//0x1c to 0x00 !!! + {0x3006, 0xff},//0xc3 to 0xff !!! + {0x471c, 0x50},//0xd0 to 0x50 !!! + {REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_fmt_raw[][2] = { + {FORMAT_CTRL00, 0x00}, // RAW + {REGLIST_TAIL, 0x00} +}; + +static const DRAM_ATTR uint16_t sensor_fmt_grayscale[][2] = { + {FORMAT_CTRL, 0x00}, // YUV422 + {FORMAT_CTRL00, 0x10}, // Y8 + {REGLIST_TAIL, 0x00} +}; + +static const DRAM_ATTR uint16_t sensor_fmt_yuv422[][2] = { + {FORMAT_CTRL, 0x00}, // YUV422 + {FORMAT_CTRL00, 0x30}, // YUYV + {REGLIST_TAIL, 0x00} +}; + +static const DRAM_ATTR uint16_t sensor_fmt_rgb565[][2] = { + {FORMAT_CTRL, 0x01}, // RGB + {FORMAT_CTRL00, 0x61}, // RGB565 (BGR) + {REGLIST_TAIL, 0x00} +}; + +static const DRAM_ATTR uint8_t sensor_saturation_levels[9][11] = { + {0x1d, 0x60, 0x03, 0x07, 0x48, 0x4f, 0x4b, 0x40, 0x0b, 0x01, 0x98},//-4 + {0x1d, 0x60, 0x03, 0x08, 0x54, 0x5c, 0x58, 0x4b, 0x0d, 0x01, 0x98},//-3 + {0x1d, 0x60, 0x03, 0x0a, 0x60, 0x6a, 0x64, 0x56, 0x0e, 0x01, 0x98},//-2 + {0x1d, 0x60, 0x03, 0x0b, 0x6c, 0x77, 0x70, 0x60, 0x10, 0x01, 0x98},//-1 + {0x1d, 0x60, 0x03, 0x0c, 0x78, 0x84, 0x7d, 0x6b, 0x12, 0x01, 0x98},//0 + {0x1d, 0x60, 0x03, 0x0d, 0x84, 0x91, 0x8a, 0x76, 0x14, 0x01, 0x98},//+1 + {0x1d, 0x60, 0x03, 0x0e, 0x90, 0x9e, 0x96, 0x80, 0x16, 0x01, 0x98},//+2 + {0x1d, 0x60, 0x03, 0x10, 0x9c, 0xac, 0xa2, 0x8b, 0x17, 0x01, 0x98},//+3 + {0x1d, 0x60, 0x03, 0x11, 0xa8, 0xb9, 0xaf, 0x96, 0x19, 0x01, 0x98},//+4 +}; + +static const DRAM_ATTR uint8_t sensor_special_effects[7][4] = { + {0x06, 0x40, 0x2c, 0x08},//Normal + {0x46, 0x40, 0x28, 0x08},//Negative + {0x1e, 0x80, 0x80, 0x08},//Grayscale + {0x1e, 0x80, 0xc0, 0x08},//Red Tint + {0x1e, 0x60, 0x60, 0x08},//Green Tint + {0x1e, 0xa0, 0x40, 0x08},//Blue Tint + {0x1e, 0x40, 0xa0, 0x08},//Sepia +}; + +#endif diff --git a/esp32-cam-rtos-allframes/ov5640.c b/esp32-cam-rtos-allframes/ov5640.c new file mode 100644 index 0000000..e7adcf4 --- /dev/null +++ b/esp32-cam-rtos-allframes/ov5640.c @@ -0,0 +1,1105 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * OV3660 driver. + * + */ +#include +#include +#include +#include "sccb.h" +#include "ov5640.h" +#include "ov5640_regs.h" +#include "ov5640_settings.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#else +#include "esp_log.h" +static const char *TAG = "ov5640"; +#endif + +//#define REG_DEBUG_ON + +static int read_reg(uint8_t slv_addr, const uint16_t reg){ + int ret = SCCB_Read16(slv_addr, reg); +#ifdef REG_DEBUG_ON + if (ret < 0) { + ESP_LOGE(TAG, "READ REG 0x%04x FAILED: %d", reg, ret); + } +#endif + return ret; +} + +static int check_reg_mask(uint8_t slv_addr, uint16_t reg, uint8_t mask){ + return (read_reg(slv_addr, reg) & mask) == mask; +} + +static int read_reg16(uint8_t slv_addr, const uint16_t reg){ + int ret = 0, ret2 = 0; + ret = read_reg(slv_addr, reg); + if (ret >= 0) { + ret = (ret & 0xFF) << 8; + ret2 = read_reg(slv_addr, reg+1); + if (ret2 < 0) { + ret = ret2; + } else { + ret |= ret2 & 0xFF; + } + } + return ret; +} + +//static void dump_reg(sensor_t *sensor, const uint16_t reg){ +// int v = SCCB_Read16(sensor->slv_addr, reg); +// if(v < 0){ +// ets_printf(" 0x%04x: FAIL[%d]\n", reg, v); +// } else { +// ets_printf(" 0x%04x: 0x%02X\n", reg, v); +// } +//} +// +//static void dump_range(sensor_t *sensor, const char * name, const uint16_t start_reg, const uint16_t end_reg){ +// ets_printf("%s: 0x%04x - 0x%04X\n", name, start_reg, end_reg); +// for(uint16_t reg = start_reg; reg <= end_reg; reg++){ +// dump_reg(sensor, reg); +// } +//} +// +//static void dump_regs(sensor_t *sensor){ +//// dump_range(sensor, "All Regs", 0x3000, 0x6100); +//// dump_range(sensor, "system and IO pad control", 0x3000, 0x3052); +//// dump_range(sensor, "SCCB control", 0x3100, 0x3108); +//// dump_range(sensor, "SRB control", 0x3200, 0x3211); +//// dump_range(sensor, "AWB gain control", 0x3400, 0x3406); +//// dump_range(sensor, "AEC/AGC control", 0x3500, 0x350D); +//// dump_range(sensor, "VCM control", 0x3600, 0x3606); +//// dump_range(sensor, "timing control", 0x3800, 0x3821); +//// dump_range(sensor, "AEC/AGC power down domain control", 0x3A00, 0x3A25); +//// dump_range(sensor, "strobe control", 0x3B00, 0x3B0C); +//// dump_range(sensor, "50/60Hz detector control", 0x3C00, 0x3C1E); +//// dump_range(sensor, "OTP control", 0x3D00, 0x3D21); +//// dump_range(sensor, "MC control", 0x3F00, 0x3F0D); +//// dump_range(sensor, "BLC control", 0x4000, 0x4033); +//// dump_range(sensor, "frame control", 0x4201, 0x4202); +//// dump_range(sensor, "format control", 0x4300, 0x430D); +//// dump_range(sensor, "JPEG control", 0x4400, 0x4431); +//// dump_range(sensor, "VFIFO control", 0x4600, 0x460D); +//// dump_range(sensor, "DVP control", 0x4709, 0x4745); +//// dump_range(sensor, "MIPI control", 0x4800, 0x4837); +//// dump_range(sensor, "ISP frame control", 0x4901, 0x4902); +//// dump_range(sensor, "ISP top control", 0x5000, 0x5063); +//// dump_range(sensor, "AWB control", 0x5180, 0x51D0); +//// dump_range(sensor, "CIP control", 0x5300, 0x530F); +//// dump_range(sensor, "CMX control", 0x5380, 0x538B); +//// dump_range(sensor, "gamma control", 0x5480, 0x5490); +//// dump_range(sensor, "SDE control", 0x5580, 0x558C); +//// dump_range(sensor, "scale control", 0x5600, 0x5606); +//// dump_range(sensor, "AVG control", 0x5680, 0x56A2); +//// dump_range(sensor, "LENC control", 0x5800, 0x5849); +//// dump_range(sensor, "AFC control", 0x6000, 0x603F); +//} + +static int write_reg(uint8_t slv_addr, const uint16_t reg, uint8_t value){ + int ret = 0; +#ifndef REG_DEBUG_ON + ret = SCCB_Write16(slv_addr, reg, value); +#else + int old_value = read_reg(slv_addr, reg); + if (old_value < 0) { + return old_value; + } + if ((uint8_t)old_value != value) { + ESP_LOGI(TAG, "NEW REG 0x%04x: 0x%02x to 0x%02x", reg, (uint8_t)old_value, value); + ret = SCCB_Write16(slv_addr, reg, value); + } else { + ESP_LOGD(TAG, "OLD REG 0x%04x: 0x%02x", reg, (uint8_t)old_value); + ret = SCCB_Write16(slv_addr, reg, value);//maybe not? + } + if (ret < 0) { + ESP_LOGE(TAG, "WRITE REG 0x%04x FAILED: %d", reg, ret); + } +#endif + return ret; +} + +static int set_reg_bits(uint8_t slv_addr, uint16_t reg, uint8_t offset, uint8_t mask, uint8_t value) +{ + int ret = 0; + uint8_t c_value, new_value; + ret = read_reg(slv_addr, reg); + if(ret < 0) { + return ret; + } + c_value = ret; + new_value = (c_value & ~(mask << offset)) | ((value & mask) << offset); + ret = write_reg(slv_addr, reg, new_value); + return ret; +} + +static int write_regs(uint8_t slv_addr, const uint16_t (*regs)[2]) +{ + int i = 0, ret = 0; + while (!ret && regs[i][0] != REGLIST_TAIL) { + if (regs[i][0] == REG_DLY) { + vTaskDelay(regs[i][1] / portTICK_PERIOD_MS); + } else { + ret = write_reg(slv_addr, regs[i][0], regs[i][1]); + } + i++; + } + return ret; +} + +static int write_reg16(uint8_t slv_addr, const uint16_t reg, uint16_t value) +{ + if (write_reg(slv_addr, reg, value >> 8) || write_reg(slv_addr, reg + 1, value)) { + return -1; + } + return 0; +} + +static int write_addr_reg(uint8_t slv_addr, const uint16_t reg, uint16_t x_value, uint16_t y_value) +{ + if (write_reg16(slv_addr, reg, x_value) || write_reg16(slv_addr, reg + 2, y_value)) { + return -1; + } + return 0; +} + +#define write_reg_bits(slv_addr, reg, mask, enable) set_reg_bits(slv_addr, reg, 0, mask, (enable)?(mask):0) + +static int calc_sysclk(int xclk, bool pll_bypass, int pll_multiplier, int pll_sys_div, int pre_div, bool root_2x, int pclk_root_div, bool pclk_manual, int pclk_div) +{ + const float pll_pre_div2x_map[] = { 1, 1, 2, 3, 4, 1.5, 6, 2.5, 8}; + const int pll_pclk_root_div_map[] = { 1, 2, 4, 8 }; + + if(!pll_sys_div) { + pll_sys_div = 1; + } + + float pll_pre_div = pll_pre_div2x_map[pre_div]; + unsigned int root_2x_div = root_2x?2:1; + unsigned int pll_pclk_root_div = pll_pclk_root_div_map[pclk_root_div]; + + unsigned int REFIN = xclk / pll_pre_div; + + unsigned int VCO = REFIN * pll_multiplier / root_2x_div; + + unsigned int PLL_CLK = pll_bypass?(xclk):(VCO / pll_sys_div * 2 / 5);//5 here is 10bit mode / 2, for 8bit it should be 4 (reg 0x3034) + + unsigned int PCLK = PLL_CLK / pll_pclk_root_div / ((pclk_manual && pclk_div)?pclk_div:2); + + unsigned int SYSCLK = PLL_CLK / 4; + + ESP_LOGD(TAG, "Calculated XVCLK: %d Hz, REFIN: %u Hz, VCO: %u Hz, PLL_CLK: %u Hz, SYSCLK: %u Hz, PCLK: %u Hz", xclk, REFIN, VCO, PLL_CLK, SYSCLK, PCLK); + return SYSCLK; +} + +static int set_pll(sensor_t *sensor, bool bypass, uint8_t multiplier, uint8_t sys_div, uint8_t pre_div, bool root_2x, uint8_t pclk_root_div, bool pclk_manual, uint8_t pclk_div){ + int ret = 0; + if(multiplier > 252 || multiplier < 4 || sys_div > 15 || pre_div > 8 || pclk_div > 31 || pclk_root_div > 3){ + ESP_LOGE(TAG, "Invalid arguments"); + return -1; + } + if(multiplier > 127){ + multiplier &= 0xFE;//only even integers above 127 + } + + calc_sysclk(sensor->xclk_freq_hz, bypass, multiplier, sys_div, pre_div, root_2x, pclk_root_div, pclk_manual, pclk_div); + + ret = write_reg(sensor->slv_addr, 0x3039, bypass?0x80:0x00); + if (ret == 0) { + ret = write_reg(sensor->slv_addr, 0x3034, 0x1A);//10bit mode + } + if (ret == 0) { + ret = write_reg(sensor->slv_addr, 0x3035, 0x01 | ((sys_div & 0x0f) << 4)); + } + if (ret == 0) { + ret = write_reg(sensor->slv_addr, 0x3036, multiplier & 0xff); + } + if (ret == 0) { + ret = write_reg(sensor->slv_addr, 0x3037, (pre_div & 0xf) | (root_2x?0x10:0x00)); + } + if (ret == 0) { + ret = write_reg(sensor->slv_addr, 0x3108, (pclk_root_div & 0x3) << 4 | 0x06); + } + if (ret == 0) { + ret = write_reg(sensor->slv_addr, 0x3824, pclk_div & 0x1f); + } + if (ret == 0) { + ret = write_reg(sensor->slv_addr, 0x460C, pclk_manual?0x22:0x20); + } + if (ret == 0) { + ret = write_reg(sensor->slv_addr, 0x3103, 0x13);// system clock from pll, bit[1] + } + if(ret){ + ESP_LOGE(TAG, "set_sensor_pll FAILED!"); + } + return ret; +} + +static int set_ae_level(sensor_t *sensor, int level); + +static int reset(sensor_t *sensor) +{ + //dump_regs(sensor); + vTaskDelay(100 / portTICK_PERIOD_MS); + int ret = 0; + // Software Reset: clear all registers and reset them to their default values + ret = write_reg(sensor->slv_addr, SYSTEM_CTROL0, 0x82); + if(ret){ + ESP_LOGE(TAG, "Software Reset FAILED!"); + return ret; + } + vTaskDelay(100 / portTICK_PERIOD_MS); + ret = write_regs(sensor->slv_addr, sensor_default_regs); + if (ret == 0) { + ESP_LOGD(TAG, "Camera defaults loaded"); + vTaskDelay(100 / portTICK_PERIOD_MS); + //write_regs(sensor->slv_addr, sensor_regs_awb0); + //write_regs(sensor->slv_addr, sensor_regs_gamma1); + } + return ret; +} + +static int set_pixformat(sensor_t *sensor, pixformat_t pixformat) +{ + int ret = 0; + const uint16_t (*regs)[2]; + + switch (pixformat) { + case PIXFORMAT_YUV422: + regs = sensor_fmt_yuv422; + break; + + case PIXFORMAT_GRAYSCALE: + regs = sensor_fmt_grayscale; + break; + + case PIXFORMAT_RGB565: + case PIXFORMAT_RGB888: + regs = sensor_fmt_rgb565; + break; + + case PIXFORMAT_JPEG: + regs = sensor_fmt_jpeg; + break; + + case PIXFORMAT_RAW: + regs = sensor_fmt_raw; + break; + + default: + ESP_LOGE(TAG, "Unsupported pixformat: %u", pixformat); + return -1; + } + + ret = write_regs(sensor->slv_addr, regs); + if(ret == 0) { + sensor->pixformat = pixformat; + ESP_LOGD(TAG, "Set pixformat to: %u", pixformat); + } + return ret; +} + +static int set_image_options(sensor_t *sensor) +{ + int ret = 0; + uint8_t reg20 = 0; + uint8_t reg21 = 0; + uint8_t reg4514 = 0; + uint8_t reg4514_test = 0; + + // compression + if (sensor->pixformat == PIXFORMAT_JPEG) { + reg21 |= 0x20; + } + + // binning + if (!sensor->status.binning) { + reg20 |= 0x40; + } else { + reg20 |= 0x01; + reg21 |= 0x01; + reg4514_test |= 4; + } + + // V-Flip + if (sensor->status.vflip) { + reg20 |= 0x06; + reg4514_test |= 1; + } + + // H-Mirror + if (sensor->status.hmirror) { + reg21 |= 0x06; + reg4514_test |= 2; + } + + switch (reg4514_test) { + //no binning + case 0: reg4514 = 0x88; break;//normal + case 1: reg4514 = 0x00; break;//v-flip + case 2: reg4514 = 0xbb; break;//h-mirror + case 3: reg4514 = 0x00; break;//v-flip+h-mirror + //binning + case 4: reg4514 = 0xaa; break;//normal + case 5: reg4514 = 0xbb; break;//v-flip + case 6: reg4514 = 0xbb; break;//h-mirror + case 7: reg4514 = 0xaa; break;//v-flip+h-mirror + } + + if(write_reg(sensor->slv_addr, TIMING_TC_REG20, reg20) + || write_reg(sensor->slv_addr, TIMING_TC_REG21, reg21) + || write_reg(sensor->slv_addr, 0x4514, reg4514)){ + ESP_LOGE(TAG, "Setting Image Options Failed"); + return -1; + } + + if (!sensor->status.binning) { + ret = write_reg(sensor->slv_addr, 0x4520, 0x10) + || write_reg(sensor->slv_addr, X_INCREMENT, 0x11)//odd:1, even: 1 + || write_reg(sensor->slv_addr, Y_INCREMENT, 0x11);//odd:1, even: 1 + } else { + ret = write_reg(sensor->slv_addr, 0x4520, 0x0b) + || write_reg(sensor->slv_addr, X_INCREMENT, 0x31)//odd:3, even: 1 + || write_reg(sensor->slv_addr, Y_INCREMENT, 0x31);//odd:3, even: 1 + } + + ESP_LOGD(TAG, "Set Image Options: Compression: %u, Binning: %u, V-Flip: %u, H-Mirror: %u, Reg-4514: 0x%02x", + sensor->pixformat == PIXFORMAT_JPEG, sensor->status.binning, sensor->status.vflip, sensor->status.hmirror, reg4514); + return ret; +} + +static int set_framesize(sensor_t *sensor, framesize_t framesize) +{ + int ret = 0; + framesize_t old_framesize = sensor->status.framesize; + sensor->status.framesize = framesize; + + if(framesize > FRAMESIZE_QSXGA){ + ESP_LOGE(TAG, "Invalid framesize: %u", framesize); + return -1; + } + uint16_t w = resolution[framesize].width; + uint16_t h = resolution[framesize].height; + aspect_ratio_t ratio = resolution[framesize].aspect_ratio; + ratio_settings_t settings = ratio_table[ratio]; + + sensor->status.binning = (w <= (settings.max_width / 2) && h <= (settings.max_height / 2)); + sensor->status.scale = !((w == settings.max_width && h == settings.max_height) + || (w == (settings.max_width / 2) && h == (settings.max_height / 2))); + + ret = write_addr_reg(sensor->slv_addr, X_ADDR_ST_H, settings.start_x, settings.start_y) + || write_addr_reg(sensor->slv_addr, X_ADDR_END_H, settings.end_x, settings.end_y) + || write_addr_reg(sensor->slv_addr, X_OUTPUT_SIZE_H, w, h); + + if (ret) { + goto fail; + } + + if (!sensor->status.binning) { + ret = write_addr_reg(sensor->slv_addr, X_TOTAL_SIZE_H, settings.total_x, settings.total_y) + || write_addr_reg(sensor->slv_addr, X_OFFSET_H, settings.offset_x, settings.offset_y); + } else { + if (w > 920) { + ret = write_addr_reg(sensor->slv_addr, X_TOTAL_SIZE_H, settings.total_x - 200, settings.total_y / 2); + } else { + ret = write_addr_reg(sensor->slv_addr, X_TOTAL_SIZE_H, 2060, settings.total_y / 2); + } + if (ret == 0) { + ret = write_addr_reg(sensor->slv_addr, X_OFFSET_H, settings.offset_x / 2, settings.offset_y / 2); + } + } + + if (ret == 0) { + ret = write_reg_bits(sensor->slv_addr, ISP_CONTROL_01, 0x20, sensor->status.scale); + } + + if (ret == 0) { + ret = set_image_options(sensor); + } + + if (ret) { + goto fail; + } + + if (sensor->pixformat == PIXFORMAT_JPEG) { + //10MHz PCLK + uint8_t sys_mul = 200; + if(framesize < FRAMESIZE_QVGA){ + sys_mul = 160; + } else if(framesize < FRAMESIZE_XGA){ + sys_mul = 180; + } + ret = set_pll(sensor, false, sys_mul, 4, 2, false, 2, true, 4); + } else { + ret = set_pll(sensor, false, 10, 1, 1, false, 1, true, 4); + } + + if (ret == 0) { + ESP_LOGD(TAG, "Set framesize to: %ux%u", w, h); + } + return ret; + +fail: + sensor->status.framesize = old_framesize; + ESP_LOGE(TAG, "Setting framesize to: %ux%u failed", w, h); + return ret; +} + +static int set_hmirror(sensor_t *sensor, int enable) +{ + int ret = 0; + sensor->status.hmirror = enable; + ret = set_image_options(sensor); + if (ret == 0) { + ESP_LOGD(TAG, "Set h-mirror to: %d", enable); + } + return ret; +} + +static int set_vflip(sensor_t *sensor, int enable) +{ + int ret = 0; + sensor->status.vflip = enable; + ret = set_image_options(sensor); + if (ret == 0) { + ESP_LOGD(TAG, "Set v-flip to: %d", enable); + } + return ret; +} + +static int set_quality(sensor_t *sensor, int qs) +{ + int ret = 0; + ret = write_reg(sensor->slv_addr, COMPRESSION_CTRL07, qs & 0x3f); + if (ret == 0) { + sensor->status.quality = qs; + ESP_LOGD(TAG, "Set quality to: %d", qs); + } + return ret; +} + +static int set_colorbar(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, PRE_ISP_TEST_SETTING_1, TEST_COLOR_BAR, enable); + if (ret == 0) { + sensor->status.colorbar = enable; + ESP_LOGD(TAG, "Set colorbar to: %d", enable); + } + return ret; +} + +static int set_gain_ctrl(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, AEC_PK_MANUAL, AEC_PK_MANUAL_AGC_MANUALEN, !enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set gain_ctrl to: %d", enable); + sensor->status.agc = enable; + } + return ret; +} + +static int set_exposure_ctrl(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, AEC_PK_MANUAL, AEC_PK_MANUAL_AEC_MANUALEN, !enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set exposure_ctrl to: %d", enable); + sensor->status.aec = enable; + } + return ret; +} + +static int set_whitebal(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, ISP_CONTROL_01, 0x01, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set awb to: %d", enable); + sensor->status.awb = enable; + } + return ret; +} + +//Advanced AWB +static int set_dcw_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, 0x5183, 0x80, !enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set dcw to: %d", enable); + sensor->status.dcw = enable; + } + return ret; +} + +//night mode enable +static int set_aec2(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, 0x3a00, 0x04, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set aec2 to: %d", enable); + sensor->status.aec2 = enable; + } + return ret; +} + +static int set_bpc_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, 0x5000, 0x04, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set bpc to: %d", enable); + sensor->status.bpc = enable; + } + return ret; +} + +static int set_wpc_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, 0x5000, 0x02, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set wpc to: %d", enable); + sensor->status.wpc = enable; + } + return ret; +} + +//Gamma enable +static int set_raw_gma_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, 0x5000, 0x20, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set raw_gma to: %d", enable); + sensor->status.raw_gma = enable; + } + return ret; +} + +static int set_lenc_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, 0x5000, 0x80, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set lenc to: %d", enable); + sensor->status.lenc = enable; + } + return ret; +} + +static int get_agc_gain(sensor_t *sensor) +{ + int ra = read_reg(sensor->slv_addr, 0x350a); + if (ra < 0) { + return 0; + } + int rb = read_reg(sensor->slv_addr, 0x350b); + if (rb < 0) { + return 0; + } + int res = (rb & 0xF0) >> 4 | (ra & 0x03) << 4; + if (rb & 0x0F) { + res += 1; + } + return res; +} + +//real gain +static int set_agc_gain(sensor_t *sensor, int gain) +{ + int ret = 0; + if(gain < 0) { + gain = 0; + } else if(gain > 64) { + gain = 64; + } + + //gain value is 6.4 bits float + //in order to use the max range, we deduct 1/16 + int gainv = gain << 4; + if(gainv){ + gainv -= 1; + } + + ret = write_reg(sensor->slv_addr, 0x350a, gainv >> 8) || write_reg(sensor->slv_addr, 0x350b, gainv & 0xff); + if (ret == 0) { + ESP_LOGD(TAG, "Set agc_gain to: %d", gain); + sensor->status.agc_gain = gain; + } + return ret; +} + +static int get_aec_value(sensor_t *sensor) +{ + int ra = read_reg(sensor->slv_addr, 0x3500); + if (ra < 0) { + return 0; + } + int rb = read_reg(sensor->slv_addr, 0x3501); + if (rb < 0) { + return 0; + } + int rc = read_reg(sensor->slv_addr, 0x3502); + if (rc < 0) { + return 0; + } + int res = (ra & 0x0F) << 12 | (rb & 0xFF) << 4 | (rc & 0xF0) >> 4; + return res; +} + +static int set_aec_value(sensor_t *sensor, int value) +{ + int ret = 0, max_val = 0; + max_val = read_reg16(sensor->slv_addr, 0x380e); + if (max_val < 0) { + ESP_LOGE(TAG, "Could not read max aec_value"); + return -1; + } + if (value > max_val) { + value =max_val; + } + + ret = write_reg(sensor->slv_addr, 0x3500, (value >> 12) & 0x0F) + || write_reg(sensor->slv_addr, 0x3501, (value >> 4) & 0xFF) + || write_reg(sensor->slv_addr, 0x3502, (value << 4) & 0xF0); + + if (ret == 0) { + ESP_LOGD(TAG, "Set aec_value to: %d / %d", value, max_val); + sensor->status.aec_value = value; + } + return ret; +} + +static int set_ae_level(sensor_t *sensor, int level) +{ + int ret = 0; + if (level < -5 || level > 5) { + return -1; + } + //good targets are between 5 and 115 + int target_level = ((level + 5) * 10) + 5; + + int level_high, level_low; + int fast_high, fast_low; + + level_low = target_level * 23 / 25; //0.92 (0.46) + level_high = target_level * 27 / 25; //1.08 (2.08) + + fast_low = level_low >> 1; + fast_high = level_high << 1; + + if(fast_high>255) { + fast_high = 255; + } + + ret = write_reg(sensor->slv_addr, 0x3a0f, level_high) + || write_reg(sensor->slv_addr, 0x3a10, level_low) + || write_reg(sensor->slv_addr, 0x3a1b, level_high) + || write_reg(sensor->slv_addr, 0x3a1e, level_low) + || write_reg(sensor->slv_addr, 0x3a11, fast_high) + || write_reg(sensor->slv_addr, 0x3a1f, fast_low); + + if (ret == 0) { + ESP_LOGD(TAG, "Set ae_level to: %d", level); + sensor->status.ae_level = level; + } + return ret; +} + +static int set_wb_mode(sensor_t *sensor, int mode) +{ + int ret = 0; + if (mode < 0 || mode > 4) { + return -1; + } + + ret = write_reg(sensor->slv_addr, 0x3406, (mode != 0)); + if (ret) { + return ret; + } + switch (mode) { + case 1://Sunny + ret = write_reg16(sensor->slv_addr, 0x3400, 0x5e0) //AWB R GAIN + || write_reg16(sensor->slv_addr, 0x3402, 0x410) //AWB G GAIN + || write_reg16(sensor->slv_addr, 0x3404, 0x540);//AWB B GAIN + break; + case 2://Cloudy + ret = write_reg16(sensor->slv_addr, 0x3400, 0x650) //AWB R GAIN + || write_reg16(sensor->slv_addr, 0x3402, 0x410) //AWB G GAIN + || write_reg16(sensor->slv_addr, 0x3404, 0x4f0);//AWB B GAIN + break; + case 3://Office + ret = write_reg16(sensor->slv_addr, 0x3400, 0x520) //AWB R GAIN + || write_reg16(sensor->slv_addr, 0x3402, 0x410) //AWB G GAIN + || write_reg16(sensor->slv_addr, 0x3404, 0x660);//AWB B GAIN + break; + case 4://HOME + ret = write_reg16(sensor->slv_addr, 0x3400, 0x420) //AWB R GAIN + || write_reg16(sensor->slv_addr, 0x3402, 0x3f0) //AWB G GAIN + || write_reg16(sensor->slv_addr, 0x3404, 0x710);//AWB B GAIN + break; + default://AUTO + break; + } + + if (ret == 0) { + ESP_LOGD(TAG, "Set wb_mode to: %d", mode); + sensor->status.wb_mode = mode; + } + return ret; +} + +static int set_awb_gain_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + int old_mode = sensor->status.wb_mode; + int mode = enable?old_mode:0; + + ret = set_wb_mode(sensor, mode); + + if (ret == 0) { + sensor->status.wb_mode = old_mode; + ESP_LOGD(TAG, "Set awb_gain to: %d", enable); + sensor->status.awb_gain = enable; + } + return ret; +} + +static int set_special_effect(sensor_t *sensor, int effect) +{ + int ret=0; + if (effect < 0 || effect > 6) { + return -1; + } + + uint8_t * regs = (uint8_t *)sensor_special_effects[effect]; + ret = write_reg(sensor->slv_addr, 0x5580, regs[0]) + || write_reg(sensor->slv_addr, 0x5583, regs[1]) + || write_reg(sensor->slv_addr, 0x5584, regs[2]) + || write_reg(sensor->slv_addr, 0x5003, regs[3]); + + if (ret == 0) { + ESP_LOGD(TAG, "Set special_effect to: %d", effect); + sensor->status.special_effect = effect; + } + return ret; +} + +static int set_brightness(sensor_t *sensor, int level) +{ + int ret = 0; + uint8_t value = 0; + bool negative = false; + + switch (level) { + case 3: + value = 0x30; + break; + case 2: + value = 0x20; + break; + case 1: + value = 0x10; + break; + case -1: + value = 0x10; + negative = true; + break; + case -2: + value = 0x20; + negative = true; + break; + case -3: + value = 0x30; + negative = true; + break; + default: // 0 + break; + } + + ret = write_reg(sensor->slv_addr, 0x5587, value); + if (ret == 0) { + ret = write_reg_bits(sensor->slv_addr, 0x5588, 0x08, negative); + } + + if (ret == 0) { + ESP_LOGD(TAG, "Set brightness to: %d", level); + sensor->status.brightness = level; + } + return ret; +} + +static int set_contrast(sensor_t *sensor, int level) +{ + int ret = 0; + if(level > 3 || level < -3) { + return -1; + } + ret = write_reg(sensor->slv_addr, 0x5586, (level + 4) << 3); + + if (ret == 0) { + ESP_LOGD(TAG, "Set contrast to: %d", level); + sensor->status.contrast = level; + } + return ret; +} + +static int set_saturation(sensor_t *sensor, int level) +{ + int ret = 0; + if(level > 4 || level < -4) { + return -1; + } + + uint8_t * regs = (uint8_t *)sensor_saturation_levels[level+4]; + for(int i=0; i<11; i++) { + ret = write_reg(sensor->slv_addr, 0x5381 + i, regs[i]); + if (ret) { + break; + } + } + + if (ret == 0) { + ESP_LOGD(TAG, "Set saturation to: %d", level); + sensor->status.saturation = level; + } + return ret; +} + +static int set_sharpness(sensor_t *sensor, int level) +{ + int ret = 0; + if(level > 3 || level < -3) { + return -1; + } + + uint8_t mt_offset_2 = (level + 3) * 8; + uint8_t mt_offset_1 = mt_offset_2 + 1; + + ret = write_reg_bits(sensor->slv_addr, 0x5308, 0x40, false)//0x40 means auto + || write_reg(sensor->slv_addr, 0x5300, 0x10) + || write_reg(sensor->slv_addr, 0x5301, 0x10) + || write_reg(sensor->slv_addr, 0x5302, mt_offset_1) + || write_reg(sensor->slv_addr, 0x5303, mt_offset_2) + || write_reg(sensor->slv_addr, 0x5309, 0x10) + || write_reg(sensor->slv_addr, 0x530a, 0x10) + || write_reg(sensor->slv_addr, 0x530b, 0x04) + || write_reg(sensor->slv_addr, 0x530c, 0x06); + + if (ret == 0) { + ESP_LOGD(TAG, "Set sharpness to: %d", level); + sensor->status.sharpness = level; + } + return ret; +} + +static int set_gainceiling(sensor_t *sensor, gainceiling_t level) +{ + int ret = 0, l = (int)level; + + ret = write_reg(sensor->slv_addr, 0x3A18, (l >> 8) & 3) + || write_reg(sensor->slv_addr, 0x3A19, l & 0xFF); + + if (ret == 0) { + ESP_LOGD(TAG, "Set gainceiling to: %d", l); + sensor->status.gainceiling = l; + } + return ret; +} + +static int get_denoise(sensor_t *sensor) +{ + if (!check_reg_mask(sensor->slv_addr, 0x5308, 0x10)) { + return 0; + } + return (read_reg(sensor->slv_addr, 0x5306) / 4) + 1; +} + +static int set_denoise(sensor_t *sensor, int level) +{ + int ret = 0; + if (level < 0 || level > 8) { + return -1; + } + + ret = write_reg_bits(sensor->slv_addr, 0x5308, 0x10, level > 0); + if (ret == 0 && level > 0) { + ret = write_reg(sensor->slv_addr, 0x5306, (level - 1) * 4); + } + + if (ret == 0) { + ESP_LOGD(TAG, "Set denoise to: %d", level); + sensor->status.denoise = level; + } + return ret; +} + +static int get_reg(sensor_t *sensor, int reg, int mask) +{ + int ret = 0, ret2 = 0; + if(mask > 0xFF){ + ret = read_reg16(sensor->slv_addr, reg); + if(ret >= 0 && mask > 0xFFFF){ + ret2 = read_reg(sensor->slv_addr, reg+2); + if(ret2 >= 0){ + ret = (ret << 8) | ret2 ; + } else { + ret = ret2; + } + } + } else { + ret = read_reg(sensor->slv_addr, reg); + } + if(ret > 0){ + ret &= mask; + } + return ret; +} + +static int set_reg(sensor_t *sensor, int reg, int mask, int value) +{ + int ret = 0, ret2 = 0; + if(mask > 0xFF){ + ret = read_reg16(sensor->slv_addr, reg); + if(ret >= 0 && mask > 0xFFFF){ + ret2 = read_reg(sensor->slv_addr, reg+2); + if(ret2 >= 0){ + ret = (ret << 8) | ret2 ; + } else { + ret = ret2; + } + } + } else { + ret = read_reg(sensor->slv_addr, reg); + } + if(ret < 0){ + return ret; + } + value = (ret & ~mask) | (value & mask); + if(mask > 0xFFFF){ + ret = write_reg16(sensor->slv_addr, reg, value >> 8); + if(ret >= 0){ + ret = write_reg(sensor->slv_addr, reg+2, value & 0xFF); + } + } else if(mask > 0xFF){ + ret = write_reg16(sensor->slv_addr, reg, value); + } else { + ret = write_reg(sensor->slv_addr, reg, value); + } + return ret; +} + +static int set_res_raw(sensor_t *sensor, int startX, int startY, int endX, int endY, int offsetX, int offsetY, int totalX, int totalY, int outputX, int outputY, bool scale, bool binning) +{ + int ret = 0; + ret = write_addr_reg(sensor->slv_addr, X_ADDR_ST_H, startX, startY) + || write_addr_reg(sensor->slv_addr, X_ADDR_END_H, endX, endY) + || write_addr_reg(sensor->slv_addr, X_OFFSET_H, offsetX, offsetY) + || write_addr_reg(sensor->slv_addr, X_TOTAL_SIZE_H, totalX, totalY) + || write_addr_reg(sensor->slv_addr, X_OUTPUT_SIZE_H, outputX, outputY) + || write_reg_bits(sensor->slv_addr, ISP_CONTROL_01, 0x20, scale); + if(!ret){ + sensor->status.scale = scale; + sensor->status.binning = binning; + ret = set_image_options(sensor); + } + return ret; +} + +static int _set_pll(sensor_t *sensor, int bypass, int multiplier, int sys_div, int root_2x, int pre_div, int seld5, int pclk_manual, int pclk_div) +{ + int ret = 0; + ret = set_pll(sensor, bypass > 0, multiplier, sys_div, pre_div, root_2x > 0, seld5, pclk_manual > 0, pclk_div); + return ret; +} + +esp_err_t xclk_timer_conf(int ledc_timer, int xclk_freq_hz); +static int set_xclk(sensor_t *sensor, int timer, int xclk) +{ + int ret = 0; + sensor->xclk_freq_hz = xclk * 1000000U; + ret = xclk_timer_conf(timer, sensor->xclk_freq_hz); + return ret; +} + +static int init_status(sensor_t *sensor) +{ + sensor->status.brightness = 0; + sensor->status.contrast = 0; + sensor->status.saturation = 0; + sensor->status.sharpness = (read_reg(sensor->slv_addr, 0x5303) / 8) - 3; + sensor->status.denoise = get_denoise(sensor); + sensor->status.ae_level = 0; + sensor->status.gainceiling = read_reg16(sensor->slv_addr, 0x3A18) & 0x3FF; + sensor->status.awb = check_reg_mask(sensor->slv_addr, ISP_CONTROL_01, 0x01); + sensor->status.dcw = !check_reg_mask(sensor->slv_addr, 0x5183, 0x80); + sensor->status.agc = !check_reg_mask(sensor->slv_addr, AEC_PK_MANUAL, AEC_PK_MANUAL_AGC_MANUALEN); + sensor->status.aec = !check_reg_mask(sensor->slv_addr, AEC_PK_MANUAL, AEC_PK_MANUAL_AEC_MANUALEN); + sensor->status.hmirror = check_reg_mask(sensor->slv_addr, TIMING_TC_REG21, TIMING_TC_REG21_HMIRROR); + sensor->status.vflip = check_reg_mask(sensor->slv_addr, TIMING_TC_REG20, TIMING_TC_REG20_VFLIP); + sensor->status.colorbar = check_reg_mask(sensor->slv_addr, PRE_ISP_TEST_SETTING_1, TEST_COLOR_BAR); + sensor->status.bpc = check_reg_mask(sensor->slv_addr, 0x5000, 0x04); + sensor->status.wpc = check_reg_mask(sensor->slv_addr, 0x5000, 0x02); + sensor->status.raw_gma = check_reg_mask(sensor->slv_addr, 0x5000, 0x20); + sensor->status.lenc = check_reg_mask(sensor->slv_addr, 0x5000, 0x80); + sensor->status.quality = read_reg(sensor->slv_addr, COMPRESSION_CTRL07) & 0x3f; + sensor->status.special_effect = 0; + sensor->status.wb_mode = 0; + sensor->status.awb_gain = check_reg_mask(sensor->slv_addr, 0x3406, 0x01); + sensor->status.agc_gain = get_agc_gain(sensor); + sensor->status.aec_value = get_aec_value(sensor); + sensor->status.aec2 = check_reg_mask(sensor->slv_addr, 0x3a00, 0x04); + return 0; +} + +int ov5640_init(sensor_t *sensor) +{ + sensor->reset = reset; + sensor->set_pixformat = set_pixformat; + sensor->set_framesize = set_framesize; + sensor->set_contrast = set_contrast; + sensor->set_brightness = set_brightness; + sensor->set_saturation = set_saturation; + sensor->set_sharpness = set_sharpness; + sensor->set_gainceiling = set_gainceiling; + sensor->set_quality = set_quality; + sensor->set_colorbar = set_colorbar; + sensor->set_gain_ctrl = set_gain_ctrl; + sensor->set_exposure_ctrl = set_exposure_ctrl; + sensor->set_whitebal = set_whitebal; + sensor->set_hmirror = set_hmirror; + sensor->set_vflip = set_vflip; + sensor->init_status = init_status; + sensor->set_aec2 = set_aec2; + sensor->set_aec_value = set_aec_value; + sensor->set_special_effect = set_special_effect; + sensor->set_wb_mode = set_wb_mode; + sensor->set_ae_level = set_ae_level; + sensor->set_dcw = set_dcw_dsp; + sensor->set_bpc = set_bpc_dsp; + sensor->set_wpc = set_wpc_dsp; + sensor->set_awb_gain = set_awb_gain_dsp; + sensor->set_agc_gain = set_agc_gain; + sensor->set_raw_gma = set_raw_gma_dsp; + sensor->set_lenc = set_lenc_dsp; + sensor->set_denoise = set_denoise; + + sensor->get_reg = get_reg; + sensor->set_reg = set_reg; + sensor->set_res_raw = set_res_raw; + sensor->set_pll = _set_pll; + sensor->set_xclk = set_xclk; + return 0; +} diff --git a/esp32-cam-rtos-allframes/ov5640.h b/esp32-cam-rtos-allframes/ov5640.h new file mode 100644 index 0000000..7b572ad --- /dev/null +++ b/esp32-cam-rtos-allframes/ov5640.h @@ -0,0 +1,9 @@ + +#ifndef __OV5640_H__ +#define __OV5640_H__ + +#include "sensor.h" + +int ov5640_init(sensor_t *sensor); + +#endif // __OV5640_H__ diff --git a/esp32-cam-rtos-allframes/ov5640_regs.h b/esp32-cam-rtos-allframes/ov5640_regs.h new file mode 100644 index 0000000..c28d80f --- /dev/null +++ b/esp32-cam-rtos-allframes/ov5640_regs.h @@ -0,0 +1,213 @@ +/* + * OV5640 register definitions. + */ +#ifndef __OV5640_REG_REGS_H__ +#define __OV5640_REG_REGS_H__ + +/* system control registers */ +#define SYSTEM_CTROL0 0x3008 // Bit[7]: Software reset + // Bit[6]: Software power down + // Bit[5]: Reserved + // Bit[4]: SRB clock SYNC enable + // Bit[3]: Isolation suspend select + // Bit[2:0]: Not used + +#define DRIVE_CAPABILITY 0x302c // Bit[7:6]: + // 00: 1x + // 01: 2x + // 10: 3x + // 11: 4x + +#define SC_PLLS_CTRL0 0x303a // Bit[7]: PLLS bypass +#define SC_PLLS_CTRL1 0x303b // Bit[4:0]: PLLS multiplier +#define SC_PLLS_CTRL2 0x303c // Bit[6:4]: PLLS charge pump control + // Bit[3:0]: PLLS system divider +#define SC_PLLS_CTRL3 0x303d // Bit[5:4]: PLLS pre-divider + // 00: 1 + // 01: 1.5 + // 10: 2 + // 11: 3 + // Bit[2]: PLLS root-divider - 1 + // Bit[1:0]: PLLS seld5 + // 00: 1 + // 01: 1 + // 10: 2 + // 11: 2.5 + +/* AEC/AGC control functions */ +#define AEC_PK_MANUAL 0x3503 // AEC Manual Mode Control + // Bit[7:6]: Reserved + // Bit[5]: Gain delay option + // Valid when 0x3503[4]=1’b0 + // 0: Delay one frame latch + // 1: One frame latch + // Bit[4:2]: Reserved + // Bit[1]: AGC manual + // 0: Auto enable + // 1: Manual enable + // Bit[0]: AEC manual + // 0: Auto enable + // 1: Manual enable + +//gain = {0x350A[1:0], 0x350B[7:0]} / 16 + + +#define X_ADDR_ST_H 0x3800 //Bit[3:0]: X address start[11:8] +#define X_ADDR_ST_L 0x3801 //Bit[7:0]: X address start[7:0] +#define Y_ADDR_ST_H 0x3802 //Bit[2:0]: Y address start[10:8] +#define Y_ADDR_ST_L 0x3803 //Bit[7:0]: Y address start[7:0] +#define X_ADDR_END_H 0x3804 //Bit[3:0]: X address end[11:8] +#define X_ADDR_END_L 0x3805 //Bit[7:0]: +#define Y_ADDR_END_H 0x3806 //Bit[2:0]: Y address end[10:8] +#define Y_ADDR_END_L 0x3807 //Bit[7:0]: +// Size after scaling +#define X_OUTPUT_SIZE_H 0x3808 //Bit[3:0]: DVP output horizontal width[11:8] +#define X_OUTPUT_SIZE_L 0x3809 //Bit[7:0]: +#define Y_OUTPUT_SIZE_H 0x380a //Bit[2:0]: DVP output vertical height[10:8] +#define Y_OUTPUT_SIZE_L 0x380b //Bit[7:0]: +#define X_TOTAL_SIZE_H 0x380c //Bit[3:0]: Total horizontal size[11:8] +#define X_TOTAL_SIZE_L 0x380d //Bit[7:0]: +#define Y_TOTAL_SIZE_H 0x380e //Bit[7:0]: Total vertical size[15:8] +#define Y_TOTAL_SIZE_L 0x380f //Bit[7:0]: +#define X_OFFSET_H 0x3810 //Bit[3:0]: ISP horizontal offset[11:8] +#define X_OFFSET_L 0x3811 //Bit[7:0]: +#define Y_OFFSET_H 0x3812 //Bit[2:0]: ISP vertical offset[10:8] +#define Y_OFFSET_L 0x3813 //Bit[7:0]: +#define X_INCREMENT 0x3814 //Bit[7:4]: Horizontal odd subsample increment + //Bit[3:0]: Horizontal even subsample increment +#define Y_INCREMENT 0x3815 //Bit[7:4]: Vertical odd subsample increment + //Bit[3:0]: Vertical even subsample increment +// Size before scaling +//#define X_INPUT_SIZE (X_ADDR_END - X_ADDR_ST + 1 - (2 * X_OFFSET)) +//#define Y_INPUT_SIZE (Y_ADDR_END - Y_ADDR_ST + 1 - (2 * Y_OFFSET)) + +/* mirror and flip registers */ +#define TIMING_TC_REG20 0x3820 // Timing Control Register + // Bit[2:1]: Vertical flip enable + // 00: Normal + // 11: Vertical flip + // Bit[0]: Vertical binning enable +#define TIMING_TC_REG21 0x3821 // Timing Control Register + // Bit[5]: Compression Enable + // Bit[2:1]: Horizontal mirror enable + // 00: Normal + // 11: Horizontal mirror + // Bit[0]: Horizontal binning enable + +#define PCLK_RATIO 0x3824 // Bit[4:0]: PCLK ratio manual + +/* frame control registers */ +#define FRAME_CTRL01 0x4201 // Control Passed Frame Number When both ON and OFF number set to 0x00,frame control is in bypass mode + // Bit[7:4]: Not used + // Bit[3:0]: Frame ON number +#define FRAME_CTRL02 0x4202 // Control Masked Frame Number When both ON and OFF number set to 0x00,frame control is in bypass mode + // Bit[7:4]: Not used + // BIT[3:0]: Frame OFF number + +/* format control registers */ +#define FORMAT_CTRL00 0x4300 + +#define CLOCK_POL_CONTROL 0x4740// Bit[5]: PCLK polarity 0: active low + // 1: active high + // Bit[3]: Gate PCLK under VSYNC + // Bit[2]: Gate PCLK under HREF + // Bit[1]: HREF polarity + // 0: active low + // 1: active high + // Bit[0] VSYNC polarity + // 0: active low + // 1: active high + +#define ISP_CONTROL_01 0x5001 // Bit[5]: Scale enable + // 0: Disable + // 1: Enable + +/* output format control registers */ +#define FORMAT_CTRL 0x501F // Format select + // Bit[2:0]: + // 000: YUV422 + // 001: RGB + // 010: Dither + // 011: RAW after DPC + // 101: RAW after CIP + +/* ISP top control registers */ +#define PRE_ISP_TEST_SETTING_1 0x503D // Bit[7]: Test enable + // 0: Test disable + // 1: Color bar enable + // Bit[6]: Rolling + // Bit[5]: Transparent + // Bit[4]: Square black and white + // Bit[3:2]: Color bar style + // 00: Standard 8 color bar + // 01: Gradual change at vertical mode 1 + // 10: Gradual change at horizontal + // 11: Gradual change at vertical mode 2 + // Bit[1:0]: Test select + // 00: Color bar + // 01: Random data + // 10: Square data + // 11: Black image + +//exposure = {0x3500[3:0], 0x3501[7:0], 0x3502[7:0]} / 16 × tROW + +#define SCALE_CTRL_1 0x5601 // Bit[6:4]: HDIV RW + // DCW scale times + // 000: DCW 1 time + // 001: DCW 2 times + // 010: DCW 4 times + // 100: DCW 8 times + // 101: DCW 16 times + // Others: DCW 16 times + // Bit[2:0]: VDIV RW + // DCW scale times + // 000: DCW 1 time + // 001: DCW 2 times + // 010: DCW 4 times + // 100: DCW 8 times + // 101: DCW 16 times + // Others: DCW 16 times + +#define SCALE_CTRL_2 0x5602 // X_SCALE High Bits +#define SCALE_CTRL_3 0x5603 // X_SCALE Low Bits +#define SCALE_CTRL_4 0x5604 // Y_SCALE High Bits +#define SCALE_CTRL_5 0x5605 // Y_SCALE Low Bits +#define SCALE_CTRL_6 0x5606 // Bit[3:0]: V Offset + +#define VFIFO_CTRL0C 0x460C // Bit[1]: PCLK manual enable + // 0: Auto + // 1: Manual by PCLK_RATIO + +#define VFIFO_X_SIZE_H 0x4602 +#define VFIFO_X_SIZE_L 0x4603 +#define VFIFO_Y_SIZE_H 0x4604 +#define VFIFO_Y_SIZE_L 0x4605 + +#define COMPRESSION_CTRL00 0x4400 // +#define COMPRESSION_CTRL01 0x4401 // +#define COMPRESSION_CTRL02 0x4402 // +#define COMPRESSION_CTRL03 0x4403 // +#define COMPRESSION_CTRL04 0x4404 // +#define COMPRESSION_CTRL05 0x4405 // +#define COMPRESSION_CTRL06 0x4406 // +#define COMPRESSION_CTRL07 0x4407 // Bit[5:0]: QS +#define COMPRESSION_ISI_CTRL 0x4408 // +#define COMPRESSION_CTRL09 0x4409 // +#define COMPRESSION_CTRL0a 0x440a // +#define COMPRESSION_CTRL0b 0x440b // +#define COMPRESSION_CTRL0c 0x440c // +#define COMPRESSION_CTRL0d 0x440d // +#define COMPRESSION_CTRL0E 0x440e // + +/** + * @brief register value + */ +#define TEST_COLOR_BAR 0xC0 /* Enable Color Bar roling Test */ + +#define AEC_PK_MANUAL_AGC_MANUALEN 0x02 /* Enable AGC Manual enable */ +#define AEC_PK_MANUAL_AEC_MANUALEN 0x01 /* Enable AEC Manual enable */ + +#define TIMING_TC_REG20_VFLIP 0x06 /* Vertical flip enable */ +#define TIMING_TC_REG21_HMIRROR 0x06 /* Horizontal mirror enable */ + +#endif // __OV3660_REG_REGS_H__ diff --git a/esp32-cam-rtos-allframes/ov5640_settings.h b/esp32-cam-rtos-allframes/ov5640_settings.h new file mode 100644 index 0000000..fec7d67 --- /dev/null +++ b/esp32-cam-rtos-allframes/ov5640_settings.h @@ -0,0 +1,334 @@ +#ifndef _OV5640_SETTINGS_H_ +#define _OV5640_SETTINGS_H_ + +#include +#include +#include "esp_attr.h" +#include "ov5640_regs.h" + +static const ratio_settings_t ratio_table[] = { + // mw, mh, sx, sy, ex, ey, ox, oy, tx, ty + { 2560, 1920, 0, 0, 2623, 1951, 32, 16, 2844, 1968 }, //4x3 + { 2560, 1704, 0, 110, 2623, 1843, 32, 16, 2844, 1752 }, //3x2 + { 2560, 1600, 0, 160, 2623, 1791, 32, 16, 2844, 1648 }, //16x10 + { 2560, 1536, 0, 192, 2623, 1759, 32, 16, 2844, 1584 }, //5x3 + { 2560, 1440, 0, 240, 2623, 1711, 32, 16, 2844, 1488 }, //16x9 + { 2560, 1080, 0, 420, 2623, 1531, 32, 16, 2844, 1128 }, //21x9 + { 2400, 1920, 80, 0, 2543, 1951, 32, 16, 2684, 1968 }, //5x4 + { 1920, 1920, 320, 0, 2543, 1951, 32, 16, 2684, 1968 }, //1x1 + { 1088, 1920, 736, 0, 1887, 1951, 32, 16, 1884, 1968 } //9x16 +}; + +#define REG_DLY 0xffff +#define REGLIST_TAIL 0x0000 + +static const DRAM_ATTR uint16_t sensor_default_regs[][2] = { + {SYSTEM_CTROL0, 0x82}, // software reset + {REG_DLY, 10}, // delay 10ms + {SYSTEM_CTROL0, 0x42}, // power down + + //enable pll + {0x3103, 0x13}, + + //io direction + {0x3017, 0xff}, + {0x3018, 0xff}, + + {DRIVE_CAPABILITY, 0xc3}, + {CLOCK_POL_CONTROL, 0x21}, + + {0x4713, 0x02},//jpg mode select + + {ISP_CONTROL_01, 0x83}, // turn color matrix, awb and SDE + + //sys reset + {0x3000, 0x00}, + {0x3002, 0x1c}, + + //clock enable + {0x3004, 0xff}, + {0x3006, 0xc3}, + + //isp control + {0x5000, 0xa7}, + {ISP_CONTROL_01, 0xa3},//+scaling? + {0x5003, 0x08},//special_effect + + //unknown + {0x370c, 0x02},//!!IMPORTANT + {0x3634, 0x40},//!!IMPORTANT + + //AEC/AGC + {0x3a02, 0x03}, + {0x3a03, 0xd8}, + {0x3a08, 0x01}, + {0x3a09, 0x27}, + {0x3a0a, 0x00}, + {0x3a0b, 0xf6}, + {0x3a0d, 0x04}, + {0x3a0e, 0x03}, + {0x3a0f, 0x30},//ae_level + {0x3a10, 0x28},//ae_level + {0x3a11, 0x60},//ae_level + {0x3a13, 0x43}, + {0x3a14, 0x03}, + {0x3a15, 0xd8}, + {0x3a18, 0x00},//gainceiling + {0x3a19, 0xf8},//gainceiling + {0x3a1b, 0x30},//ae_level + {0x3a1e, 0x26},//ae_level + {0x3a1f, 0x14},//ae_level + + //vcm debug + {0x3600, 0x08}, + {0x3601, 0x33}, + + //50/60Hz + {0x3c01, 0xa4}, + {0x3c04, 0x28}, + {0x3c05, 0x98}, + {0x3c06, 0x00}, + {0x3c07, 0x08}, + {0x3c08, 0x00}, + {0x3c09, 0x1c}, + {0x3c0a, 0x9c}, + {0x3c0b, 0x40}, + + {0x460c, 0x22},//disable jpeg footer + + //BLC + {0x4001, 0x02}, + {0x4004, 0x02}, + + //AWB + {0x5180, 0xff}, + {0x5181, 0xf2}, + {0x5182, 0x00}, + {0x5183, 0x14}, + {0x5184, 0x25}, + {0x5185, 0x24}, + {0x5186, 0x09}, + {0x5187, 0x09}, + {0x5188, 0x09}, + {0x5189, 0x75}, + {0x518a, 0x54}, + {0x518b, 0xe0}, + {0x518c, 0xb2}, + {0x518d, 0x42}, + {0x518e, 0x3d}, + {0x518f, 0x56}, + {0x5190, 0x46}, + {0x5191, 0xf8}, + {0x5192, 0x04}, + {0x5193, 0x70}, + {0x5194, 0xf0}, + {0x5195, 0xf0}, + {0x5196, 0x03}, + {0x5197, 0x01}, + {0x5198, 0x04}, + {0x5199, 0x12}, + {0x519a, 0x04}, + {0x519b, 0x00}, + {0x519c, 0x06}, + {0x519d, 0x82}, + {0x519e, 0x38}, + + //color matrix (Saturation) + {0x5381, 0x1e}, + {0x5382, 0x5b}, + {0x5383, 0x08}, + {0x5384, 0x0a}, + {0x5385, 0x7e}, + {0x5386, 0x88}, + {0x5387, 0x7c}, + {0x5388, 0x6c}, + {0x5389, 0x10}, + {0x538a, 0x01}, + {0x538b, 0x98}, + + //CIP control (Sharpness) + {0x5300, 0x10},//sharpness + {0x5301, 0x10},//sharpness + {0x5302, 0x18},//sharpness + {0x5303, 0x19},//sharpness + {0x5304, 0x10}, + {0x5305, 0x10}, + {0x5306, 0x08},//denoise + {0x5307, 0x16}, + {0x5308, 0x40}, + {0x5309, 0x10},//sharpness + {0x530a, 0x10},//sharpness + {0x530b, 0x04},//sharpness + {0x530c, 0x06},//sharpness + + //GAMMA + {0x5480, 0x01}, + {0x5481, 0x00}, + {0x5482, 0x1e}, + {0x5483, 0x3b}, + {0x5484, 0x58}, + {0x5485, 0x66}, + {0x5486, 0x71}, + {0x5487, 0x7d}, + {0x5488, 0x83}, + {0x5489, 0x8f}, + {0x548a, 0x98}, + {0x548b, 0xa6}, + {0x548c, 0xb8}, + {0x548d, 0xca}, + {0x548e, 0xd7}, + {0x548f, 0xe3}, + {0x5490, 0x1d}, + + //Special Digital Effects (SDE) (UV adjust) + {0x5580, 0x06},//enable brightness and contrast + {0x5583, 0x40},//special_effect + {0x5584, 0x10},//special_effect + {0x5586, 0x20},//contrast + {0x5587, 0x00},//brightness + {0x5588, 0x00},//brightness + {0x5589, 0x10}, + {0x558a, 0x00}, + {0x558b, 0xf8}, + {0x501d, 0x40},// enable manual offset of contrast + + //power on + {0x3008, 0x02}, + + //50Hz + {0x3c00, 0x04}, + + {REG_DLY, 300}, + {REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_fmt_jpeg[][2] = { + {FORMAT_CTRL, 0x00}, // YUV422 + {FORMAT_CTRL00, 0x30}, // YUYV + {0x3002, 0x00},//0x1c to 0x00 !!! + {0x3006, 0xff},//0xc3 to 0xff !!! + {0x471c, 0x50},//0xd0 to 0x50 !!! + {REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_fmt_raw[][2] = { + {FORMAT_CTRL, 0x03}, // RAW (DPC) + {FORMAT_CTRL00, 0x00}, // RAW + {REGLIST_TAIL, 0x00} +}; + +static const DRAM_ATTR uint16_t sensor_fmt_grayscale[][2] = { + {FORMAT_CTRL, 0x00}, // YUV422 + {FORMAT_CTRL00, 0x10}, // Y8 + {REGLIST_TAIL, 0x00} +}; + +static const DRAM_ATTR uint16_t sensor_fmt_yuv422[][2] = { + {FORMAT_CTRL, 0x00}, // YUV422 + {FORMAT_CTRL00, 0x30}, // YUYV + {REGLIST_TAIL, 0x00} +}; + +static const DRAM_ATTR uint16_t sensor_fmt_rgb565[][2] = { + {FORMAT_CTRL, 0x01}, // RGB + {FORMAT_CTRL00, 0x61}, // RGB565 (BGR) + {REGLIST_TAIL, 0x00} +}; + +static const DRAM_ATTR uint8_t sensor_saturation_levels[9][11] = { + {0x1d, 0x60, 0x03, 0x07, 0x48, 0x4f, 0x4b, 0x40, 0x0b, 0x01, 0x98},//-4 + {0x1d, 0x60, 0x03, 0x08, 0x54, 0x5c, 0x58, 0x4b, 0x0d, 0x01, 0x98},//-3 + {0x1d, 0x60, 0x03, 0x0a, 0x60, 0x6a, 0x64, 0x56, 0x0e, 0x01, 0x98},//-2 + {0x1d, 0x60, 0x03, 0x0b, 0x6c, 0x77, 0x70, 0x60, 0x10, 0x01, 0x98},//-1 + {0x1d, 0x60, 0x03, 0x0c, 0x78, 0x84, 0x7d, 0x6b, 0x12, 0x01, 0x98},//0 + {0x1d, 0x60, 0x03, 0x0d, 0x84, 0x91, 0x8a, 0x76, 0x14, 0x01, 0x98},//+1 + {0x1d, 0x60, 0x03, 0x0e, 0x90, 0x9e, 0x96, 0x80, 0x16, 0x01, 0x98},//+2 + {0x1d, 0x60, 0x03, 0x10, 0x9c, 0xac, 0xa2, 0x8b, 0x17, 0x01, 0x98},//+3 + {0x1d, 0x60, 0x03, 0x11, 0xa8, 0xb9, 0xaf, 0x96, 0x19, 0x01, 0x98},//+4 +}; + +static const DRAM_ATTR uint8_t sensor_special_effects[7][4] = { + {0x06, 0x40, 0x2c, 0x08},//Normal + {0x46, 0x40, 0x28, 0x08},//Negative + {0x1e, 0x80, 0x80, 0x08},//Grayscale + {0x1e, 0x80, 0xc0, 0x08},//Red Tint + {0x1e, 0x60, 0x60, 0x08},//Green Tint + {0x1e, 0xa0, 0x40, 0x08},//Blue Tint + {0x1e, 0x40, 0xa0, 0x08},//Sepia +}; + +static const DRAM_ATTR uint16_t sensor_regs_gamma0[][2] = { + {0x5480, 0x01}, + {0x5481, 0x08}, + {0x5482, 0x14}, + {0x5483, 0x28}, + {0x5484, 0x51}, + {0x5485, 0x65}, + {0x5486, 0x71}, + {0x5487, 0x7d}, + {0x5488, 0x87}, + {0x5489, 0x91}, + {0x548a, 0x9a}, + {0x548b, 0xaa}, + {0x548c, 0xb8}, + {0x548d, 0xcd}, + {0x548e, 0xdd}, + {0x548f, 0xea}, + {0x5490, 0x1d} +}; + +static const DRAM_ATTR uint16_t sensor_regs_gamma1[][2] = { + {0x5480, 0x1}, + {0x5481, 0x0}, + {0x5482, 0x1e}, + {0x5483, 0x3b}, + {0x5484, 0x58}, + {0x5485, 0x66}, + {0x5486, 0x71}, + {0x5487, 0x7d}, + {0x5488, 0x83}, + {0x5489, 0x8f}, + {0x548a, 0x98}, + {0x548b, 0xa6}, + {0x548c, 0xb8}, + {0x548d, 0xca}, + {0x548e, 0xd7}, + {0x548f, 0xe3}, + {0x5490, 0x1d} +}; + +static const DRAM_ATTR uint16_t sensor_regs_awb0[][2] = { + {0x5180, 0xff}, + {0x5181, 0xf2}, + {0x5182, 0x00}, + {0x5183, 0x14}, + {0x5184, 0x25}, + {0x5185, 0x24}, + {0x5186, 0x09}, + {0x5187, 0x09}, + {0x5188, 0x09}, + {0x5189, 0x75}, + {0x518a, 0x54}, + {0x518b, 0xe0}, + {0x518c, 0xb2}, + {0x518d, 0x42}, + {0x518e, 0x3d}, + {0x518f, 0x56}, + {0x5190, 0x46}, + {0x5191, 0xf8}, + {0x5192, 0x04}, + {0x5193, 0x70}, + {0x5194, 0xf0}, + {0x5195, 0xf0}, + {0x5196, 0x03}, + {0x5197, 0x01}, + {0x5198, 0x04}, + {0x5199, 0x12}, + {0x519a, 0x04}, + {0x519b, 0x00}, + {0x519c, 0x06}, + {0x519d, 0x82}, + {0x519e, 0x38} +}; + +#endif diff --git a/esp32-cam-rtos-allframes/ov7725.c b/esp32-cam-rtos-allframes/ov7725.c new file mode 100644 index 0000000..bb31573 --- /dev/null +++ b/esp32-cam-rtos-allframes/ov7725.c @@ -0,0 +1,557 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * OV7725 driver. + * + */ +#include +#include +#include +#include +#include "sccb.h" +#include "ov7725.h" +#include "ov7725_regs.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#else +#include "esp_log.h" +static const char* TAG = "ov7725"; +#endif + + +static const uint8_t default_regs[][2] = { + {COM3, COM3_SWAP_YUV}, + {COM7, COM7_RES_QVGA | COM7_FMT_YUV}, + + {COM4, 0x01 | 0x00}, /* bypass PLL (0x00:off, 0x40:4x, 0x80:6x, 0xC0:8x) */ + {CLKRC, 0x80 | 0x03}, /* Res/Bypass pre-scalar (0x40:bypass, 0x00-0x3F:prescaler PCLK=XCLK/(prescaler + 1)/2 ) */ + + // QVGA Window Size + {HSTART, 0x3F}, + {HSIZE, 0x50}, + {VSTART, 0x03}, + {VSIZE, 0x78}, + {HREF, 0x00}, + + // Scale down to QVGA Resolution + {HOUTSIZE, 0x50}, + {VOUTSIZE, 0x78}, + {EXHCH, 0x00}, + + {COM12, 0x03}, + {TGT_B, 0x7F}, + {FIXGAIN, 0x09}, + {AWB_CTRL0, 0xE0}, + {DSP_CTRL1, 0xFF}, + + {DSP_CTRL2, DSP_CTRL2_VDCW_EN | DSP_CTRL2_HDCW_EN | DSP_CTRL2_HZOOM_EN | DSP_CTRL2_VZOOM_EN}, + + {DSP_CTRL3, 0x00}, + {DSP_CTRL4, 0x00}, + {DSPAUTO, 0xFF}, + + {COM8, 0xF0}, + {COM6, 0xC5}, + {COM9, 0x11}, + {COM10, COM10_VSYNC_NEG | COM10_PCLK_MASK}, //Invert VSYNC and MASK PCLK + {BDBASE, 0x7F}, + {DBSTEP, 0x03}, + {AEW, 0x96}, + {AEB, 0x64}, + {VPT, 0xA1}, + {EXHCL, 0x00}, + {AWB_CTRL3, 0xAA}, + {COM8, 0xFF}, + + //Gamma + {GAM1, 0x0C}, + {GAM2, 0x16}, + {GAM3, 0x2A}, + {GAM4, 0x4E}, + {GAM5, 0x61}, + {GAM6, 0x6F}, + {GAM7, 0x7B}, + {GAM8, 0x86}, + {GAM9, 0x8E}, + {GAM10, 0x97}, + {GAM11, 0xA4}, + {GAM12, 0xAF}, + {GAM13, 0xC5}, + {GAM14, 0xD7}, + {GAM15, 0xE8}, + + {SLOP, 0x20}, + {EDGE1, 0x05}, + {EDGE2, 0x03}, + {EDGE3, 0x00}, + {DNSOFF, 0x01}, + + {MTX1, 0xB0}, + {MTX2, 0x9D}, + {MTX3, 0x13}, + {MTX4, 0x16}, + {MTX5, 0x7B}, + {MTX6, 0x91}, + {MTX_CTRL, 0x1E}, + + {BRIGHTNESS, 0x08}, + {CONTRAST, 0x30}, + {UVADJ0, 0x81}, + {SDE, (SDE_CONT_BRIGHT_EN | SDE_SATURATION_EN)}, + + // For 30 fps/60Hz + {DM_LNL, 0x00}, + {DM_LNH, 0x00}, + {BDBASE, 0x7F}, + {DBSTEP, 0x03}, + + // Lens Correction, should be tuned with real camera module + {LC_RADI, 0x10}, + {LC_COEF, 0x10}, + {LC_COEFB, 0x14}, + {LC_COEFR, 0x17}, + {LC_CTR, 0x05}, + {COM5, 0xF5}, //0x65 + + {0x00, 0x00}, +}; + +static int get_reg(sensor_t *sensor, int reg, int mask) +{ + int ret = SCCB_Read(sensor->slv_addr, reg & 0xFF); + if(ret > 0){ + ret &= mask; + } + return ret; +} + +static int set_reg(sensor_t *sensor, int reg, int mask, int value) +{ + int ret = 0; + ret = SCCB_Read(sensor->slv_addr, reg & 0xFF); + if(ret < 0){ + return ret; + } + value = (ret & ~mask) | (value & mask); + ret = SCCB_Write(sensor->slv_addr, reg & 0xFF, value); + return ret; +} + +static int set_reg_bits(sensor_t *sensor, uint8_t reg, uint8_t offset, uint8_t length, uint8_t value) +{ + int ret = 0; + ret = SCCB_Read(sensor->slv_addr, reg); + if(ret < 0){ + return ret; + } + uint8_t mask = ((1 << length) - 1) << offset; + value = (ret & ~mask) | ((value << offset) & mask); + ret = SCCB_Write(sensor->slv_addr, reg & 0xFF, value); + return ret; +} + +static int get_reg_bits(sensor_t *sensor, uint8_t reg, uint8_t offset, uint8_t length) +{ + int ret = 0; + ret = SCCB_Read(sensor->slv_addr, reg); + if(ret < 0){ + return ret; + } + uint8_t mask = ((1 << length) - 1) << offset; + return (ret & mask) >> offset; +} + + +static int reset(sensor_t *sensor) +{ + int i=0; + const uint8_t (*regs)[2]; + + // Reset all registers + SCCB_Write(sensor->slv_addr, COM7, COM7_RESET); + + // Delay 10 ms + vTaskDelay(10 / portTICK_PERIOD_MS); + + // Write default regsiters + for (i=0, regs = default_regs; regs[i][0]; i++) { + SCCB_Write(sensor->slv_addr, regs[i][0], regs[i][1]); + } + + // Delay + vTaskDelay(30 / portTICK_PERIOD_MS); + + return 0; +} + + +static int set_pixformat(sensor_t *sensor, pixformat_t pixformat) +{ + int ret=0; + sensor->pixformat = pixformat; + // Read register COM7 + uint8_t reg = SCCB_Read(sensor->slv_addr, COM7); + + switch (pixformat) { + case PIXFORMAT_RGB565: + reg = COM7_SET_RGB(reg, COM7_FMT_RGB565); + break; + case PIXFORMAT_YUV422: + case PIXFORMAT_GRAYSCALE: + reg = COM7_SET_FMT(reg, COM7_FMT_YUV); + break; + default: + return -1; + } + + // Write back register COM7 + ret = SCCB_Write(sensor->slv_addr, COM7, reg); + + // Delay + vTaskDelay(30 / portTICK_PERIOD_MS); + + return ret; +} + +static int set_framesize(sensor_t *sensor, framesize_t framesize) +{ + int ret=0; + if (framesize > FRAMESIZE_VGA) { + return -1; + } + uint16_t w = resolution[framesize].width; + uint16_t h = resolution[framesize].height; + uint8_t reg = SCCB_Read(sensor->slv_addr, COM7); + + sensor->status.framesize = framesize; + + // Write MSBs + ret |= SCCB_Write(sensor->slv_addr, HOUTSIZE, w>>2); + ret |= SCCB_Write(sensor->slv_addr, VOUTSIZE, h>>1); + + ret |= SCCB_Write(sensor->slv_addr, HSIZE, w>>2); + ret |= SCCB_Write(sensor->slv_addr, VSIZE, h>>1); + + // Write LSBs + ret |= SCCB_Write(sensor->slv_addr, HREF, ((w&0x3) | ((h&0x1) << 2))); + + if (framesize < FRAMESIZE_VGA) { + // Enable auto-scaling/zooming factors + ret |= SCCB_Write(sensor->slv_addr, DSPAUTO, 0xFF); + + ret |= SCCB_Write(sensor->slv_addr, HSTART, 0x3F); + ret |= SCCB_Write(sensor->slv_addr, VSTART, 0x03); + + ret |= SCCB_Write(sensor->slv_addr, COM7, reg | COM7_RES_QVGA); + + ret |= SCCB_Write(sensor->slv_addr, CLKRC, 0x80 | 0x01); + + } else { + // Disable auto-scaling/zooming factors + ret |= SCCB_Write(sensor->slv_addr, DSPAUTO, 0xF3); + + // Clear auto-scaling/zooming factors + ret |= SCCB_Write(sensor->slv_addr, SCAL0, 0x00); + ret |= SCCB_Write(sensor->slv_addr, SCAL1, 0x00); + ret |= SCCB_Write(sensor->slv_addr, SCAL2, 0x00); + + ret |= SCCB_Write(sensor->slv_addr, HSTART, 0x23); + ret |= SCCB_Write(sensor->slv_addr, VSTART, 0x07); + + ret |= SCCB_Write(sensor->slv_addr, COM7, reg & ~COM7_RES_QVGA); + + ret |= SCCB_Write(sensor->slv_addr, CLKRC, 0x80 | 0x03); + } + + // Delay + vTaskDelay(30 / portTICK_PERIOD_MS); + + return ret; +} + +static int set_colorbar(sensor_t *sensor, int enable) +{ + int ret=0; + uint8_t reg; + sensor->status.colorbar = enable; + + // Read reg COM3 + reg = SCCB_Read(sensor->slv_addr, COM3); + // Enable colorbar test pattern output + reg = COM3_SET_CBAR(reg, enable); + // Write back COM3 + ret |= SCCB_Write(sensor->slv_addr, COM3, reg); + + // Read reg DSP_CTRL3 + reg = SCCB_Read(sensor->slv_addr, DSP_CTRL3); + // Enable DSP colorbar output + reg = DSP_CTRL3_SET_CBAR(reg, enable); + // Write back DSP_CTRL3 + ret |= SCCB_Write(sensor->slv_addr, DSP_CTRL3, reg); + + return ret; +} + +static int set_whitebal(sensor_t *sensor, int enable) +{ + if(set_reg_bits(sensor, COM8, 1, 1, enable) >= 0){ + sensor->status.awb = !!enable; + } + return sensor->status.awb; +} + +static int set_gain_ctrl(sensor_t *sensor, int enable) +{ + if(set_reg_bits(sensor, COM8, 2, 1, enable) >= 0){ + sensor->status.agc = !!enable; + } + return sensor->status.agc; +} + +static int set_exposure_ctrl(sensor_t *sensor, int enable) +{ + if(set_reg_bits(sensor, COM8, 0, 1, enable) >= 0){ + sensor->status.aec = !!enable; + } + return sensor->status.aec; +} + +static int set_hmirror(sensor_t *sensor, int enable) +{ + if(set_reg_bits(sensor, COM3, 6, 1, enable) >= 0){ + sensor->status.hmirror = !!enable; + } + return sensor->status.hmirror; +} + +static int set_vflip(sensor_t *sensor, int enable) +{ + if(set_reg_bits(sensor, COM3, 7, 1, enable) >= 0){ + sensor->status.vflip = !!enable; + } + return sensor->status.vflip; +} + +static int set_dcw_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = set_reg_bits(sensor, 0x65, 2, 1, !enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set dcw to: %d", enable); + sensor->status.dcw = enable; + } + return ret; +} + +static int set_aec2(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = set_reg_bits(sensor, COM8, 7, 1, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set aec2 to: %d", enable); + sensor->status.aec2 = enable; + } + return ret; +} + +static int set_bpc_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = set_reg_bits(sensor, 0x64, 1, 1, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set bpc to: %d", enable); + sensor->status.bpc = enable; + } + return ret; +} + +static int set_wpc_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = set_reg_bits(sensor, 0x64, 0, 1, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set wpc to: %d", enable); + sensor->status.wpc = enable; + } + return ret; +} + +static int set_raw_gma_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = set_reg_bits(sensor, 0x64, 2, 1, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set raw_gma to: %d", enable); + sensor->status.raw_gma = enable; + } + return ret; +} + +static int set_lenc_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = set_reg_bits(sensor, LC_CTR, 0, 1, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set lenc to: %d", enable); + sensor->status.lenc = enable; + } + return ret; +} + +//real gain +static int set_agc_gain(sensor_t *sensor, int gain) +{ + int ret = 0; + ret = set_reg_bits(sensor, COM9, 4, 3, gain % 5); + if (ret == 0) { + ESP_LOGD(TAG, "Set gain to: %d", gain); + sensor->status.agc_gain = gain; + } + return ret; +} + +static int set_aec_value(sensor_t *sensor, int value) +{ + int ret = 0; + ret = SCCB_Write(sensor->slv_addr, AEC, value & 0xff) | SCCB_Write(sensor->slv_addr, AECH, value >> 8); + if (ret == 0) { + ESP_LOGD(TAG, "Set aec_value to: %d", value); + sensor->status.aec_value = value; + } + return ret; +} + +static int set_awb_gain_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = set_reg_bits(sensor, 0x63, 7, 1, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set awb_gain to: %d", enable); + sensor->status.awb_gain = enable; + } + return ret; +} + +static int set_brightness(sensor_t *sensor, int level) +{ + int ret = 0; + ret = SCCB_Write(sensor->slv_addr, 0x9B, level); + if (ret == 0) { + ESP_LOGD(TAG, "Set brightness to: %d", level); + sensor->status.brightness = level; + } + return ret; +} + +static int set_contrast(sensor_t *sensor, int level) +{ + int ret = 0; + ret = SCCB_Write(sensor->slv_addr, 0x9C, level); + if (ret == 0) { + ESP_LOGD(TAG, "Set contrast to: %d", level); + sensor->status.contrast = level; + } + return ret; +} + +static int init_status(sensor_t *sensor) +{ + sensor->status.brightness = SCCB_Read(sensor->slv_addr, 0x9B); + sensor->status.contrast = SCCB_Read(sensor->slv_addr, 0x9C); + sensor->status.saturation = 0; + sensor->status.ae_level = 0; + sensor->status.special_effect = get_reg_bits(sensor, 0x64, 5, 1); + sensor->status.wb_mode = get_reg_bits(sensor, 0x6B, 7, 1); + sensor->status.agc_gain = get_reg_bits(sensor, COM9, 4, 3); + sensor->status.aec_value = SCCB_Read(sensor->slv_addr, AEC) | (SCCB_Read(sensor->slv_addr, AECH) << 8); + sensor->status.gainceiling = SCCB_Read(sensor->slv_addr, 0x00); + sensor->status.awb = get_reg_bits(sensor, COM8, 1, 1); + sensor->status.awb_gain = get_reg_bits(sensor, 0x63, 7, 1); + sensor->status.aec = get_reg_bits(sensor, COM8, 0, 1); + sensor->status.aec2 = get_reg_bits(sensor, COM8, 7, 1); + sensor->status.agc = get_reg_bits(sensor, COM8, 2, 1); + sensor->status.bpc = get_reg_bits(sensor, 0x64, 1, 1); + sensor->status.wpc = get_reg_bits(sensor, 0x64, 0, 1); + sensor->status.raw_gma = get_reg_bits(sensor, 0x64, 2, 1); + sensor->status.lenc = get_reg_bits(sensor, LC_CTR, 0, 1); + sensor->status.hmirror = get_reg_bits(sensor, COM3, 6, 1); + sensor->status.vflip = get_reg_bits(sensor, COM3, 7, 1); + sensor->status.dcw = get_reg_bits(sensor, 0x65, 2, 1); + sensor->status.colorbar = get_reg_bits(sensor, COM3, 0, 1); + sensor->status.sharpness = get_reg_bits(sensor, EDGE0, 0, 5); + sensor->status.denoise = SCCB_Read(sensor->slv_addr, 0x8E); + return 0; +} + +static int set_dummy(sensor_t *sensor, int val){ return -1; } +static int set_gainceiling_dummy(sensor_t *sensor, gainceiling_t val){ return -1; } +static int set_res_raw(sensor_t *sensor, int startX, int startY, int endX, int endY, int offsetX, int offsetY, int totalX, int totalY, int outputX, int outputY, bool scale, bool binning){return -1;} +static int _set_pll(sensor_t *sensor, int bypass, int multiplier, int sys_div, int root_2x, int pre_div, int seld5, int pclk_manual, int pclk_div){return -1;} + +esp_err_t xclk_timer_conf(int ledc_timer, int xclk_freq_hz); +static int set_xclk(sensor_t *sensor, int timer, int xclk) +{ + int ret = 0; + sensor->xclk_freq_hz = xclk * 1000000U; + ret = xclk_timer_conf(timer, sensor->xclk_freq_hz); + return ret; +} + +int ov7725_init(sensor_t *sensor) +{ + // Set function pointers + sensor->reset = reset; + sensor->init_status = init_status; + sensor->set_pixformat = set_pixformat; + sensor->set_framesize = set_framesize; + sensor->set_colorbar = set_colorbar; + sensor->set_whitebal = set_whitebal; + sensor->set_gain_ctrl = set_gain_ctrl; + sensor->set_exposure_ctrl = set_exposure_ctrl; + sensor->set_hmirror = set_hmirror; + sensor->set_vflip = set_vflip; + + sensor->set_brightness = set_brightness; + sensor->set_contrast = set_contrast; + sensor->set_aec2 = set_aec2; + sensor->set_aec_value = set_aec_value; + sensor->set_awb_gain = set_awb_gain_dsp; + sensor->set_agc_gain = set_agc_gain; + sensor->set_dcw = set_dcw_dsp; + sensor->set_bpc = set_bpc_dsp; + sensor->set_wpc = set_wpc_dsp; + sensor->set_raw_gma = set_raw_gma_dsp; + sensor->set_lenc = set_lenc_dsp; + + //not supported + sensor->set_saturation= set_dummy; + sensor->set_sharpness = set_dummy; + sensor->set_denoise = set_dummy; + sensor->set_quality = set_dummy; + sensor->set_special_effect = set_dummy; + sensor->set_wb_mode = set_dummy; + sensor->set_ae_level = set_dummy; + sensor->set_gainceiling = set_gainceiling_dummy; + + + sensor->get_reg = get_reg; + sensor->set_reg = set_reg; + sensor->set_res_raw = set_res_raw; + sensor->set_pll = _set_pll; + sensor->set_xclk = set_xclk; + + // Retrieve sensor's signature + sensor->id.MIDH = SCCB_Read(sensor->slv_addr, REG_MIDH); + sensor->id.MIDL = SCCB_Read(sensor->slv_addr, REG_MIDL); + sensor->id.PID = SCCB_Read(sensor->slv_addr, REG_PID); + sensor->id.VER = SCCB_Read(sensor->slv_addr, REG_VER); + + ESP_LOGD(TAG, "OV7725 Attached"); + + return 0; +} diff --git a/esp32-cam-rtos-allframes/ov7725.h b/esp32-cam-rtos-allframes/ov7725.h new file mode 100644 index 0000000..f8c3516 --- /dev/null +++ b/esp32-cam-rtos-allframes/ov7725.h @@ -0,0 +1,14 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * OV7725 driver. + * + */ +#ifndef __OV7725_H__ +#define __OV7725_H__ +#include "sensor.h" + +int ov7725_init(sensor_t *sensor); +#endif // __OV7725_H__ diff --git a/esp32-cam-rtos-allframes/ov7725_regs.h b/esp32-cam-rtos-allframes/ov7725_regs.h new file mode 100644 index 0000000..5cb233d --- /dev/null +++ b/esp32-cam-rtos-allframes/ov7725_regs.h @@ -0,0 +1,335 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * OV2640 register definitions. + */ +#ifndef __REG_REGS_H__ +#define __REG_REGS_H__ +#define GAIN 0x00 /* AGC – Gain control gain setting */ +#define BLUE 0x01 /* AWB – Blue channel gain setting */ +#define RED 0x02 /* AWB – Red channel gain setting */ +#define GREEN 0x03 /* AWB – Green channel gain setting */ +#define BAVG 0x05 /* U/B Average Level */ +#define GAVG 0x06 /* Y/Gb Average Level */ +#define RAVG 0x07 /* V/R Average Level */ +#define AECH 0x08 /* Exposure Value – AEC MSBs */ + +#define COM2 0x09 /* Common Control 2 */ +#define COM2_SOFT_SLEEP 0x10 /* Soft sleep mode */ +#define COM2_OUT_DRIVE_1x 0x00 /* Output drive capability 1x */ +#define COM2_OUT_DRIVE_2x 0x01 /* Output drive capability 2x */ +#define COM2_OUT_DRIVE_3x 0x02 /* Output drive capability 3x */ +#define COM2_OUT_DRIVE_4x 0x03 /* Output drive capability 4x */ + +#define REG_PID 0x0A /* Product ID Number MSB */ +#define REG_VER 0x0B /* Product ID Number LSB */ + +#define COM3 0x0C /* Common Control 3 */ +#define COM3_VFLIP 0x80 /* Vertical flip image ON/OFF selection */ +#define COM3_MIRROR 0x40 /* Horizontal mirror image ON/OFF selection */ +#define COM3_SWAP_BR 0x20 /* Swap B/R output sequence in RGB output mode */ +#define COM3_SWAP_YUV 0x10 /* Swap Y/UV output sequence in YUV output mode */ +#define COM3_SWAP_MSB 0x08 /* Swap output MSB/LSB */ +#define COM3_TRI_CLOCK 0x04 /* Tri-state option for output clock at power-down period */ +#define COM3_TRI_DATA 0x02 /* Tri-state option for output data at power-down period */ +#define COM3_COLOR_BAR 0x01 /* Sensor color bar test pattern output enable */ +#define COM3_SET_CBAR(r, x) ((r&0xFE)|((x&1)<<0)) +#define COM3_SET_MIRROR(r, x) ((r&0xBF)|((x&1)<<6)) +#define COM3_SET_FLIP(r, x) ((r&0x7F)|((x&1)<<7)) + +#define COM4 0x0D /* Common Control 4 */ +#define COM4_PLL_BYPASS 0x00 /* Bypass PLL */ +#define COM4_PLL_4x 0x40 /* PLL frequency 4x */ +#define COM4_PLL_6x 0x80 /* PLL frequency 6x */ +#define COM4_PLL_8x 0xc0 /* PLL frequency 8x */ +#define COM4_AEC_FULL 0x00 /* AEC evaluate full window */ +#define COM4_AEC_1_2 0x10 /* AEC evaluate 1/2 window */ +#define COM4_AEC_1_4 0x20 /* AEC evaluate 1/4 window */ +#define COM4_AEC_2_3 0x30 /* AEC evaluate 2/3 window */ + +#define COM5 0x0E /* Common Control 5 */ +#define COM5_AFR 0x80 /* Auto frame rate control ON/OFF selection (night mode) */ +#define COM5_AFR_SPEED 0x40 /* Auto frame rate control speed selection */ +#define COM5_AFR_0 0x00 /* No reduction of frame rate */ +#define COM5_AFR_1_2 0x10 /* Max reduction to 1/2 frame rate */ +#define COM5_AFR_1_4 0x20 /* Max reduction to 1/4 frame rate */ +#define COM5_AFR_1_8 0x30 /* Max reduction to 1/8 frame rate */ +#define COM5_AFR_4x 0x04 /* Add frame when AGC reaches 4x gain */ +#define COM5_AFR_8x 0x08 /* Add frame when AGC reaches 8x gain */ +#define COM5_AFR_16x 0x0c /* Add frame when AGC reaches 16x gain */ +#define COM5_AEC_NO_LIMIT 0x01 /* No limit to AEC increase step */ + +#define COM6 0x0F /* Common Control 6 */ +#define COM6_AUTO_WINDOW 0x01 /* Auto window setting ON/OFF selection when format changes */ + +#define AEC 0x10 /* AEC[7:0] (see register AECH for AEC[15:8]) */ +#define CLKRC 0x11 /* Internal Clock */ + +#define COM7 0x12 /* Common Control 7 */ +#define COM7_RESET 0x80 /* SCCB Register Reset */ +#define COM7_RES_VGA 0x00 /* Resolution VGA */ +#define COM7_RES_QVGA 0x40 /* Resolution QVGA */ +#define COM7_BT656 0x20 /* BT.656 protocol ON/OFF */ +#define COM7_SENSOR_RAW 0x10 /* Sensor RAW */ +#define COM7_FMT_GBR422 0x00 /* RGB output format GBR422 */ +#define COM7_FMT_RGB565 0x04 /* RGB output format RGB565 */ +#define COM7_FMT_RGB555 0x08 /* RGB output format RGB555 */ +#define COM7_FMT_RGB444 0x0C /* RGB output format RGB444 */ +#define COM7_FMT_YUV 0x00 /* Output format YUV */ +#define COM7_FMT_P_BAYER 0x01 /* Output format Processed Bayer RAW */ +#define COM7_FMT_RGB 0x02 /* Output format RGB */ +#define COM7_FMT_R_BAYER 0x03 /* Output format Bayer RAW */ +#define COM7_SET_FMT(r, x) ((r&0xFC)|((x&0x3)<<0)) +#define COM7_SET_RGB(r, x) ((r&0xF0)|(x&0x0C)|COM7_FMT_RGB) + +#define COM8 0x13 /* Common Control 8 */ +#define COM8_FAST_AUTO 0x80 /* Enable fast AGC/AEC algorithm */ +#define COM8_STEP_VSYNC 0x00 /* AEC - Step size limited to vertical blank */ +#define COM8_STEP_UNLIMIT 0x40 /* AEC - Step size unlimited step size */ +#define COM8_BANDF_EN 0x20 /* Banding filter ON/OFF */ +#define COM8_AEC_BANDF 0x10 /* Enable AEC below banding value */ +#define COM8_AEC_FINE_EN 0x08 /* Fine AEC ON/OFF control */ +#define COM8_AGC_EN 0x04 /* AGC Enable */ +#define COM8_AWB_EN 0x02 /* AWB Enable */ +#define COM8_AEC_EN 0x01 /* AEC Enable */ +#define COM8_SET_AGC(r, x) ((r&0xFB)|((x&0x1)<<2)) +#define COM8_SET_AWB(r, x) ((r&0xFD)|((x&0x1)<<1)) +#define COM8_SET_AEC(r, x) ((r&0xFE)|((x&0x1)<<0)) + +#define COM9 0x14 /* Common Control 9 */ +#define COM9_HISTO_AVG 0x80 /* Histogram or average based AEC/AGC selection */ +#define COM9_AGC_GAIN_2x 0x00 /* Automatic Gain Ceiling 2x */ +#define COM9_AGC_GAIN_4x 0x10 /* Automatic Gain Ceiling 4x */ +#define COM9_AGC_GAIN_8x 0x20 /* Automatic Gain Ceiling 8x */ +#define COM9_AGC_GAIN_16x 0x30 /* Automatic Gain Ceiling 16x */ +#define COM9_AGC_GAIN_32x 0x40 /* Automatic Gain Ceiling 32x */ +#define COM9_DROP_VSYNC 0x04 /* Drop VSYNC output of corrupt frame */ +#define COM9_DROP_HREF 0x02 /* Drop HREF output of corrupt frame */ +#define COM9_SET_AGC(r, x) ((r&0x8F)|((x&0x07)<<4)) + +#define COM10 0x15 /* Common Control 10 */ +#define COM10_NEGATIVE 0x80 /* Output negative data */ +#define COM10_HSYNC_EN 0x40 /* HREF changes to HSYNC */ +#define COM10_PCLK_FREE 0x00 /* PCLK output option: free running PCLK */ +#define COM10_PCLK_MASK 0x20 /* PCLK output option: masked during horizontal blank */ +#define COM10_PCLK_REV 0x10 /* PCLK reverse */ +#define COM10_HREF_REV 0x08 /* HREF reverse */ +#define COM10_VSYNC_FALLING 0x00 /* VSYNC changes on falling edge of PCLK */ +#define COM10_VSYNC_RISING 0x04 /* VSYNC changes on rising edge of PCLK */ +#define COM10_VSYNC_NEG 0x02 /* VSYNC negative */ +#define COM10_OUT_RANGE_8 0x01 /* Output data range: Full range */ +#define COM10_OUT_RANGE_10 0x00 /* Output data range: Data from [10] to [F0] (8 MSBs) */ + +#define REG16 0x16 /* Register 16 */ +#define REG16_BIT_SHIFT 0x80 /* Bit shift test pattern options */ +#define HSTART 0x17 /* Horizontal Frame (HREF column) Start 8 MSBs (2 LSBs are at HREF[5:4]) */ +#define HSIZE 0x18 /* Horizontal Sensor Size (2 LSBs are at HREF[1:0]) */ +#define VSTART 0x19 /* Vertical Frame (row) Start 8 MSBs (1 LSB is at HREF[6]) */ +#define VSIZE 0x1A /* Vertical Sensor Size (1 LSB is at HREF[2]) */ +#define PSHFT 0x1B /* Data Format - Pixel Delay Select */ +#define REG_MIDH 0x1C /* Manufacturer ID Byte – High */ +#define REG_MIDL 0x1D /* Manufacturer ID Byte – Low */ +#define LAEC 0x1F /* Fine AEC Value - defines exposure value less than one row period */ + +#define COM11 0x20 /* Common Control 11 */ +#define COM11_SNGL_FRAME_EN 0x02 /* Single frame ON/OFF selection */ +#define COM11_SNGL_XFR_TRIG 0x01 /* Single frame transfer trigger */ + +#define BDBASE 0x22 /* Banding Filter Minimum AEC Value */ +#define DBSTEP 0x23 /* Banding Filter Maximum Step */ +#define AEW 0x24 /* AGC/AEC - Stable Operating Region (Upper Limit) */ +#define AEB 0x25 /* AGC/AEC - Stable Operating Region (Lower Limit) */ +#define VPT 0x26 /* AGC/AEC Fast Mode Operating Region */ +#define REG28 0x28 /* Selection on the number of dummy rows, N */ +#define HOUTSIZE 0x29 /* Horizontal Data Output Size MSBs (2 LSBs at register EXHCH[1:0]) */ +#define EXHCH 0x2A /* Dummy Pixel Insert MSB */ +#define EXHCL 0x2B /* Dummy Pixel Insert LSB */ +#define VOUTSIZE 0x2C /* Vertical Data Output Size MSBs (LSB at register EXHCH[2]) */ +#define ADVFL 0x2D /* LSB of Insert Dummy Rows in Vertical Sync (1 bit equals 1 row) */ +#define ADVFH 0x2E /* MSB of Insert Dummy Rows in Vertical Sync */ +#define YAVE 0x2F /* Y/G Channel Average Value */ +#define LUMHTH 0x30 /* Histogram AEC/AGC Luminance High Level Threshold */ +#define LUMLTH 0x31 /* Histogram AEC/AGC Luminance Low Level Threshold */ +#define HREF 0x32 /* Image Start and Size Control */ +#define DM_LNL 0x33 /* Dummy Row Low 8 Bits */ +#define DM_LNH 0x34 /* Dummy Row High 8 Bits */ +#define ADOFF_B 0x35 /* AD Offset Compensation Value for B Channel */ +#define ADOFF_R 0x36 /* AD Offset Compensation Value for R Channel */ +#define ADOFF_GB 0x37 /* AD Offset Compensation Value for GB Channel */ +#define ADOFF_GR 0x38 /* AD Offset Compensation Value for GR Channel */ +#define OFF_B 0x39 /* AD Offset Compensation Value for B Channel */ +#define OFF_R 0x3A /* AD Offset Compensation Value for R Channel */ +#define OFF_GB 0x3B /* AD Offset Compensation Value for GB Channel */ +#define OFF_GR 0x3C /* AD Offset Compensation Value for GR Channel */ +#define COM12 0x3D /* DC offset compensation for analog process */ + +#define COM13 0x3E /* Common Control 13 */ +#define COM13_BLC_EN 0x80 /* BLC enable */ +#define COM13_ADC_EN 0x40 /* ADC channel BLC ON/OFF control */ +#define COM13_ANALOG_BLC 0x20 /* Analog processing channel BLC ON/OFF control */ +#define COM13_ABLC_GAIN_EN 0x04 /* ABLC gain trigger enable */ + +#define COM14 0x3F /* Common Control 14 */ +#define COM15 0x40 /* Common Control 15 */ +#define COM16 0x41 /* Common Control 16 */ +#define TGT_B 0x42 /* BLC Blue Channel Target Value */ +#define TGT_R 0x43 /* BLC Red Channel Target Value */ +#define TGT_GB 0x44 /* BLC Gb Channel Target Value */ +#define TGT_GR 0x45 /* BLC Gr Channel Target Value */ + +#define LC_CTR 0x46 /* Lens Correction Control */ +#define LC_CTR_RGB_COMP_1 0x00 /* R, G, and B channel compensation coefficient is set by LC_COEF (0x49) */ +#define LC_CTR_RGB_COMP_3 0x04 /* R, G, and B channel compensation coefficient is set by registers + LC_COEFB (0x4B), LC_COEF (0x49), and LC_COEFR (0x4C), respectively */ +#define LC_CTR_EN 0x01 /* Lens correction enable */ +#define LC_XC 0x47 /* X Coordinate of Lens Correction Center Relative to Array Center */ +#define LC_YC 0x48 /* Y Coordinate of Lens Correction Center Relative to Array Center */ +#define LC_COEF 0x49 /* Lens Correction Coefficient */ +#define LC_RADI 0x4A /* Lens Correction Radius */ +#define LC_COEFB 0x4B /* Lens Correction B Channel Compensation Coefficient */ +#define LC_COEFR 0x4C /* Lens Correction R Channel Compensation Coefficient */ + +#define FIXGAIN 0x4D /* Analog Fix Gain Amplifier */ +#define AREF0 0x4E /* Sensor Reference Control */ +#define AREF1 0x4F /* Sensor Reference Current Control */ +#define AREF2 0x50 /* Analog Reference Control */ +#define AREF3 0x51 /* ADC Reference Control */ +#define AREF4 0x52 /* ADC Reference Control */ +#define AREF5 0x53 /* ADC Reference Control */ +#define AREF6 0x54 /* Analog Reference Control */ +#define AREF7 0x55 /* Analog Reference Control */ +#define UFIX 0x60 /* U Channel Fixed Value Output */ +#define VFIX 0x61 /* V Channel Fixed Value Output */ +#define AWBB_BLK 0x62 /* AWB Option for Advanced AWB */ + +#define AWB_CTRL0 0x63 /* AWB Control Byte 0 */ +#define AWB_CTRL0_GAIN_EN 0x80 /* AWB gain enable */ +#define AWB_CTRL0_CALC_EN 0x40 /* AWB calculate enable */ +#define AWB_CTRL0_WBC_MASK 0x0F /* WBC threshold 2 */ + +#define DSP_CTRL1 0x64 /* DSP Control Byte 1 */ +#define DSP_CTRL1_FIFO_EN 0x80 /* FIFO enable/disable selection */ +#define DSP_CTRL1_UV_EN 0x40 /* UV adjust function ON/OFF selection */ +#define DSP_CTRL1_SDE_EN 0x20 /* SDE enable */ +#define DSP_CTRL1_MTRX_EN 0x10 /* Color matrix ON/OFF selection */ +#define DSP_CTRL1_INTRP_EN 0x08 /* Interpolation ON/OFF selection */ +#define DSP_CTRL1_GAMMA_EN 0x04 /* Gamma function ON/OFF selection */ +#define DSP_CTRL1_BLACK_EN 0x02 /* Black defect auto correction ON/OFF */ +#define DSP_CTRL1_WHITE_EN 0x01 /* White defect auto correction ON/OFF */ + +#define DSP_CTRL2 0x65 /* DSP Control Byte 2 */ +#define DSP_CTRL2_VDCW_EN 0x08 /* Vertical DCW enable */ +#define DSP_CTRL2_HDCW_EN 0x04 /* Horizontal DCW enable */ +#define DSP_CTRL2_VZOOM_EN 0x02 /* Vertical zoom out enable */ +#define DSP_CTRL2_HZOOM_EN 0x01 /* Horizontal zoom out enable */ + +#define DSP_CTRL3 0x66 /* DSP Control Byte 3 */ +#define DSP_CTRL3_UV_EN 0x80 /* UV output sequence option */ +#define DSP_CTRL3_CBAR_EN 0x20 /* DSP color bar ON/OFF selection */ +#define DSP_CTRL3_FIFO_EN 0x08 /* FIFO power down ON/OFF selection */ +#define DSP_CTRL3_SCAL1_PWDN 0x04 /* Scaling module power down control 1 */ +#define DSP_CTRL3_SCAL2_PWDN 0x02 /* Scaling module power down control 2 */ +#define DSP_CTRL3_INTRP_PWDN 0x01 /* Interpolation module power down control */ +#define DSP_CTRL3_SET_CBAR(r, x) ((r&0xDF)|((x&1)<<5)) + + +#define DSP_CTRL4 0x67 /* DSP Control Byte 4 */ +#define DSP_CTRL4_YUV_RGB 0x00 /* Output selection YUV or RGB */ +#define DSP_CTRL4_RAW8 0x02 /* Output selection RAW8 */ +#define DSP_CTRL4_RAW10 0x03 /* Output selection RAW10 */ + + +#define AWB_BIAS 0x68 /* AWB BLC Level Clip */ +#define AWB_CTRL1 0x69 /* AWB Control 1 */ +#define AWB_CTRL2 0x6A /* AWB Control 2 */ + +#define AWB_CTRL3 0x6B /* AWB Control 3 */ +#define AWB_CTRL3_ADVANCED 0x80 /* AWB mode select - Advanced AWB */ +#define AWB_CTRL3_SIMPLE 0x00 /* AWB mode select - Simple AWB */ + +#define AWB_CTRL4 0x6C /* AWB Control 4 */ +#define AWB_CTRL5 0x6D /* AWB Control 5 */ +#define AWB_CTRL6 0x6E /* AWB Control 6 */ +#define AWB_CTRL7 0x6F /* AWB Control 7 */ +#define AWB_CTRL8 0x70 /* AWB Control 8 */ +#define AWB_CTRL9 0x71 /* AWB Control 9 */ +#define AWB_CTRL10 0x72 /* AWB Control 10 */ +#define AWB_CTRL11 0x73 /* AWB Control 11 */ +#define AWB_CTRL12 0x74 /* AWB Control 12 */ +#define AWB_CTRL13 0x75 /* AWB Control 13 */ +#define AWB_CTRL14 0x76 /* AWB Control 14 */ +#define AWB_CTRL15 0x77 /* AWB Control 15 */ +#define AWB_CTRL16 0x78 /* AWB Control 16 */ +#define AWB_CTRL17 0x79 /* AWB Control 17 */ +#define AWB_CTRL18 0x7A /* AWB Control 18 */ +#define AWB_CTRL19 0x7B /* AWB Control 19 */ +#define AWB_CTRL20 0x7C /* AWB Control 20 */ +#define AWB_CTRL21 0x7D /* AWB Control 21 */ +#define GAM1 0x7E /* Gamma Curve 1st Segment Input End Point 0x04 Output Value */ +#define GAM2 0x7F /* Gamma Curve 2nd Segment Input End Point 0x08 Output Value */ +#define GAM3 0x80 /* Gamma Curve 3rd Segment Input End Point 0x10 Output Value */ +#define GAM4 0x81 /* Gamma Curve 4th Segment Input End Point 0x20 Output Value */ +#define GAM5 0x82 /* Gamma Curve 5th Segment Input End Point 0x28 Output Value */ +#define GAM6 0x83 /* Gamma Curve 6th Segment Input End Point 0x30 Output Value */ +#define GAM7 0x84 /* Gamma Curve 7th Segment Input End Point 0x38 Output Value */ +#define GAM8 0x85 /* Gamma Curve 8th Segment Input End Point 0x40 Output Value */ +#define GAM9 0x86 /* Gamma Curve 9th Segment Input End Point 0x48 Output Value */ +#define GAM10 0x87 /* Gamma Curve 10th Segment Input End Point 0x50 Output Value */ +#define GAM11 0x88 /* Gamma Curve 11th Segment Input End Point 0x60 Output Value */ +#define GAM12 0x89 /* Gamma Curve 12th Segment Input End Point 0x70 Output Value */ +#define GAM13 0x8A /* Gamma Curve 13th Segment Input End Point 0x90 Output Value */ +#define GAM14 0x8B /* Gamma Curve 14th Segment Input End Point 0xB0 Output Value */ +#define GAM15 0x8C /* Gamma Curve 15th Segment Input End Point 0xD0 Output Value */ +#define SLOP 0x8D /* Gamma Curve Highest Segment Slope */ +#define DNSTH 0x8E /* De-noise Threshold */ +#define EDGE0 0x8F /* Edge Enhancement Strength Control */ +#define EDGE1 0x90 /* Edge Enhancement Threshold Control */ +#define DNSOFF 0x91 /* Auto De-noise Threshold Control */ +#define EDGE2 0x92 /* Edge Enhancement Strength Upper Limit */ +#define EDGE3 0x93 /* Edge Enhancement Strength Upper Limit */ +#define MTX1 0x94 /* Matrix Coefficient 1 */ +#define MTX2 0x95 /* Matrix Coefficient 2 */ +#define MTX3 0x96 /* Matrix Coefficient 3 */ +#define MTX4 0x97 /* Matrix Coefficient 4 */ +#define MTX5 0x98 /* Matrix Coefficient 5 */ +#define MTX6 0x99 /* Matrix Coefficient 6 */ + +#define MTX_CTRL 0x9A /* Matrix Control */ +#define MTX_CTRL_DBL_EN 0x80 /* Matrix double ON/OFF selection */ + +#define BRIGHTNESS 0x9B /* Brightness Control */ +#define CONTRAST 0x9C /* Contrast Gain */ +#define UVADJ0 0x9E /* Auto UV Adjust Control 0 */ +#define UVADJ1 0x9F /* Auto UV Adjust Control 1 */ +#define SCAL0 0xA0 /* DCW Ratio Control */ +#define SCAL1 0xA1 /* Horizontal Zoom Out Control */ +#define SCAL2 0xA2 /* Vertical Zoom Out Control */ +#define FIFODLYM 0xA3 /* FIFO Manual Mode Delay Control */ +#define FIFODLYA 0xA4 /* FIFO Auto Mode Delay Control */ + +#define SDE 0xA6 /* Special Digital Effect Control */ +#define SDE_NEGATIVE_EN 0x40 /* Negative image enable */ +#define SDE_GRAYSCALE_EN 0x20 /* Gray scale image enable */ +#define SDE_V_FIXED_EN 0x10 /* V fixed value enable */ +#define SDE_U_FIXED_EN 0x08 /* U fixed value enable */ +#define SDE_CONT_BRIGHT_EN 0x04 /* Contrast/Brightness enable */ +#define SDE_SATURATION_EN 0x02 /* Saturation enable */ +#define SDE_HUE_EN 0x01 /* Hue enable */ + +#define USAT 0xA7 /* U Component Saturation Gain */ +#define VSAT 0xA8 /* V Component Saturation Gain */ +#define HUECOS 0xA9 /* Cosine value × 0x80 */ +#define HUESIN 0xAA /* Sine value × 0x80 */ +#define SIGN_BIT 0xAB /* Sign Bit for Hue and Brightness */ + +#define DSPAUTO 0xAC /* DSP Auto Function ON/OFF Control */ +#define DSPAUTO_AWB_EN 0x80 /* AWB auto threshold control */ +#define DSPAUTO_DENOISE_EN 0x40 /* De-noise auto threshold control */ +#define DSPAUTO_EDGE_EN 0x20 /* Sharpness (edge enhancement) auto strength control */ +#define DSPAUTO_UV_EN 0x10 /* UV adjust auto slope control */ +#define DSPAUTO_SCAL0_EN 0x08 /* Auto scaling factor control (register SCAL0 (0xA0)) */ +#define DSPAUTO_SCAL1_EN 0x04 /* Auto scaling factor control (registers SCAL1 (0xA1 and SCAL2 (0xA2))*/ +#define SET_REG(reg, x) (##reg_DEFAULT|x) +#endif //__REG_REGS_H__ diff --git a/esp32-cam-rtos-allframes/sccb.c b/esp32-cam-rtos-allframes/sccb.c new file mode 100644 index 0000000..d2f5fb9 --- /dev/null +++ b/esp32-cam-rtos-allframes/sccb.c @@ -0,0 +1,260 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * SCCB (I2C like) driver. + * + */ +#include +#include +#include +#include "sccb.h" +#include +#include "sdkconfig.h" +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#else +#include "esp_log.h" +static const char* TAG = "sccb"; +#endif + +//#undef CONFIG_SCCB_HARDWARE_I2C + +#define LITTLETOBIG(x) ((x<<8)|(x>>8)) + +#ifdef CONFIG_SCCB_HARDWARE_I2C +#include "driver/i2c.h" + +#define SCCB_FREQ 100000 /*!< I2C master frequency*/ +#define WRITE_BIT I2C_MASTER_WRITE /*!< I2C master write */ +#define READ_BIT I2C_MASTER_READ /*!< I2C master read */ +#define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/ +#define ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */ +#define ACK_VAL 0x0 /*!< I2C ack value */ +#define NACK_VAL 0x1 /*!< I2C nack value */ +#if CONFIG_SCCB_HARDWARE_I2C_PORT1 +const int SCCB_I2C_PORT = 1; +#else +const int SCCB_I2C_PORT = 0; +#endif +static uint8_t ESP_SLAVE_ADDR = 0x3c; +#else +#include "twi.h" +#endif + +int SCCB_Init(int pin_sda, int pin_scl) +{ + ESP_LOGI(TAG, "pin_sda %d pin_scl %d\n", pin_sda, pin_scl); +#ifdef CONFIG_SCCB_HARDWARE_I2C + //log_i("SCCB_Init start"); + i2c_config_t conf; + conf.mode = I2C_MODE_MASTER; + conf.sda_io_num = pin_sda; + conf.sda_pullup_en = GPIO_PULLUP_ENABLE; + conf.scl_io_num = pin_scl; + conf.scl_pullup_en = GPIO_PULLUP_ENABLE; + conf.master.clk_speed = SCCB_FREQ; + + i2c_param_config(SCCB_I2C_PORT, &conf); + i2c_driver_install(SCCB_I2C_PORT, conf.mode, 0, 0, 0); +#else + twi_init(pin_sda, pin_scl); +#endif + return 0; +} + +uint8_t SCCB_Probe() +{ +#ifdef CONFIG_SCCB_HARDWARE_I2C + uint8_t slave_addr = 0x0; + while(slave_addr < 0x7f) { + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( slave_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); + i2c_master_stop(cmd); + esp_err_t ret = i2c_master_cmd_begin(SCCB_I2C_PORT, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + if( ret == ESP_OK) { + ESP_SLAVE_ADDR = slave_addr; + return ESP_SLAVE_ADDR; + } + slave_addr++; + } + return ESP_SLAVE_ADDR; +#else + uint8_t reg = 0x00; + uint8_t slv_addr = 0x00; + + ESP_LOGI(TAG, "SCCB_Probe start"); + for (uint8_t i = 0; i < 127; i++) { + if (twi_writeTo(i, ®, 1, true) == 0) { + slv_addr = i; + break; + } + + if (i!=126) { + vTaskDelay(10 / portTICK_PERIOD_MS); // Necessary for OV7725 camera (not for OV2640). + } + } + return slv_addr; +#endif +} + +uint8_t SCCB_Read(uint8_t slv_addr, uint8_t reg) +{ +#ifdef CONFIG_SCCB_HARDWARE_I2C + uint8_t data=0; + esp_err_t ret = ESP_FAIL; + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); + i2c_master_write_byte(cmd, reg, ACK_CHECK_EN); + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin(SCCB_I2C_PORT, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + if(ret != ESP_OK) return -1; + cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( slv_addr << 1 ) | READ_BIT, ACK_CHECK_EN); + i2c_master_read_byte(cmd, &data, NACK_VAL); + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin(SCCB_I2C_PORT, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + if(ret != ESP_OK) { + ESP_LOGE(TAG, "SCCB_Read Failed addr:0x%02x, reg:0x%02x, data:0x%02x, ret:%d", slv_addr, reg, data, ret); + } + return data; +#else + uint8_t data=0; + + int rc = twi_writeTo(slv_addr, ®, 1, true); + if (rc != 0) { + data = 0xff; + } else { + rc = twi_readFrom(slv_addr, &data, 1, true); + if (rc != 0) { + data=0xFF; + } + } + if (rc != 0) { + ESP_LOGE(TAG, "SCCB_Read [%02x] failed rc=%d\n", reg, rc); + } + return data; +#endif +} + +uint8_t SCCB_Write(uint8_t slv_addr, uint8_t reg, uint8_t data) +{ +#ifdef CONFIG_SCCB_HARDWARE_I2C + esp_err_t ret = ESP_FAIL; + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); + i2c_master_write_byte(cmd, reg, ACK_CHECK_EN); + i2c_master_write_byte(cmd, data, ACK_CHECK_EN); + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin(SCCB_I2C_PORT, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + if(ret != ESP_OK) { + ESP_LOGE(TAG, "SCCB_Write Failed addr:0x%02x, reg:0x%02x, data:0x%02x, ret:%d", slv_addr, reg, data, ret); + } + return ret == ESP_OK ? 0 : -1; +#else + uint8_t ret=0; + uint8_t buf[] = {reg, data}; + + if(twi_writeTo(slv_addr, buf, 2, true) != 0) { + ret=0xFF; + } + if (ret != 0) { + ESP_LOGE(TAG, "SCCB_Write [%02x]=%02x failed\n", reg, data); + } + return ret; +#endif +} + +uint8_t SCCB_Read16(uint8_t slv_addr, uint16_t reg) +{ +#ifdef CONFIG_SCCB_HARDWARE_I2C + uint8_t data=0; + esp_err_t ret = ESP_FAIL; + uint16_t reg_htons = LITTLETOBIG(reg); + uint8_t *reg_u8 = (uint8_t *)®_htons; + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); + i2c_master_write_byte(cmd, reg_u8[0], ACK_CHECK_EN); + i2c_master_write_byte(cmd, reg_u8[1], ACK_CHECK_EN); + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin(SCCB_I2C_PORT, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + if(ret != ESP_OK) return -1; + cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( slv_addr << 1 ) | READ_BIT, ACK_CHECK_EN); + i2c_master_read_byte(cmd, &data, NACK_VAL); + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin(SCCB_I2C_PORT, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + if(ret != ESP_OK) { + ESP_LOGE(TAG, "W [%04x]=%02x fail\n", reg, data); + } + return data; +#else + uint8_t data=0; + uint16_t reg_htons = LITTLETOBIG(reg); + uint8_t *reg_u8 = (uint8_t *)®_htons; + uint8_t buf[] = {reg_u8[0], reg_u8[1]}; + + int rc = twi_writeTo(slv_addr, buf, 2, true); + if (rc != 0) { + data = 0xff; + } else { + rc = twi_readFrom(slv_addr, &data, 1, true); + if (rc != 0) { + data=0xFF; + } + } + if (rc != 0) { + ESP_LOGE(TAG, "R [%04x] fail rc=%d\n", reg, rc); + } + return data; +#endif +} + +uint8_t SCCB_Write16(uint8_t slv_addr, uint16_t reg, uint8_t data) +{ + static uint16_t i = 0; +#ifdef CONFIG_SCCB_HARDWARE_I2C + esp_err_t ret = ESP_FAIL; + uint16_t reg_htons = LITTLETOBIG(reg); + uint8_t *reg_u8 = (uint8_t *)®_htons; + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); + i2c_master_write_byte(cmd, reg_u8[0], ACK_CHECK_EN); + i2c_master_write_byte(cmd, reg_u8[1], ACK_CHECK_EN); + i2c_master_write_byte(cmd, data, ACK_CHECK_EN); + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin(SCCB_I2C_PORT, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + if(ret != ESP_OK) { + ESP_LOGE(TAG, "W [%04x]=%02x %d fail\n", reg, data, i++); + } + return ret == ESP_OK ? 0 : -1; +#else + uint8_t ret=0; + uint16_t reg_htons = LITTLETOBIG(reg); + uint8_t *reg_u8 = (uint8_t *)®_htons; + uint8_t buf[] = {reg_u8[0], reg_u8[1], data}; + + if(twi_writeTo(slv_addr, buf, 3, true) != 0) { + ret = 0xFF; + } + if (ret != 0) { + ESP_LOGE(TAG, "W [%04x]=%02x %d fail\n", reg, data, i++); + } + return ret; +#endif +} diff --git a/esp32-cam-rtos-allframes/sccb.h b/esp32-cam-rtos-allframes/sccb.h new file mode 100644 index 0000000..4d5b5b4 --- /dev/null +++ b/esp32-cam-rtos-allframes/sccb.h @@ -0,0 +1,18 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * SCCB (I2C like) driver. + * + */ +#ifndef __SCCB_H__ +#define __SCCB_H__ +#include +int SCCB_Init(int pin_sda, int pin_scl); +uint8_t SCCB_Probe(); +uint8_t SCCB_Read(uint8_t slv_addr, uint8_t reg); +uint8_t SCCB_Write(uint8_t slv_addr, uint8_t reg, uint8_t data); +uint8_t SCCB_Read16(uint8_t slv_addr, uint16_t reg); +uint8_t SCCB_Write16(uint8_t slv_addr, uint16_t reg, uint8_t data); +#endif // __SCCB_H__ diff --git a/esp32-cam-rtos-allframes/sensor.c b/esp32-cam-rtos-allframes/sensor.c new file mode 100644 index 0000000..2e6d111 --- /dev/null +++ b/esp32-cam-rtos-allframes/sensor.c @@ -0,0 +1,28 @@ +#include "sensor.h" + +const resolution_info_t resolution[FRAMESIZE_INVALID] = { + { 96, 96, ASPECT_RATIO_1X1 }, /* 96x96 */ + { 160, 120, ASPECT_RATIO_4X3 }, /* QQVGA */ + { 176, 144, ASPECT_RATIO_5X4 }, /* QCIF */ + { 240, 176, ASPECT_RATIO_4X3 }, /* HQVGA */ + { 240, 240, ASPECT_RATIO_1X1 }, /* 240x240 */ + { 320, 240, ASPECT_RATIO_4X3 }, /* QVGA */ + { 400, 296, ASPECT_RATIO_4X3 }, /* CIF */ + { 480, 320, ASPECT_RATIO_3X2 }, /* HVGA */ + { 640, 480, ASPECT_RATIO_4X3 }, /* VGA */ + { 800, 600, ASPECT_RATIO_4X3 }, /* SVGA */ + { 1024, 768, ASPECT_RATIO_4X3 }, /* XGA */ + { 1280, 720, ASPECT_RATIO_16X9 }, /* HD */ + { 1280, 1024, ASPECT_RATIO_5X4 }, /* SXGA */ + { 1600, 1200, ASPECT_RATIO_4X3 }, /* UXGA */ + // 3MP Sensors + { 1920, 1080, ASPECT_RATIO_16X9 }, /* FHD */ + { 720, 1280, ASPECT_RATIO_9X16 }, /* Portrait HD */ + { 864, 1536, ASPECT_RATIO_9X16 }, /* Portrait 3MP */ + { 2048, 1536, ASPECT_RATIO_4X3 }, /* QXGA */ + // 5MP Sensors + { 2560, 1440, ASPECT_RATIO_16X9 }, /* QHD */ + { 2560, 1600, ASPECT_RATIO_16X10 }, /* WQXGA */ + { 1088, 1920, ASPECT_RATIO_9X16 }, /* Portrait FHD */ + { 2560, 1920, ASPECT_RATIO_4X3 }, /* QSXGA */ +}; diff --git a/esp32-cam-rtos-allframes/sensor.h b/esp32-cam-rtos-allframes/sensor.h new file mode 100644 index 0000000..3ea7e2c --- /dev/null +++ b/esp32-cam-rtos-allframes/sensor.h @@ -0,0 +1,191 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Sensor abstraction layer. + * + */ +#ifndef __SENSOR_H__ +#define __SENSOR_H__ +#include +#include + +#define OV9650_PID (0x96) +#define OV7725_PID (0x77) +#define OV2640_PID (0x26) +#define OV3660_PID (0x36) +#define OV5640_PID (0x56) + +typedef enum { + PIXFORMAT_RGB565, // 2BPP/RGB565 + PIXFORMAT_YUV422, // 2BPP/YUV422 + PIXFORMAT_GRAYSCALE, // 1BPP/GRAYSCALE + PIXFORMAT_JPEG, // JPEG/COMPRESSED + PIXFORMAT_RGB888, // 3BPP/RGB888 + PIXFORMAT_RAW, // RAW + PIXFORMAT_RGB444, // 3BP2P/RGB444 + PIXFORMAT_RGB555, // 3BP2P/RGB555 +} pixformat_t; + +typedef enum { + FRAMESIZE_96X96, // 96x96 + FRAMESIZE_QQVGA, // 160x120 + FRAMESIZE_QCIF, // 176x144 + FRAMESIZE_HQVGA, // 240x176 + FRAMESIZE_240X240, // 240x240 + FRAMESIZE_QVGA, // 320x240 + FRAMESIZE_CIF, // 400x296 + FRAMESIZE_HVGA, // 480x320 + FRAMESIZE_VGA, // 640x480 + FRAMESIZE_SVGA, // 800x600 + FRAMESIZE_XGA, // 1024x768 + FRAMESIZE_HD, // 1280x720 + FRAMESIZE_SXGA, // 1280x1024 + FRAMESIZE_UXGA, // 1600x1200 + // 3MP Sensors + FRAMESIZE_FHD, // 1920x1080 + FRAMESIZE_P_HD, // 720x1280 + FRAMESIZE_P_3MP, // 864x1536 + FRAMESIZE_QXGA, // 2048x1536 + // 5MP Sensors + FRAMESIZE_QHD, // 2560x1440 + FRAMESIZE_WQXGA, // 2560x1600 + FRAMESIZE_P_FHD, // 1080x1920 + FRAMESIZE_QSXGA, // 2560x1920 + FRAMESIZE_INVALID +} framesize_t; + +typedef enum { + ASPECT_RATIO_4X3, + ASPECT_RATIO_3X2, + ASPECT_RATIO_16X10, + ASPECT_RATIO_5X3, + ASPECT_RATIO_16X9, + ASPECT_RATIO_21X9, + ASPECT_RATIO_5X4, + ASPECT_RATIO_1X1, + ASPECT_RATIO_9X16 +} aspect_ratio_t; + +typedef enum { + GAINCEILING_2X, + GAINCEILING_4X, + GAINCEILING_8X, + GAINCEILING_16X, + GAINCEILING_32X, + GAINCEILING_64X, + GAINCEILING_128X, +} gainceiling_t; + +typedef struct { + uint16_t max_width; + uint16_t max_height; + uint16_t start_x; + uint16_t start_y; + uint16_t end_x; + uint16_t end_y; + uint16_t offset_x; + uint16_t offset_y; + uint16_t total_x; + uint16_t total_y; +} ratio_settings_t; + +typedef struct { + const uint16_t width; + const uint16_t height; + const aspect_ratio_t aspect_ratio; +} resolution_info_t; + +// Resolution table (in sensor.c) +extern const resolution_info_t resolution[]; + +typedef struct { + uint8_t MIDH; + uint8_t MIDL; + uint8_t PID; + uint8_t VER; +} sensor_id_t; + +typedef struct { + framesize_t framesize;//0 - 10 + bool scale; + bool binning; + uint8_t quality;//0 - 63 + int8_t brightness;//-2 - 2 + int8_t contrast;//-2 - 2 + int8_t saturation;//-2 - 2 + int8_t sharpness;//-2 - 2 + uint8_t denoise; + uint8_t special_effect;//0 - 6 + uint8_t wb_mode;//0 - 4 + uint8_t awb; + uint8_t awb_gain; + uint8_t aec; + uint8_t aec2; + int8_t ae_level;//-2 - 2 + uint16_t aec_value;//0 - 1200 + uint8_t agc; + uint8_t agc_gain;//0 - 30 + uint8_t gainceiling;//0 - 6 + uint8_t bpc; + uint8_t wpc; + uint8_t raw_gma; + uint8_t lenc; + uint8_t hmirror; + uint8_t vflip; + uint8_t dcw; + uint8_t colorbar; +} camera_status_t; + +typedef struct _sensor sensor_t; +typedef struct _sensor { + sensor_id_t id; // Sensor ID. + uint8_t slv_addr; // Sensor I2C slave address. + pixformat_t pixformat; + camera_status_t status; + int xclk_freq_hz; + + // Sensor function pointers + int (*init_status) (sensor_t *sensor); + int (*reset) (sensor_t *sensor); + int (*set_pixformat) (sensor_t *sensor, pixformat_t pixformat); + int (*set_framesize) (sensor_t *sensor, framesize_t framesize); + int (*set_contrast) (sensor_t *sensor, int level); + int (*set_brightness) (sensor_t *sensor, int level); + int (*set_saturation) (sensor_t *sensor, int level); + int (*set_sharpness) (sensor_t *sensor, int level); + int (*set_denoise) (sensor_t *sensor, int level); + int (*set_gainceiling) (sensor_t *sensor, gainceiling_t gainceiling); + int (*set_quality) (sensor_t *sensor, int quality); + int (*set_colorbar) (sensor_t *sensor, int enable); + int (*set_whitebal) (sensor_t *sensor, int enable); + int (*set_gain_ctrl) (sensor_t *sensor, int enable); + int (*set_exposure_ctrl) (sensor_t *sensor, int enable); + int (*set_hmirror) (sensor_t *sensor, int enable); + int (*set_vflip) (sensor_t *sensor, int enable); + + int (*set_aec2) (sensor_t *sensor, int enable); + int (*set_awb_gain) (sensor_t *sensor, int enable); + int (*set_agc_gain) (sensor_t *sensor, int gain); + int (*set_aec_value) (sensor_t *sensor, int gain); + + int (*set_special_effect) (sensor_t *sensor, int effect); + int (*set_wb_mode) (sensor_t *sensor, int mode); + int (*set_ae_level) (sensor_t *sensor, int level); + + int (*set_dcw) (sensor_t *sensor, int enable); + int (*set_bpc) (sensor_t *sensor, int enable); + int (*set_wpc) (sensor_t *sensor, int enable); + + int (*set_raw_gma) (sensor_t *sensor, int enable); + int (*set_lenc) (sensor_t *sensor, int enable); + + int (*get_reg) (sensor_t *sensor, int reg, int mask); + int (*set_reg) (sensor_t *sensor, int reg, int mask, int value); + int (*set_res_raw) (sensor_t *sensor, int startX, int startY, int endX, int endY, int offsetX, int offsetY, int totalX, int totalY, int outputX, int outputY, bool scale, bool binning); + int (*set_pll) (sensor_t *sensor, int bypass, int mul, int sys, int root, int pre, int seld5, int pclken, int pclk); + int (*set_xclk) (sensor_t *sensor, int timer, int xclk); +} sensor_t; + +#endif /* __SENSOR_H__ */ diff --git a/esp32-cam-rtos-allframes/to_bmp.c b/esp32-cam-rtos-allframes/to_bmp.c new file mode 100644 index 0000000..59455de --- /dev/null +++ b/esp32-cam-rtos-allframes/to_bmp.c @@ -0,0 +1,315 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include +#include +#include "img_converters.h" +#include "soc/efuse_reg.h" +#include "esp_heap_caps.h" +#include "yuv.h" +#include "sdkconfig.h" +#include "esp_jpg_decode.h" + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define TAG "" +#else +#include "esp_log.h" +static const char* TAG = "to_bmp"; +#endif + +static const int BMP_HEADER_LEN = 54; + +typedef struct { + uint32_t filesize; + uint32_t reserved; + uint32_t fileoffset_to_pixelarray; + uint32_t dibheadersize; + int32_t width; + int32_t height; + uint16_t planes; + uint16_t bitsperpixel; + uint32_t compression; + uint32_t imagesize; + uint32_t ypixelpermeter; + uint32_t xpixelpermeter; + uint32_t numcolorspallette; + uint32_t mostimpcolor; +} bmp_header_t; + +typedef struct { + uint16_t width; + uint16_t height; + uint16_t data_offset; + const uint8_t *input; + uint8_t *output; +} rgb_jpg_decoder; + +static void *_malloc(size_t size) +{ + return heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); +} + +//output buffer and image width +static bool _rgb_write(void * arg, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t *data) +{ + rgb_jpg_decoder * jpeg = (rgb_jpg_decoder *)arg; + if(!data){ + if(x == 0 && y == 0){ + //write start + jpeg->width = w; + jpeg->height = h; + //if output is null, this is BMP + if(!jpeg->output){ + jpeg->output = (uint8_t *)_malloc((w*h*3)+jpeg->data_offset); + if(!jpeg->output){ + return false; + } + } + } else { + //write end + } + return true; + } + + size_t jw = jpeg->width*3; + size_t t = y * jw; + size_t b = t + (h * jw); + size_t l = x * 3; + uint8_t *out = jpeg->output+jpeg->data_offset; + uint8_t *o = out; + size_t iy, ix; + + w = w * 3; + + for(iy=t; iyinput + index, len); + } + return len; +} + +static bool jpg2rgb888(const uint8_t *src, size_t src_len, uint8_t * out, jpg_scale_t scale) +{ + rgb_jpg_decoder jpeg; + jpeg.width = 0; + jpeg.height = 0; + jpeg.input = src; + jpeg.output = out; + jpeg.data_offset = 0; + + if(esp_jpg_decode(src_len, scale, _jpg_read, _rgb_write, (void*)&jpeg) != ESP_OK){ + return false; + } + return true; +} + +bool jpg2bmp(const uint8_t *src, size_t src_len, uint8_t ** out, size_t * out_len) +{ + + rgb_jpg_decoder jpeg; + jpeg.width = 0; + jpeg.height = 0; + jpeg.input = src; + jpeg.output = NULL; + jpeg.data_offset = BMP_HEADER_LEN; + + if(esp_jpg_decode(src_len, JPG_SCALE_NONE, _jpg_read, _rgb_write, (void*)&jpeg) != ESP_OK){ + return false; + } + + size_t output_size = jpeg.width*jpeg.height*3; + + jpeg.output[0] = 'B'; + jpeg.output[1] = 'M'; + bmp_header_t * bitmap = (bmp_header_t*)&jpeg.output[2]; + bitmap->reserved = 0; + bitmap->filesize = output_size+BMP_HEADER_LEN; + bitmap->fileoffset_to_pixelarray = BMP_HEADER_LEN; + bitmap->dibheadersize = 40; + bitmap->width = jpeg.width; + bitmap->height = -jpeg.height;//set negative for top to bottom + bitmap->planes = 1; + bitmap->bitsperpixel = 24; + bitmap->compression = 0; + bitmap->imagesize = output_size; + bitmap->ypixelpermeter = 0x0B13 ; //2835 , 72 DPI + bitmap->xpixelpermeter = 0x0B13 ; //2835 , 72 DPI + bitmap->numcolorspallette = 0; + bitmap->mostimpcolor = 0; + + *out = jpeg.output; + *out_len = output_size+BMP_HEADER_LEN; + + return true; +} + +bool fmt2rgb888(const uint8_t *src_buf, size_t src_len, pixformat_t format, uint8_t * rgb_buf) +{ + int pix_count = 0; + if(format == PIXFORMAT_JPEG) { + return jpg2rgb888(src_buf, src_len, rgb_buf, JPG_SCALE_NONE); + } else if(format == PIXFORMAT_RGB888) { + memcpy(rgb_buf, src_buf, src_len); + } else if(format == PIXFORMAT_RGB565) { + int i; + uint8_t hb, lb; + pix_count = src_len / 2; + for(i=0; i> 3; + *rgb_buf++ = hb & 0xF8; + } + } else if(format == PIXFORMAT_GRAYSCALE) { + int i; + uint8_t b; + pix_count = src_len; + for(i=0; ireserved = 0; + bitmap->filesize = out_size; + bitmap->fileoffset_to_pixelarray = BMP_HEADER_LEN; + bitmap->dibheadersize = 40; + bitmap->width = width; + bitmap->height = -height;//set negative for top to bottom + bitmap->planes = 1; + bitmap->bitsperpixel = 24; + bitmap->compression = 0; + bitmap->imagesize = pix_count * 3; + bitmap->ypixelpermeter = 0x0B13 ; //2835 , 72 DPI + bitmap->xpixelpermeter = 0x0B13 ; //2835 , 72 DPI + bitmap->numcolorspallette = 0; + bitmap->mostimpcolor = 0; + + uint8_t * rgb_buf = out_buf + BMP_HEADER_LEN; + uint8_t * src_buf = src; + + + //convert data to RGB888 + if(format == PIXFORMAT_RGB888) { + memcpy(rgb_buf, src_buf, pix_count*3); + } else if(format == PIXFORMAT_RGB565) { + int i; + uint8_t hb, lb; + for(i=0; i> 3; + *rgb_buf++ = hb & 0xF8; + } + } else if(format == PIXFORMAT_GRAYSCALE) { + int i; + uint8_t b; + for(i=0; ibuf, fb->len, fb->width, fb->height, fb->format, out, out_len); +} diff --git a/esp32-cam-rtos-allframes/to_jpg.cpp b/esp32-cam-rtos-allframes/to_jpg.cpp new file mode 100644 index 0000000..f8987a8 --- /dev/null +++ b/esp32-cam-rtos-allframes/to_jpg.cpp @@ -0,0 +1,241 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include +#include +#include "esp_attr.h" +#include "soc/efuse_reg.h" +#include "esp_heap_caps.h" +#include "esp_camera.h" +#include "img_converters.h" +#include "jpge.h" +#include "yuv.h" + +#include "esp_system.h" +#if ESP_IDF_VERSION_MAJOR >= 4 // IDF 4+ +#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 +#include "esp32/spiram.h" +#else +#error Target CONFIG_IDF_TARGET is not supported +#endif +#else // ESP32 Before IDF 4.0 +#include "esp_spiram.h" +#endif + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define TAG "" +#else +#include "esp_log.h" +static const char* TAG = "to_jpg"; +#endif + +static void *_malloc(size_t size) +{ + void * res = malloc(size); + if(res) { + return res; + } + return heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); +} + +static IRAM_ATTR void convert_line_format(uint8_t * src, pixformat_t format, uint8_t * dst, size_t width, size_t in_channels, size_t line) +{ + int i=0, o=0, l=0; + if(format == PIXFORMAT_GRAYSCALE) { + memcpy(dst, src + line * width, width); + } else if(format == PIXFORMAT_RGB888) { + l = width * 3; + src += l * line; + for(i=0; i> 3; + dst[o++] = (src[i+1] & 0x1F) << 3; + } + } else if(format == PIXFORMAT_YUV422) { + uint8_t y0, y1, u, v; + uint8_t r, g, b; + l = width * 2; + src += l * line; + for(i=0; i 100) { + quality = 100; + } + + jpge::params comp_params = jpge::params(); + comp_params.m_subsampling = subsampling; + comp_params.m_quality = quality; + + jpge::jpeg_encoder dst_image; + + if (!dst_image.init(dst_stream, width, height, num_channels, comp_params)) { + ESP_LOGE(TAG, "JPG encoder init failed"); + return false; + } + + uint8_t* line = (uint8_t*)_malloc(width * num_channels); + if(!line) { + ESP_LOGE(TAG, "Scan line malloc failed"); + return false; + } + + for (int i = 0; i < height; i++) { + convert_line_format(src, format, line, width, num_channels, i); + if (!dst_image.process_scanline(line)) { + ESP_LOGE(TAG, "JPG process line %u failed", i); + free(line); + return false; + } + } + free(line); + + if (!dst_image.process_scanline(NULL)) { + ESP_LOGE(TAG, "JPG image finish failed"); + return false; + } + dst_image.deinit(); + return true; +} + +class callback_stream : public jpge::output_stream { +protected: + jpg_out_cb ocb; + void * oarg; + size_t index; + +public: + callback_stream(jpg_out_cb cb, void * arg) : ocb(cb), oarg(arg), index(0) { } + virtual ~callback_stream() { } + virtual bool put_buf(const void* data, int len) + { + index += ocb(oarg, index, data, len); + return true; + } + virtual size_t get_size() const + { + return index; + } +}; + +bool fmt2jpg_cb(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, jpg_out_cb cb, void * arg) +{ + callback_stream dst_stream(cb, arg); + return convert_image(src, width, height, format, quality, &dst_stream); +} + +bool frame2jpg_cb(camera_fb_t * fb, uint8_t quality, jpg_out_cb cb, void * arg) +{ + return fmt2jpg_cb(fb->buf, fb->len, fb->width, fb->height, fb->format, quality, cb, arg); +} + + + +class memory_stream : public jpge::output_stream { +protected: + uint8_t *out_buf; + size_t max_len, index; + +public: + memory_stream(void *pBuf, uint buf_size) : out_buf(static_cast(pBuf)), max_len(buf_size), index(0) { } + + virtual ~memory_stream() { } + + virtual bool put_buf(const void* pBuf, int len) + { + if (!pBuf) { + //end of image + return true; + } + if ((size_t)len > (max_len - index)) { + ESP_LOGW(TAG, "JPG output overflow: %d bytes", len - (max_len - index)); + len = max_len - index; + } + if (len) { + memcpy(out_buf + index, pBuf, len); + index += len; + } + return true; + } + + virtual size_t get_size() const + { + return index; + } +}; + +bool fmt2jpg(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, uint8_t ** out, size_t * out_len) +{ + //todo: allocate proper buffer for holding JPEG data + //this should be enough for CIF frame size + int jpg_buf_len = 64*1024; + + + uint8_t * jpg_buf = (uint8_t *)_malloc(jpg_buf_len); + if(jpg_buf == NULL) { + ESP_LOGE(TAG, "JPG buffer malloc failed"); + return false; + } + memory_stream dst_stream(jpg_buf, jpg_buf_len); + + if(!convert_image(src, width, height, format, quality, &dst_stream)) { + free(jpg_buf); + return false; + } + + *out = jpg_buf; + *out_len = dst_stream.get_size(); + return true; +} + +bool frame2jpg(camera_fb_t * fb, uint8_t quality, uint8_t ** out, size_t * out_len) +{ + return fmt2jpg(fb->buf, fb->len, fb->width, fb->height, fb->format, quality, out, out_len); +} diff --git a/esp32-cam-rtos-allframes/twi.c b/esp32-cam-rtos-allframes/twi.c new file mode 100644 index 0000000..25d71fc --- /dev/null +++ b/esp32-cam-rtos-allframes/twi.c @@ -0,0 +1,432 @@ +/* + si2c.c - Software I2C library for ESP31B + + Copyright (c) 2015 Hristo Gochkov. All rights reserved. + This file is part of the ESP31B core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include +#include +#include "twi.h" +#include "soc/gpio_reg.h" +#include "soc/gpio_struct.h" +#include "soc/io_mux_reg.h" +#include "driver/rtc_io.h" +#include + + +#define LOW 0x0 +#define HIGH 0x1 + +//GPIO FUNCTIONS +#define INPUT 0x01 +#define OUTPUT 0x02 +#define PULLUP 0x04 +#define INPUT_PULLUP 0x05 +#define PULLDOWN 0x08 +#define INPUT_PULLDOWN 0x09 +#define OPEN_DRAIN 0x10 +#define OUTPUT_OPEN_DRAIN 0x12 +#define SPECIAL 0xF0 +#define FUNCTION_1 0x00 +#define FUNCTION_2 0x20 +#define FUNCTION_3 0x40 +#define FUNCTION_4 0x60 +#define FUNCTION_5 0x80 +#define FUNCTION_6 0xA0 + +#define ESP_REG(addr) *((volatile uint32_t *)(addr)) + +const uint8_t pin_to_mux[40] = { 0x44, 0x88, 0x40, 0x84, 0x48, 0x6c, 0x60, 0x64, 0x68, 0x54, 0x58, 0x5c, 0x34, 0x38, 0x30, 0x3c, 0x4c, 0x50, 0x70, 0x74, 0x78, 0x7c, 0x80, 0x8c, 0, 0x24, 0x28, 0x2c, 0, 0, 0, 0, 0x1c, 0x20, 0x14, 0x18, 0x04, 0x08, 0x0c, 0x10}; + +static void pinMode(uint8_t pin, uint8_t mode) +{ + if(pin >= 40) { + return; + } + + uint32_t rtc_reg = rtc_gpio_desc[pin].reg; + + //RTC pins PULL settings + if(rtc_reg) { + //lock rtc + ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].mux); + if(mode & PULLUP) { + ESP_REG(rtc_reg) = (ESP_REG(rtc_reg) | rtc_gpio_desc[pin].pullup) & ~(rtc_gpio_desc[pin].pulldown); + } else if(mode & PULLDOWN) { + ESP_REG(rtc_reg) = (ESP_REG(rtc_reg) | rtc_gpio_desc[pin].pulldown) & ~(rtc_gpio_desc[pin].pullup); + } else { + ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].pullup | rtc_gpio_desc[pin].pulldown); + } + //unlock rtc + } + + uint32_t pinFunction = 0, pinControl = 0; + + //lock gpio + if(mode & INPUT) { + if(pin < 32) { + GPIO.enable_w1tc = BIT(pin); + } else { + GPIO.enable1_w1tc.val = BIT(pin - 32); + } + } else if(mode & OUTPUT) { + if(pin > 33) { + //unlock gpio + return;//pins above 33 can be only inputs + } else if(pin < 32) { + GPIO.enable_w1ts = BIT(pin); + } else { + GPIO.enable1_w1ts.val = BIT(pin - 32); + } + } + + if(mode & PULLUP) { + pinFunction |= FUN_PU; + } else if(mode & PULLDOWN) { + pinFunction |= FUN_PD; + } + + pinFunction |= ((uint32_t)2 << FUN_DRV_S);//what are the drivers? + pinFunction |= FUN_IE;//input enable but required for output as well? + + if(mode & (INPUT | OUTPUT)) { + pinFunction |= ((uint32_t)2 << MCU_SEL_S); + } else if(mode == SPECIAL) { + pinFunction |= ((uint32_t)(((pin)==1||(pin)==3)?0:1) << MCU_SEL_S); + } else { + pinFunction |= ((uint32_t)(mode >> 5) << MCU_SEL_S); + } + + ESP_REG(DR_REG_IO_MUX_BASE + pin_to_mux[pin]) = pinFunction; + + if(mode & OPEN_DRAIN) { + pinControl = (1 << GPIO_PIN0_PAD_DRIVER_S); + } + + GPIO.pin[pin].val = pinControl; + //unlock gpio +} + +static void digitalWrite(uint8_t pin, uint8_t val) +{ + if(val) { + if(pin < 32) { + GPIO.out_w1ts = BIT(pin); + } else if(pin < 34) { + GPIO.out1_w1ts.val = BIT(pin - 32); + } + } else { + if(pin < 32) { + GPIO.out_w1tc = BIT(pin); + } else if(pin < 34) { + GPIO.out1_w1tc.val = BIT(pin - 32); + } + } +} + + +unsigned char twi_dcount = 18; +static unsigned char twi_sda, twi_scl; + + +static inline void SDA_LOW() +{ + //Enable SDA (becomes output and since GPO is 0 for the pin, + // it will pull the line low) + if (twi_sda < 32) { + GPIO.enable_w1ts = BIT(twi_sda); + } else { + GPIO.enable1_w1ts.val = BIT(twi_sda - 32); + } +} + +static inline void SDA_HIGH() +{ + //Disable SDA (becomes input and since it has pullup it will go high) + if (twi_sda < 32) { + GPIO.enable_w1tc = BIT(twi_sda); + } else { + GPIO.enable1_w1tc.val = BIT(twi_sda - 32); + } +} + +static inline uint32_t SDA_READ() +{ + if (twi_sda < 32) { + return (GPIO.in & BIT(twi_sda)) != 0; + } else { + return (GPIO.in1.val & BIT(twi_sda - 32)) != 0; + } +} + +static void SCL_LOW() +{ + if (twi_scl < 32) { + GPIO.enable_w1ts = BIT(twi_scl); + } else { + GPIO.enable1_w1ts.val = BIT(twi_scl - 32); + } +} + +static void SCL_HIGH() +{ + if (twi_scl < 32) { + GPIO.enable_w1tc = BIT(twi_scl); + } else { + GPIO.enable1_w1tc.val = BIT(twi_scl - 32); + } +} + +static uint32_t SCL_READ() +{ + if (twi_scl < 32) { + return (GPIO.in & BIT(twi_scl)) != 0; + } else { + return (GPIO.in1.val & BIT(twi_scl - 32)) != 0; + } +} + + +#ifndef FCPU80 +#define FCPU80 80000000L +#endif + +#if F_CPU == FCPU80 +#define TWI_CLOCK_STRETCH 800 +#else +#define TWI_CLOCK_STRETCH 1600 +#endif + +void twi_setClock(unsigned int freq) +{ +#if F_CPU == FCPU80 + if(freq <= 100000) { + twi_dcount = 19; //about 100KHz + } else if(freq <= 200000) { + twi_dcount = 8; //about 200KHz + } else if(freq <= 300000) { + twi_dcount = 3; //about 300KHz + } else if(freq <= 400000) { + twi_dcount = 1; //about 400KHz + } else { + twi_dcount = 1; //about 400KHz + } +#else + if(freq <= 100000) { + twi_dcount = 32; //about 100KHz + } else if(freq <= 200000) { + twi_dcount = 14; //about 200KHz + } else if(freq <= 300000) { + twi_dcount = 8; //about 300KHz + } else if(freq <= 400000) { + twi_dcount = 5; //about 400KHz + } else if(freq <= 500000) { + twi_dcount = 3; //about 500KHz + } else if(freq <= 600000) { + twi_dcount = 2; //about 600KHz + } else { + twi_dcount = 1; //about 700KHz + } +#endif +} + +void twi_init(unsigned char sda, unsigned char scl) +{ + twi_sda = sda; + twi_scl = scl; + pinMode(twi_sda, OUTPUT); + pinMode(twi_scl, OUTPUT); + + digitalWrite(twi_sda, 0); + digitalWrite(twi_scl, 0); + + pinMode(twi_sda, INPUT_PULLUP); + pinMode(twi_scl, INPUT_PULLUP); + twi_setClock(100000); +} + +void twi_stop(void) +{ + pinMode(twi_sda, INPUT); + pinMode(twi_scl, INPUT); +} + +static void twi_delay(unsigned char v) +{ + unsigned int i; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" + unsigned int reg; + for(i=0; i= 4 + timer_conf.clk_cfg = LEDC_AUTO_CLK; +#endif + timer_conf.timer_num = (ledc_timer_t)ledc_timer; + esp_err_t err = ledc_timer_config(&timer_conf); + if (err != ESP_OK) { + ESP_LOGE(TAG, "ledc_timer_config failed for freq %d, rc=%x", xclk_freq_hz, err); + } + return err; +} + +esp_err_t camera_enable_out_clock(camera_config_t* config) +{ + periph_module_enable(PERIPH_LEDC_MODULE); + + esp_err_t err = xclk_timer_conf(config->ledc_timer, config->xclk_freq_hz); + if (err != ESP_OK) { + ESP_LOGE(TAG, "ledc_timer_config failed, rc=%x", err); + return err; + } + + ledc_channel_config_t ch_conf; + ch_conf.gpio_num = config->pin_xclk; + ch_conf.speed_mode = LEDC_HIGH_SPEED_MODE; + ch_conf.channel = config->ledc_channel; + ch_conf.intr_type = LEDC_INTR_DISABLE; + ch_conf.timer_sel = config->ledc_timer; + ch_conf.duty = 2; + ch_conf.hpoint = 0; + err = ledc_channel_config(&ch_conf); + if (err != ESP_OK) { + ESP_LOGE(TAG, "ledc_channel_config failed, rc=%x", err); + return err; + } + return ESP_OK; +} + +void camera_disable_out_clock() +{ + periph_module_disable(PERIPH_LEDC_MODULE); +} diff --git a/esp32-cam-rtos-allframes/xclk.h b/esp32-cam-rtos-allframes/xclk.h new file mode 100644 index 0000000..15ed736 --- /dev/null +++ b/esp32-cam-rtos-allframes/xclk.h @@ -0,0 +1,7 @@ +#pragma once + +#include "camera_common.h" + +esp_err_t camera_enable_out_clock(); + +void camera_disable_out_clock(); diff --git a/esp32-cam-rtos-allframes/yuv.c b/esp32-cam-rtos-allframes/yuv.c new file mode 100644 index 0000000..46034cc --- /dev/null +++ b/esp32-cam-rtos-allframes/yuv.c @@ -0,0 +1,298 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "yuv.h" +#include "esp_attr.h" + +typedef struct { + int16_t vY; + int16_t vVr; + int16_t vVg; + int16_t vUg; + int16_t vUb; +} yuv_table_row; + +static const yuv_table_row yuv_table[256] = { + // Y Vr Vg Ug Ub // # + { -18, -204, 50, 104, -258 }, // 0 + { -17, -202, 49, 103, -256 }, // 1 + { -16, -201, 49, 102, -254 }, // 2 + { -15, -199, 48, 101, -252 }, // 3 + { -13, -197, 48, 100, -250 }, // 4 + { -12, -196, 48, 99, -248 }, // 5 + { -11, -194, 47, 99, -246 }, // 6 + { -10, -193, 47, 98, -244 }, // 7 + { -9, -191, 46, 97, -242 }, // 8 + { -8, -189, 46, 96, -240 }, // 9 + { -6, -188, 46, 95, -238 }, // 10 + { -5, -186, 45, 95, -236 }, // 11 + { -4, -185, 45, 94, -234 }, // 12 + { -3, -183, 44, 93, -232 }, // 13 + { -2, -181, 44, 92, -230 }, // 14 + { -1, -180, 44, 91, -228 }, // 15 + { 0, -178, 43, 91, -226 }, // 16 + { 1, -177, 43, 90, -223 }, // 17 + { 2, -175, 43, 89, -221 }, // 18 + { 3, -173, 42, 88, -219 }, // 19 + { 4, -172, 42, 87, -217 }, // 20 + { 5, -170, 41, 86, -215 }, // 21 + { 6, -169, 41, 86, -213 }, // 22 + { 8, -167, 41, 85, -211 }, // 23 + { 9, -165, 40, 84, -209 }, // 24 + { 10, -164, 40, 83, -207 }, // 25 + { 11, -162, 39, 82, -205 }, // 26 + { 12, -161, 39, 82, -203 }, // 27 + { 13, -159, 39, 81, -201 }, // 28 + { 15, -158, 38, 80, -199 }, // 29 + { 16, -156, 38, 79, -197 }, // 30 + { 17, -154, 37, 78, -195 }, // 31 + { 18, -153, 37, 78, -193 }, // 32 + { 19, -151, 37, 77, -191 }, // 33 + { 20, -150, 36, 76, -189 }, // 34 + { 22, -148, 36, 75, -187 }, // 35 + { 23, -146, 35, 74, -185 }, // 36 + { 24, -145, 35, 73, -183 }, // 37 + { 25, -143, 35, 73, -181 }, // 38 + { 26, -142, 34, 72, -179 }, // 39 + { 27, -140, 34, 71, -177 }, // 40 + { 29, -138, 34, 70, -175 }, // 41 + { 30, -137, 33, 69, -173 }, // 42 + { 31, -135, 33, 69, -171 }, // 43 + { 32, -134, 32, 68, -169 }, // 44 + { 33, -132, 32, 67, -167 }, // 45 + { 34, -130, 32, 66, -165 }, // 46 + { 36, -129, 31, 65, -163 }, // 47 + { 37, -127, 31, 65, -161 }, // 48 + { 38, -126, 30, 64, -159 }, // 49 + { 39, -124, 30, 63, -157 }, // 50 + { 40, -122, 30, 62, -155 }, // 51 + { 41, -121, 29, 61, -153 }, // 52 + { 43, -119, 29, 60, -151 }, // 53 + { 44, -118, 28, 60, -149 }, // 54 + { 45, -116, 28, 59, -147 }, // 55 + { 46, -114, 28, 58, -145 }, // 56 + { 47, -113, 27, 57, -143 }, // 57 + { 48, -111, 27, 56, -141 }, // 58 + { 50, -110, 26, 56, -139 }, // 59 + { 51, -108, 26, 55, -137 }, // 60 + { 52, -106, 26, 54, -135 }, // 61 + { 53, -105, 25, 53, -133 }, // 62 + { 54, -103, 25, 52, -131 }, // 63 + { 55, -102, 25, 52, -129 }, // 64 + { 57, -100, 24, 51, -127 }, // 65 + { 58, -98, 24, 50, -125 }, // 66 + { 59, -97, 23, 49, -123 }, // 67 + { 60, -95, 23, 48, -121 }, // 68 + { 61, -94, 23, 47, -119 }, // 69 + { 62, -92, 22, 47, -117 }, // 70 + { 64, -90, 22, 46, -115 }, // 71 + { 65, -89, 21, 45, -113 }, // 72 + { 66, -87, 21, 44, -110 }, // 73 + { 67, -86, 21, 43, -108 }, // 74 + { 68, -84, 20, 43, -106 }, // 75 + { 69, -82, 20, 42, -104 }, // 76 + { 71, -81, 19, 41, -102 }, // 77 + { 72, -79, 19, 40, -100 }, // 78 + { 73, -78, 19, 39, -98 }, // 79 + { 74, -76, 18, 39, -96 }, // 80 + { 75, -75, 18, 38, -94 }, // 81 + { 76, -73, 17, 37, -92 }, // 82 + { 77, -71, 17, 36, -90 }, // 83 + { 79, -70, 17, 35, -88 }, // 84 + { 80, -68, 16, 34, -86 }, // 85 + { 81, -67, 16, 34, -84 }, // 86 + { 82, -65, 16, 33, -82 }, // 87 + { 83, -63, 15, 32, -80 }, // 88 + { 84, -62, 15, 31, -78 }, // 89 + { 86, -60, 14, 30, -76 }, // 90 + { 87, -59, 14, 30, -74 }, // 91 + { 88, -57, 14, 29, -72 }, // 92 + { 89, -55, 13, 28, -70 }, // 93 + { 90, -54, 13, 27, -68 }, // 94 + { 91, -52, 12, 26, -66 }, // 95 + { 93, -51, 12, 26, -64 }, // 96 + { 94, -49, 12, 25, -62 }, // 97 + { 95, -47, 11, 24, -60 }, // 98 + { 96, -46, 11, 23, -58 }, // 99 + { 97, -44, 10, 22, -56 }, // 100 + { 98, -43, 10, 21, -54 }, // 101 + { 100, -41, 10, 21, -52 }, // 102 + { 101, -39, 9, 20, -50 }, // 103 + { 102, -38, 9, 19, -48 }, // 104 + { 103, -36, 8, 18, -46 }, // 105 + { 104, -35, 8, 17, -44 }, // 106 + { 105, -33, 8, 17, -42 }, // 107 + { 107, -31, 7, 16, -40 }, // 108 + { 108, -30, 7, 15, -38 }, // 109 + { 109, -28, 7, 14, -36 }, // 110 + { 110, -27, 6, 13, -34 }, // 111 + { 111, -25, 6, 13, -32 }, // 112 + { 112, -23, 5, 12, -30 }, // 113 + { 114, -22, 5, 11, -28 }, // 114 + { 115, -20, 5, 10, -26 }, // 115 + { 116, -19, 4, 9, -24 }, // 116 + { 117, -17, 4, 8, -22 }, // 117 + { 118, -15, 3, 8, -20 }, // 118 + { 119, -14, 3, 7, -18 }, // 119 + { 121, -12, 3, 6, -16 }, // 120 + { 122, -11, 2, 5, -14 }, // 121 + { 123, -9, 2, 4, -12 }, // 122 + { 124, -7, 1, 4, -10 }, // 123 + { 125, -6, 1, 3, -8 }, // 124 + { 126, -4, 1, 2, -6 }, // 125 + { 128, -3, 0, 1, -4 }, // 126 + { 129, -1, 0, 0, -2 }, // 127 + { 130, 0, 0, 0, 0 }, // 128 + { 131, 1, 0, 0, 2 }, // 129 + { 132, 3, 0, -1, 4 }, // 130 + { 133, 4, -1, -2, 6 }, // 131 + { 135, 6, -1, -3, 8 }, // 132 + { 136, 7, -1, -4, 10 }, // 133 + { 137, 9, -2, -4, 12 }, // 134 + { 138, 11, -2, -5, 14 }, // 135 + { 139, 12, -3, -6, 16 }, // 136 + { 140, 14, -3, -7, 18 }, // 137 + { 142, 15, -3, -8, 20 }, // 138 + { 143, 17, -4, -8, 22 }, // 139 + { 144, 19, -4, -9, 24 }, // 140 + { 145, 20, -5, -10, 26 }, // 141 + { 146, 22, -5, -11, 28 }, // 142 + { 147, 23, -5, -12, 30 }, // 143 + { 148, 25, -6, -13, 32 }, // 144 + { 150, 27, -6, -13, 34 }, // 145 + { 151, 28, -7, -14, 36 }, // 146 + { 152, 30, -7, -15, 38 }, // 147 + { 153, 31, -7, -16, 40 }, // 148 + { 154, 33, -8, -17, 42 }, // 149 + { 155, 35, -8, -17, 44 }, // 150 + { 157, 36, -8, -18, 46 }, // 151 + { 158, 38, -9, -19, 48 }, // 152 + { 159, 39, -9, -20, 50 }, // 153 + { 160, 41, -10, -21, 52 }, // 154 + { 161, 43, -10, -21, 54 }, // 155 + { 162, 44, -10, -22, 56 }, // 156 + { 164, 46, -11, -23, 58 }, // 157 + { 165, 47, -11, -24, 60 }, // 158 + { 166, 49, -12, -25, 62 }, // 159 + { 167, 51, -12, -26, 64 }, // 160 + { 168, 52, -12, -26, 66 }, // 161 + { 169, 54, -13, -27, 68 }, // 162 + { 171, 55, -13, -28, 70 }, // 163 + { 172, 57, -14, -29, 72 }, // 164 + { 173, 59, -14, -30, 74 }, // 165 + { 174, 60, -14, -30, 76 }, // 166 + { 175, 62, -15, -31, 78 }, // 167 + { 176, 63, -15, -32, 80 }, // 168 + { 178, 65, -16, -33, 82 }, // 169 + { 179, 67, -16, -34, 84 }, // 170 + { 180, 68, -16, -34, 86 }, // 171 + { 181, 70, -17, -35, 88 }, // 172 + { 182, 71, -17, -36, 90 }, // 173 + { 183, 73, -17, -37, 92 }, // 174 + { 185, 75, -18, -38, 94 }, // 175 + { 186, 76, -18, -39, 96 }, // 176 + { 187, 78, -19, -39, 98 }, // 177 + { 188, 79, -19, -40, 100 }, // 178 + { 189, 81, -19, -41, 102 }, // 179 + { 190, 82, -20, -42, 104 }, // 180 + { 192, 84, -20, -43, 106 }, // 181 + { 193, 86, -21, -43, 108 }, // 182 + { 194, 87, -21, -44, 110 }, // 183 + { 195, 89, -21, -45, 113 }, // 184 + { 196, 90, -22, -46, 115 }, // 185 + { 197, 92, -22, -47, 117 }, // 186 + { 199, 94, -23, -47, 119 }, // 187 + { 200, 95, -23, -48, 121 }, // 188 + { 201, 97, -23, -49, 123 }, // 189 + { 202, 98, -24, -50, 125 }, // 190 + { 203, 100, -24, -51, 127 }, // 191 + { 204, 102, -25, -52, 129 }, // 192 + { 206, 103, -25, -52, 131 }, // 193 + { 207, 105, -25, -53, 133 }, // 194 + { 208, 106, -26, -54, 135 }, // 195 + { 209, 108, -26, -55, 137 }, // 196 + { 210, 110, -26, -56, 139 }, // 197 + { 211, 111, -27, -56, 141 }, // 198 + { 213, 113, -27, -57, 143 }, // 199 + { 214, 114, -28, -58, 145 }, // 200 + { 215, 116, -28, -59, 147 }, // 201 + { 216, 118, -28, -60, 149 }, // 202 + { 217, 119, -29, -60, 151 }, // 203 + { 218, 121, -29, -61, 153 }, // 204 + { 219, 122, -30, -62, 155 }, // 205 + { 221, 124, -30, -63, 157 }, // 206 + { 222, 126, -30, -64, 159 }, // 207 + { 223, 127, -31, -65, 161 }, // 208 + { 224, 129, -31, -65, 163 }, // 209 + { 225, 130, -32, -66, 165 }, // 210 + { 226, 132, -32, -67, 167 }, // 211 + { 228, 134, -32, -68, 169 }, // 212 + { 229, 135, -33, -69, 171 }, // 213 + { 230, 137, -33, -69, 173 }, // 214 + { 231, 138, -34, -70, 175 }, // 215 + { 232, 140, -34, -71, 177 }, // 216 + { 233, 142, -34, -72, 179 }, // 217 + { 235, 143, -35, -73, 181 }, // 218 + { 236, 145, -35, -73, 183 }, // 219 + { 237, 146, -35, -74, 185 }, // 220 + { 238, 148, -36, -75, 187 }, // 221 + { 239, 150, -36, -76, 189 }, // 222 + { 240, 151, -37, -77, 191 }, // 223 + { 242, 153, -37, -78, 193 }, // 224 + { 243, 154, -37, -78, 195 }, // 225 + { 244, 156, -38, -79, 197 }, // 226 + { 245, 158, -38, -80, 199 }, // 227 + { 246, 159, -39, -81, 201 }, // 228 + { 247, 161, -39, -82, 203 }, // 229 + { 249, 162, -39, -82, 205 }, // 230 + { 250, 164, -40, -83, 207 }, // 231 + { 251, 165, -40, -84, 209 }, // 232 + { 252, 167, -41, -85, 211 }, // 233 + { 253, 169, -41, -86, 213 }, // 234 + { 254, 170, -41, -86, 215 }, // 235 + { 256, 172, -42, -87, 217 }, // 236 + { 257, 173, -42, -88, 219 }, // 237 + { 258, 175, -43, -89, 221 }, // 238 + { 259, 177, -43, -90, 223 }, // 239 + { 260, 178, -43, -91, 226 }, // 240 + { 261, 180, -44, -91, 228 }, // 241 + { 263, 181, -44, -92, 230 }, // 242 + { 264, 183, -44, -93, 232 }, // 243 + { 265, 185, -45, -94, 234 }, // 244 + { 266, 186, -45, -95, 236 }, // 245 + { 267, 188, -46, -95, 238 }, // 246 + { 268, 189, -46, -96, 240 }, // 247 + { 270, 191, -46, -97, 242 }, // 248 + { 271, 193, -47, -98, 244 }, // 249 + { 272, 194, -47, -99, 246 }, // 250 + { 273, 196, -48, -99, 248 }, // 251 + { 274, 197, -48, -100, 250 }, // 252 + { 275, 199, -48, -101, 252 }, // 253 + { 277, 201, -49, -102, 254 }, // 254 + { 278, 202, -49, -103, 256 } // 255 +}; + +#define YUYV_CONSTRAIN(v) ((v)<0)?0:(((v)>255)?255:(v)) + +void IRAM_ATTR yuv2rgb(uint8_t y, uint8_t u, uint8_t v, uint8_t *r, uint8_t *g, uint8_t *b) +{ + int16_t ri, gi, bi; + + ri = yuv_table[y].vY + yuv_table[v].vVr; + gi = yuv_table[y].vY + yuv_table[u].vUg + yuv_table[v].vVg; + bi = yuv_table[y].vY + yuv_table[u].vUb; + + *r = YUYV_CONSTRAIN(ri); + *g = YUYV_CONSTRAIN(gi); + *b = YUYV_CONSTRAIN(bi); +} diff --git a/esp32-cam-rtos-allframes/yuv.h b/esp32-cam-rtos-allframes/yuv.h new file mode 100644 index 0000000..c5a0577 --- /dev/null +++ b/esp32-cam-rtos-allframes/yuv.h @@ -0,0 +1,29 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef _CONVERSIONS_YUV_H_ +#define _CONVERSIONS_YUV_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +void yuv2rgb(uint8_t y, uint8_t u, uint8_t v, uint8_t *r, uint8_t *g, uint8_t *b); + +#ifdef __cplusplus +} +#endif + +#endif /* _CONVERSIONS_YUV_H_ */ diff --git a/esp32-cam-rtos/Kconfig b/esp32-cam-rtos/Kconfig new file mode 100644 index 0000000..78fc607 --- /dev/null +++ b/esp32-cam-rtos/Kconfig @@ -0,0 +1,65 @@ +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 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-rtos/LICENSE b/esp32-cam-rtos/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/esp32-cam-rtos/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-rtos/README.md b/esp32-cam-rtos/README.md new file mode 100644 index 0000000..d773b9f --- /dev/null +++ b/esp32-cam-rtos/README.md @@ -0,0 +1,16 @@ +# ESP32 MJPEG Multiclient Streaming Server + +This is a simple MJPEG streaming webserver implemented for AI-Thinker ESP32-CAM or ESP-EYE modules. + +This is tested to work with **VLC** and **Blynk** video widget. + + + +**This version uses FreeRTOS tasks to enable streaming to up to 10 connected clients** + + + +Inspired by and based on this Instructable: [$9 RTSP Video Streamer Using the ESP32-CAM Board](https://www.instructables.com/id/9-RTSP-Video-Streamer-Using-the-ESP32-CAM-Board/) + + + diff --git a/esp32-cam-rtos/camera.c b/esp32-cam-rtos/camera.c new file mode 100644 index 0000000..0fc85f6 --- /dev/null +++ b/esp32-cam-rtos/camera.c @@ -0,0 +1,1517 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include +#include +#include +#include "time.h" +#include "sys/time.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "soc/soc.h" +#include "soc/gpio_sig_map.h" +#include "soc/i2s_reg.h" +#include "soc/i2s_struct.h" +#include "soc/io_mux_reg.h" +#include "driver/gpio.h" +#include "driver/rtc_io.h" +#include "driver/periph_ctrl.h" +#include "esp_intr_alloc.h" +#include "esp_system.h" +#include "nvs_flash.h" +#include "nvs.h" +#include "sensor.h" +#include "sccb.h" +#include "esp_camera.h" +#include "camera_common.h" +#include "xclk.h" +#if CONFIG_OV2640_SUPPORT +#include "ov2640.h" +#endif +#if CONFIG_OV7725_SUPPORT +#include "ov7725.h" +#endif +#if CONFIG_OV3660_SUPPORT +#include "ov3660.h" +#endif +#if CONFIG_OV5640_SUPPORT +#include "ov5640.h" +#endif + +typedef enum { + CAMERA_NONE = 0, + CAMERA_UNKNOWN = 1, + CAMERA_OV7725 = 7725, + CAMERA_OV2640 = 2640, + CAMERA_OV3660 = 3660, + CAMERA_OV5640 = 5640, +} camera_model_t; + +#define REG_PID 0x0A +#define REG_VER 0x0B +#define REG_MIDH 0x1C +#define REG_MIDL 0x1D + +#define REG16_CHIDH 0x300A +#define REG16_CHIDL 0x300B + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define TAG "" +#else +#include "esp_log.h" +static const char* TAG = "camera"; +#endif +static const char* CAMERA_SENSOR_NVS_KEY = "sensor"; +static const char* CAMERA_PIXFORMAT_NVS_KEY = "pixformat"; + +typedef void (*dma_filter_t)(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst); + +typedef struct camera_fb_s { + uint8_t * buf; + size_t len; + size_t width; + size_t height; + pixformat_t format; + struct timeval timestamp; + size_t size; + uint8_t ref; + uint8_t bad; + struct camera_fb_s * next; +} camera_fb_int_t; + +typedef struct fb_s { + uint8_t * buf; + size_t len; + struct fb_s * next; +} fb_item_t; + +typedef struct { + camera_config_t config; + sensor_t sensor; + + camera_fb_int_t *fb; + size_t fb_size; + size_t data_size; + + size_t width; + size_t height; + size_t in_bytes_per_pixel; + size_t fb_bytes_per_pixel; + + size_t dma_received_count; + size_t dma_filtered_count; + size_t dma_per_line; + size_t dma_buf_width; + size_t dma_sample_count; + + lldesc_t *dma_desc; + dma_elem_t **dma_buf; + size_t dma_desc_count; + size_t dma_desc_cur; + + i2s_sampling_mode_t sampling_mode; + dma_filter_t dma_filter; + intr_handle_t i2s_intr_handle; + QueueHandle_t data_ready; + QueueHandle_t fb_in; + QueueHandle_t fb_out; + + SemaphoreHandle_t frame_ready; + TaskHandle_t dma_filter_task; +} camera_state_t; + +camera_state_t* s_state = NULL; + +static void i2s_init(); +static int i2s_run(); +static void IRAM_ATTR vsync_isr(void* arg); +static void IRAM_ATTR i2s_isr(void* arg); +static esp_err_t dma_desc_init(); +static void dma_desc_deinit(); +static void dma_filter_task(void *pvParameters); +static void dma_filter_grayscale(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst); +static void dma_filter_grayscale_highspeed(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst); +static void dma_filter_yuyv(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst); +static void dma_filter_yuyv_highspeed(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst); +static void dma_filter_jpeg(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst); +static void i2s_stop(bool* need_yield); + +static bool is_hs_mode() +{ + return s_state->config.xclk_freq_hz > 10000000; +} + +static size_t i2s_bytes_per_sample(i2s_sampling_mode_t mode) +{ + switch(mode) { + case SM_0A00_0B00: + return 4; + case SM_0A0B_0B0C: + return 4; + case SM_0A0B_0C0D: + return 2; + default: + assert(0 && "invalid sampling mode"); + return 0; + } +} + +static int IRAM_ATTR _gpio_get_level(gpio_num_t gpio_num) +{ + if (gpio_num < 32) { + return (GPIO.in >> gpio_num) & 0x1; + } else { + return (GPIO.in1.data >> (gpio_num - 32)) & 0x1; + } +} + +static void IRAM_ATTR vsync_intr_disable() +{ + gpio_set_intr_type(s_state->config.pin_vsync, GPIO_INTR_DISABLE); +} + +static void vsync_intr_enable() +{ + gpio_set_intr_type(s_state->config.pin_vsync, GPIO_INTR_NEGEDGE); +} + +static int skip_frame() +{ + if (s_state == NULL) { + return -1; + } + int64_t st_t = esp_timer_get_time(); + while (_gpio_get_level(s_state->config.pin_vsync) == 0) { + if((esp_timer_get_time() - st_t) > 1000000LL){ + goto timeout; + } + } + while (_gpio_get_level(s_state->config.pin_vsync) != 0) { + if((esp_timer_get_time() - st_t) > 1000000LL){ + goto timeout; + } + } + while (_gpio_get_level(s_state->config.pin_vsync) == 0) { + if((esp_timer_get_time() - st_t) > 1000000LL){ + goto timeout; + } + } + return 0; + +timeout: + ESP_LOGE(TAG, "Timeout waiting for VSYNC"); + return -1; +} + +static void camera_fb_deinit() +{ + camera_fb_int_t * _fb1 = s_state->fb, * _fb2 = NULL; + while(s_state->fb) { + _fb2 = s_state->fb; + s_state->fb = _fb2->next; + if(_fb2->next == _fb1) { + s_state->fb = NULL; + } + free(_fb2->buf); + free(_fb2); + } +} + +static esp_err_t camera_fb_init(size_t count) +{ + if(!count) { + return ESP_ERR_INVALID_ARG; + } + + camera_fb_deinit(); + + ESP_LOGI(TAG, "Allocating %u frame buffers (%d KB total)", count, (s_state->fb_size * count) / 1024); + + camera_fb_int_t * _fb = NULL, * _fb1 = NULL, * _fb2 = NULL; + for(size_t i = 0; i < count; i++) { + _fb2 = (camera_fb_int_t *)malloc(sizeof(camera_fb_int_t)); + if(!_fb2) { + goto fail; + } + memset(_fb2, 0, sizeof(camera_fb_int_t)); + _fb2->size = s_state->fb_size; +// _fb2->buf = (uint8_t*) calloc(_fb2->size, 1); +// if(!_fb2->buf) { + ESP_LOGI(TAG, "Allocating %d KB frame buffer in PSRAM", s_state->fb_size/1024); + _fb2->buf = (uint8_t*) heap_caps_calloc(_fb2->size, 1, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); +// } else { +// ESP_LOGI(TAG, "Allocating %d KB frame buffer in OnBoard RAM", s_state->fb_size/1024); +// } + if(!_fb2->buf) { + free(_fb2); + ESP_LOGE(TAG, "Allocating %d KB frame buffer Failed", s_state->fb_size/1024); + goto fail; + } + memset(_fb2->buf, 0, _fb2->size); + _fb2->next = _fb; + _fb = _fb2; + if(!i) { + _fb1 = _fb2; + } + } + if(_fb1) { + _fb1->next = _fb; + } + + s_state->fb = _fb;//load first buffer + + return ESP_OK; + +fail: + while(_fb) { + _fb2 = _fb; + _fb = _fb->next; + free(_fb2->buf); + free(_fb2); + } + return ESP_ERR_NO_MEM; +} + +static esp_err_t dma_desc_init() +{ + assert(s_state->width % 4 == 0); + size_t line_size = s_state->width * s_state->in_bytes_per_pixel * + i2s_bytes_per_sample(s_state->sampling_mode); + ESP_LOGD(TAG, "Line width (for DMA): %d bytes", line_size); + size_t dma_per_line = 1; + size_t buf_size = line_size; + while (buf_size >= 4096) { + buf_size /= 2; + dma_per_line *= 2; + } + size_t dma_desc_count = dma_per_line * 4; + s_state->dma_buf_width = line_size; + s_state->dma_per_line = dma_per_line; + s_state->dma_desc_count = dma_desc_count; + ESP_LOGD(TAG, "DMA buffer size: %d, DMA buffers per line: %d", buf_size, dma_per_line); + ESP_LOGD(TAG, "DMA buffer count: %d", dma_desc_count); + ESP_LOGD(TAG, "DMA buffer total: %d bytes", buf_size * dma_desc_count); + + s_state->dma_buf = (dma_elem_t**) malloc(sizeof(dma_elem_t*) * dma_desc_count); + if (s_state->dma_buf == NULL) { + return ESP_ERR_NO_MEM; + } + s_state->dma_desc = (lldesc_t*) malloc(sizeof(lldesc_t) * dma_desc_count); + if (s_state->dma_desc == NULL) { + return ESP_ERR_NO_MEM; + } + size_t dma_sample_count = 0; + for (int i = 0; i < dma_desc_count; ++i) { + ESP_LOGD(TAG, "Allocating DMA buffer #%d, size=%d", i, buf_size); + dma_elem_t* buf = (dma_elem_t*) malloc(buf_size); + if (buf == NULL) { + return ESP_ERR_NO_MEM; + } + s_state->dma_buf[i] = buf; + ESP_LOGV(TAG, "dma_buf[%d]=%p", i, buf); + + lldesc_t* pd = &s_state->dma_desc[i]; + pd->length = buf_size; + if (s_state->sampling_mode == SM_0A0B_0B0C && + (i + 1) % dma_per_line == 0) { + pd->length -= 4; + } + dma_sample_count += pd->length / 4; + pd->size = pd->length; + pd->owner = 1; + pd->sosf = 1; + pd->buf = (uint8_t*) buf; + pd->offset = 0; + pd->empty = 0; + pd->eof = 1; + pd->qe.stqe_next = &s_state->dma_desc[(i + 1) % dma_desc_count]; + } + s_state->dma_sample_count = dma_sample_count; + return ESP_OK; +} + +static void dma_desc_deinit() +{ + if (s_state->dma_buf) { + for (int i = 0; i < s_state->dma_desc_count; ++i) { + free(s_state->dma_buf[i]); + } + } + free(s_state->dma_buf); + free(s_state->dma_desc); +} + +static inline void IRAM_ATTR i2s_conf_reset() +{ + const uint32_t lc_conf_reset_flags = I2S_IN_RST_M | I2S_AHBM_RST_M + | I2S_AHBM_FIFO_RST_M; + I2S0.lc_conf.val |= lc_conf_reset_flags; + I2S0.lc_conf.val &= ~lc_conf_reset_flags; + + const uint32_t conf_reset_flags = I2S_RX_RESET_M | I2S_RX_FIFO_RESET_M + | I2S_TX_RESET_M | I2S_TX_FIFO_RESET_M; + I2S0.conf.val |= conf_reset_flags; + I2S0.conf.val &= ~conf_reset_flags; + while (I2S0.state.rx_fifo_reset_back) { + ; + } +} + +static void i2s_init() +{ + camera_config_t* config = &s_state->config; + + // Configure input GPIOs + gpio_num_t pins[] = { + config->pin_d7, + config->pin_d6, + config->pin_d5, + config->pin_d4, + config->pin_d3, + config->pin_d2, + config->pin_d1, + config->pin_d0, + config->pin_vsync, + config->pin_href, + config->pin_pclk + }; + gpio_config_t conf = { + .mode = GPIO_MODE_INPUT, + .pull_up_en = GPIO_PULLUP_ENABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE + }; + for (int i = 0; i < sizeof(pins) / sizeof(gpio_num_t); ++i) { + if (rtc_gpio_is_valid_gpio(pins[i])) { + rtc_gpio_deinit(pins[i]); + } + conf.pin_bit_mask = 1LL << pins[i]; + gpio_config(&conf); + } + + // Route input GPIOs to I2S peripheral using GPIO matrix + gpio_matrix_in(config->pin_d0, I2S0I_DATA_IN0_IDX, false); + gpio_matrix_in(config->pin_d1, I2S0I_DATA_IN1_IDX, false); + gpio_matrix_in(config->pin_d2, I2S0I_DATA_IN2_IDX, false); + gpio_matrix_in(config->pin_d3, I2S0I_DATA_IN3_IDX, false); + gpio_matrix_in(config->pin_d4, I2S0I_DATA_IN4_IDX, false); + gpio_matrix_in(config->pin_d5, I2S0I_DATA_IN5_IDX, false); + gpio_matrix_in(config->pin_d6, I2S0I_DATA_IN6_IDX, false); + gpio_matrix_in(config->pin_d7, I2S0I_DATA_IN7_IDX, false); + gpio_matrix_in(config->pin_vsync, I2S0I_V_SYNC_IDX, false); + gpio_matrix_in(0x38, I2S0I_H_SYNC_IDX, false); + gpio_matrix_in(config->pin_href, I2S0I_H_ENABLE_IDX, false); + gpio_matrix_in(config->pin_pclk, I2S0I_WS_IN_IDX, false); + + // Enable and configure I2S peripheral + periph_module_enable(PERIPH_I2S0_MODULE); + // Toggle some reset bits in LC_CONF register + // Toggle some reset bits in CONF register + i2s_conf_reset(); + // Enable slave mode (sampling clock is external) + I2S0.conf.rx_slave_mod = 1; + // Enable parallel mode + I2S0.conf2.lcd_en = 1; + // Use HSYNC/VSYNC/HREF to control sampling + I2S0.conf2.camera_en = 1; + // Configure clock divider + I2S0.clkm_conf.clkm_div_a = 1; + I2S0.clkm_conf.clkm_div_b = 0; + I2S0.clkm_conf.clkm_div_num = 2; + // FIFO will sink data to DMA + I2S0.fifo_conf.dscr_en = 1; + // FIFO configuration + I2S0.fifo_conf.rx_fifo_mod = s_state->sampling_mode; + I2S0.fifo_conf.rx_fifo_mod_force_en = 1; + I2S0.conf_chan.rx_chan_mod = 1; + // Clear flags which are used in I2S serial mode + I2S0.sample_rate_conf.rx_bits_mod = 0; + I2S0.conf.rx_right_first = 0; + I2S0.conf.rx_msb_right = 0; + I2S0.conf.rx_msb_shift = 0; + I2S0.conf.rx_mono = 0; + I2S0.conf.rx_short_sync = 0; + I2S0.timing.val = 0; + I2S0.timing.rx_dsync_sw = 1; + + // Allocate I2S interrupt, keep it disabled + ESP_ERROR_CHECK(esp_intr_alloc(ETS_I2S0_INTR_SOURCE, + ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM, + &i2s_isr, NULL, &s_state->i2s_intr_handle)); +} + +static void IRAM_ATTR i2s_start_bus() +{ + s_state->dma_desc_cur = 0; + s_state->dma_received_count = 0; + //s_state->dma_filtered_count = 0; + esp_intr_disable(s_state->i2s_intr_handle); + i2s_conf_reset(); + + I2S0.rx_eof_num = s_state->dma_sample_count; + I2S0.in_link.addr = (uint32_t) &s_state->dma_desc[0]; + I2S0.in_link.start = 1; + I2S0.int_clr.val = I2S0.int_raw.val; + I2S0.int_ena.val = 0; + I2S0.int_ena.in_done = 1; + + esp_intr_enable(s_state->i2s_intr_handle); + I2S0.conf.rx_start = 1; + if (s_state->config.pixel_format == PIXFORMAT_JPEG) { + vsync_intr_enable(); + } +} + +static int i2s_run() +{ + for (int i = 0; i < s_state->dma_desc_count; ++i) { + lldesc_t* d = &s_state->dma_desc[i]; + ESP_LOGV(TAG, "DMA desc %2d: %u %u %u %u %u %u %p %p", + i, d->length, d->size, d->offset, d->eof, d->sosf, d->owner, d->buf, d->qe.stqe_next); + memset(s_state->dma_buf[i], 0, d->length); + } + + // wait for frame + camera_fb_int_t * fb = s_state->fb; + while(s_state->config.fb_count > 1) { + while(s_state->fb->ref && s_state->fb->next != fb) { + s_state->fb = s_state->fb->next; + } + if(s_state->fb->ref == 0) { + break; + } + vTaskDelay(2); + } + + //todo: wait for vsync + ESP_LOGV(TAG, "Waiting for negative edge on VSYNC"); + + int64_t st_t = esp_timer_get_time(); + while (_gpio_get_level(s_state->config.pin_vsync) != 0) { + if((esp_timer_get_time() - st_t) > 1000000LL){ + ESP_LOGE(TAG, "Timeout waiting for VSYNC"); + return -1; + } + } + ESP_LOGV(TAG, "Got VSYNC"); + i2s_start_bus(); + return 0; +} + +static void IRAM_ATTR i2s_stop_bus() +{ + esp_intr_disable(s_state->i2s_intr_handle); + vsync_intr_disable(); + i2s_conf_reset(); + I2S0.conf.rx_start = 0; +} + +static void IRAM_ATTR i2s_stop(bool* need_yield) +{ + if(s_state->config.fb_count == 1 && !s_state->fb->bad) { + i2s_stop_bus(); + } else { + s_state->dma_received_count = 0; + } + + size_t val = SIZE_MAX; + BaseType_t higher_priority_task_woken; + BaseType_t ret = xQueueSendFromISR(s_state->data_ready, &val, &higher_priority_task_woken); + if(need_yield && !*need_yield) { + *need_yield = (ret == pdTRUE && higher_priority_task_woken == pdTRUE); + } +} + +static void IRAM_ATTR signal_dma_buf_received(bool* need_yield) +{ + size_t dma_desc_filled = s_state->dma_desc_cur; + s_state->dma_desc_cur = (dma_desc_filled + 1) % s_state->dma_desc_count; + s_state->dma_received_count++; + if(!s_state->fb->ref && s_state->fb->bad){ + *need_yield = false; + return; + } + BaseType_t higher_priority_task_woken; + BaseType_t ret = xQueueSendFromISR(s_state->data_ready, &dma_desc_filled, &higher_priority_task_woken); + if (ret != pdTRUE) { + if(!s_state->fb->ref) { + s_state->fb->bad = 1; + } + //ESP_EARLY_LOGW(TAG, "qsf:%d", s_state->dma_received_count); + //ets_printf("qsf:%d\n", s_state->dma_received_count); + //ets_printf("qovf\n"); + } + *need_yield = (ret == pdTRUE && higher_priority_task_woken == pdTRUE); +} + +static void IRAM_ATTR i2s_isr(void* arg) +{ + I2S0.int_clr.val = I2S0.int_raw.val; + bool need_yield = false; + signal_dma_buf_received(&need_yield); + if (s_state->config.pixel_format != PIXFORMAT_JPEG + && s_state->dma_received_count == s_state->height * s_state->dma_per_line) { + i2s_stop(&need_yield); + } + if (need_yield) { + portYIELD_FROM_ISR(); + } +} + +static void IRAM_ATTR vsync_isr(void* arg) +{ + GPIO.status1_w1tc.val = GPIO.status1.val; + GPIO.status_w1tc = GPIO.status; + bool need_yield = false; + //if vsync is low and we have received some data, frame is done + if (_gpio_get_level(s_state->config.pin_vsync) == 0) { + if(s_state->dma_received_count > 0) { + signal_dma_buf_received(&need_yield); + //ets_printf("end_vsync\n"); + if(s_state->dma_filtered_count > 1 || s_state->fb->bad || s_state->config.fb_count > 1) { + i2s_stop(&need_yield); + } + //ets_printf("vs\n"); + } + if(s_state->config.fb_count > 1 || s_state->dma_filtered_count < 2) { + I2S0.conf.rx_start = 0; + I2S0.in_link.start = 0; + I2S0.int_clr.val = I2S0.int_raw.val; + i2s_conf_reset(); + s_state->dma_desc_cur = (s_state->dma_desc_cur + 1) % s_state->dma_desc_count; + //I2S0.rx_eof_num = s_state->dma_sample_count; + I2S0.in_link.addr = (uint32_t) &s_state->dma_desc[s_state->dma_desc_cur]; + I2S0.in_link.start = 1; + I2S0.conf.rx_start = 1; + s_state->dma_received_count = 0; + } + } + if (need_yield) { + portYIELD_FROM_ISR(); + } +} + +static void IRAM_ATTR camera_fb_done() +{ + camera_fb_int_t * fb = NULL, * fb2 = NULL; + BaseType_t taskAwoken = 0; + + if(s_state->config.fb_count == 1) { + xSemaphoreGive(s_state->frame_ready); + return; + } + + fb = s_state->fb; + if(!fb->ref && fb->len) { + //add reference + fb->ref = 1; + + //check if the queue is full + if(xQueueIsQueueFullFromISR(s_state->fb_out) == pdTRUE) { + //pop frame buffer from the queue + if(xQueueReceiveFromISR(s_state->fb_out, &fb2, &taskAwoken) == pdTRUE) { + //free the popped buffer + fb2->ref = 0; + fb2->len = 0; + //push the new frame to the end of the queue + xQueueSendFromISR(s_state->fb_out, &fb, &taskAwoken); + } else { + //queue is full and we could not pop a frame from it + } + } else { + //push the new frame to the end of the queue + xQueueSendFromISR(s_state->fb_out, &fb, &taskAwoken); + } + } else { + //frame was referenced or empty + } + + //return buffers to be filled + while(xQueueReceiveFromISR(s_state->fb_in, &fb2, &taskAwoken) == pdTRUE) { + fb2->ref = 0; + fb2->len = 0; + } + + //advance frame buffer only if the current one has data + if(s_state->fb->len) { + s_state->fb = s_state->fb->next; + } + //try to find the next free frame buffer + while(s_state->fb->ref && s_state->fb->next != fb) { + s_state->fb = s_state->fb->next; + } + //is the found frame buffer free? + if(!s_state->fb->ref) { + //buffer found. make sure it's empty + s_state->fb->len = 0; + *((uint32_t *)s_state->fb->buf) = 0; + } else { + //stay at the previous buffer + s_state->fb = fb; + } +} + +static void IRAM_ATTR dma_finish_frame() +{ + size_t buf_len = s_state->width * s_state->fb_bytes_per_pixel / s_state->dma_per_line; + + if(!s_state->fb->ref) { + // is the frame bad? + if(s_state->fb->bad){ + s_state->fb->bad = 0; + s_state->fb->len = 0; + *((uint32_t *)s_state->fb->buf) = 0; + if(s_state->config.fb_count == 1) { + i2s_start_bus(); + } + //ets_printf("bad\n"); + } else { + s_state->fb->len = s_state->dma_filtered_count * buf_len; + if(s_state->fb->len) { + //find the end marker for JPEG. Data after that can be discarded + if(s_state->fb->format == PIXFORMAT_JPEG){ + uint8_t * dptr = &s_state->fb->buf[s_state->fb->len - 1]; + while(dptr > s_state->fb->buf){ + if(dptr[0] == 0xFF && dptr[1] == 0xD9 && dptr[2] == 0x00 && dptr[3] == 0x00){ + dptr += 2; + s_state->fb->len = dptr - s_state->fb->buf; + if((s_state->fb->len & 0x1FF) == 0){ + s_state->fb->len += 1; + } + if((s_state->fb->len % 100) == 0){ + s_state->fb->len += 1; + } + break; + } + dptr--; + } + } + //send out the frame + camera_fb_done(); + } else if(s_state->config.fb_count == 1){ + //frame was empty? + i2s_start_bus(); + } else { + //ets_printf("empty\n"); + } + } + } else if(s_state->fb->len) { + camera_fb_done(); + } + s_state->dma_filtered_count = 0; +} + +static void IRAM_ATTR dma_filter_buffer(size_t buf_idx) +{ + //no need to process the data if frame is in use or is bad + if(s_state->fb->ref || s_state->fb->bad) { + return; + } + + //check if there is enough space in the frame buffer for the new data + size_t buf_len = s_state->width * s_state->fb_bytes_per_pixel / s_state->dma_per_line; + size_t fb_pos = s_state->dma_filtered_count * buf_len; + if(fb_pos > s_state->fb_size - buf_len) { + //size_t processed = s_state->dma_received_count * buf_len; + //ets_printf("[%s:%u] ovf pos: %u, processed: %u\n", __FUNCTION__, __LINE__, fb_pos, processed); + return; + } + + //convert I2S DMA buffer to pixel data + (*s_state->dma_filter)(s_state->dma_buf[buf_idx], &s_state->dma_desc[buf_idx], s_state->fb->buf + fb_pos); + + //first frame buffer + if(!s_state->dma_filtered_count) { + //check for correct JPEG header + if(s_state->sensor.pixformat == PIXFORMAT_JPEG) { + uint32_t sig = *((uint32_t *)s_state->fb->buf) & 0xFFFFFF; + if(sig != 0xffd8ff) { + ets_printf("bh 0x%08x\n", sig); + s_state->fb->bad = 1; + return; + } + } + //set the frame properties + s_state->fb->width = resolution[s_state->sensor.status.framesize].width; + s_state->fb->height = resolution[s_state->sensor.status.framesize].height; + s_state->fb->format = s_state->sensor.pixformat; + + uint64_t us = (uint64_t)esp_timer_get_time(); + s_state->fb->timestamp.tv_sec = us / 1000000UL; + s_state->fb->timestamp.tv_usec = us % 1000000UL; + } + s_state->dma_filtered_count++; +} + +static void IRAM_ATTR dma_filter_task(void *pvParameters) +{ + s_state->dma_filtered_count = 0; + while (true) { + size_t buf_idx; + if(xQueueReceive(s_state->data_ready, &buf_idx, portMAX_DELAY) == pdTRUE) { + if (buf_idx == SIZE_MAX) { + //this is the end of the frame + dma_finish_frame(); + } else { + dma_filter_buffer(buf_idx); + } + } + } +} + +static void IRAM_ATTR dma_filter_jpeg(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst) +{ + size_t end = dma_desc->length / sizeof(dma_elem_t) / 4; + // manually unrolling 4 iterations of the loop here + for (size_t i = 0; i < end; ++i) { + dst[0] = src[0].sample1; + dst[1] = src[1].sample1; + dst[2] = src[2].sample1; + dst[3] = src[3].sample1; + src += 4; + dst += 4; + } +} + +static void IRAM_ATTR dma_filter_grayscale(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst) +{ + size_t end = dma_desc->length / sizeof(dma_elem_t) / 4; + for (size_t i = 0; i < end; ++i) { + // manually unrolling 4 iterations of the loop here + dst[0] = src[0].sample1; + dst[1] = src[1].sample1; + dst[2] = src[2].sample1; + dst[3] = src[3].sample1; + src += 4; + dst += 4; + } +} + +static void IRAM_ATTR dma_filter_grayscale_highspeed(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst) +{ + size_t end = dma_desc->length / sizeof(dma_elem_t) / 8; + for (size_t i = 0; i < end; ++i) { + // manually unrolling 4 iterations of the loop here + dst[0] = src[0].sample1; + dst[1] = src[2].sample1; + dst[2] = src[4].sample1; + dst[3] = src[6].sample1; + src += 8; + dst += 4; + } + // the final sample of a line in SM_0A0B_0B0C sampling mode needs special handling + if ((dma_desc->length & 0x7) != 0) { + dst[0] = src[0].sample1; + dst[1] = src[2].sample1; + } +} + +static void IRAM_ATTR dma_filter_yuyv(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst) +{ + size_t end = dma_desc->length / sizeof(dma_elem_t) / 4; + for (size_t i = 0; i < end; ++i) { + dst[0] = src[0].sample1;//y0 + dst[1] = src[0].sample2;//u + dst[2] = src[1].sample1;//y1 + dst[3] = src[1].sample2;//v + + dst[4] = src[2].sample1;//y0 + dst[5] = src[2].sample2;//u + dst[6] = src[3].sample1;//y1 + dst[7] = src[3].sample2;//v + src += 4; + dst += 8; + } +} + +static void IRAM_ATTR dma_filter_yuyv_highspeed(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst) +{ + size_t end = dma_desc->length / sizeof(dma_elem_t) / 8; + for (size_t i = 0; i < end; ++i) { + dst[0] = src[0].sample1;//y0 + dst[1] = src[1].sample1;//u + dst[2] = src[2].sample1;//y1 + dst[3] = src[3].sample1;//v + + dst[4] = src[4].sample1;//y0 + dst[5] = src[5].sample1;//u + dst[6] = src[6].sample1;//y1 + dst[7] = src[7].sample1;//v + src += 8; + dst += 8; + } + if ((dma_desc->length & 0x7) != 0) { + dst[0] = src[0].sample1;//y0 + dst[1] = src[1].sample1;//u + dst[2] = src[2].sample1;//y1 + dst[3] = src[2].sample2;//v + } +} + +static void IRAM_ATTR dma_filter_rgb888(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst) +{ + size_t end = dma_desc->length / sizeof(dma_elem_t) / 4; + uint8_t lb, hb; + for (size_t i = 0; i < end; ++i) { + hb = src[0].sample1; + lb = src[0].sample2; + dst[0] = (lb & 0x1F) << 3; + dst[1] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3; + dst[2] = hb & 0xF8; + + hb = src[1].sample1; + lb = src[1].sample2; + dst[3] = (lb & 0x1F) << 3; + dst[4] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3; + dst[5] = hb & 0xF8; + + hb = src[2].sample1; + lb = src[2].sample2; + dst[6] = (lb & 0x1F) << 3; + dst[7] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3; + dst[8] = hb & 0xF8; + + hb = src[3].sample1; + lb = src[3].sample2; + dst[9] = (lb & 0x1F) << 3; + dst[10] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3; + dst[11] = hb & 0xF8; + src += 4; + dst += 12; + } +} + +static void IRAM_ATTR dma_filter_rgb888_highspeed(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst) +{ + size_t end = dma_desc->length / sizeof(dma_elem_t) / 8; + uint8_t lb, hb; + for (size_t i = 0; i < end; ++i) { + hb = src[0].sample1; + lb = src[1].sample1; + dst[0] = (lb & 0x1F) << 3; + dst[1] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3; + dst[2] = hb & 0xF8; + + hb = src[2].sample1; + lb = src[3].sample1; + dst[3] = (lb & 0x1F) << 3; + dst[4] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3; + dst[5] = hb & 0xF8; + + hb = src[4].sample1; + lb = src[5].sample1; + dst[6] = (lb & 0x1F) << 3; + dst[7] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3; + dst[8] = hb & 0xF8; + + hb = src[6].sample1; + lb = src[7].sample1; + dst[9] = (lb & 0x1F) << 3; + dst[10] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3; + dst[11] = hb & 0xF8; + + src += 8; + dst += 12; + } + if ((dma_desc->length & 0x7) != 0) { + hb = src[0].sample1; + lb = src[1].sample1; + dst[0] = (lb & 0x1F) << 3; + dst[1] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3; + dst[2] = hb & 0xF8; + + hb = src[2].sample1; + lb = src[2].sample2; + dst[3] = (lb & 0x1F) << 3; + dst[4] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3; + dst[5] = hb & 0xF8; + } +} + +/* + * Public Methods + * */ + +esp_err_t camera_probe(const camera_config_t* config, camera_model_t* out_camera_model) +{ + if (s_state != NULL) { + return ESP_ERR_INVALID_STATE; + } + + s_state = (camera_state_t*) calloc(sizeof(*s_state), 1); + if (!s_state) { + return ESP_ERR_NO_MEM; + } + + ESP_LOGD(TAG, "Enabling XCLK output"); + camera_enable_out_clock(config); + + ESP_LOGD(TAG, "Initializing SSCB"); + SCCB_Init(config->pin_sscb_sda, config->pin_sscb_scl); + + if(config->pin_pwdn >= 0) { + ESP_LOGD(TAG, "Resetting camera by power down line"); + gpio_config_t conf = { 0 }; + conf.pin_bit_mask = 1LL << config->pin_pwdn; + conf.mode = GPIO_MODE_OUTPUT; + gpio_config(&conf); + + // carefull, logic is inverted compared to reset pin + gpio_set_level(config->pin_pwdn, 1); + vTaskDelay(10 / portTICK_PERIOD_MS); + gpio_set_level(config->pin_pwdn, 0); + vTaskDelay(10 / portTICK_PERIOD_MS); + } + + if(config->pin_reset >= 0) { + ESP_LOGD(TAG, "Resetting camera"); + gpio_config_t conf = { 0 }; + conf.pin_bit_mask = 1LL << config->pin_reset; + conf.mode = GPIO_MODE_OUTPUT; + gpio_config(&conf); + + gpio_set_level(config->pin_reset, 0); + vTaskDelay(10 / portTICK_PERIOD_MS); + gpio_set_level(config->pin_reset, 1); + vTaskDelay(10 / portTICK_PERIOD_MS); + } + + ESP_LOGD(TAG, "Searching for camera address"); + vTaskDelay(10 / portTICK_PERIOD_MS); + uint8_t slv_addr = SCCB_Probe(); + if (slv_addr == 0) { + *out_camera_model = CAMERA_NONE; + camera_disable_out_clock(); + return ESP_ERR_CAMERA_NOT_DETECTED; + } + + //slv_addr = 0x30; + ESP_LOGD(TAG, "Detected camera at address=0x%02x", slv_addr); + sensor_id_t* id = &s_state->sensor.id; + +#if CONFIG_OV2640_SUPPORT + if (slv_addr == 0x30) { + ESP_LOGD(TAG, "Resetting OV2640"); + //camera might be OV2640. try to reset it + SCCB_Write(0x30, 0xFF, 0x01);//bank sensor + SCCB_Write(0x30, 0x12, 0x80);//reset + vTaskDelay(10 / portTICK_PERIOD_MS); + slv_addr = SCCB_Probe(); + } +#endif + + s_state->sensor.slv_addr = slv_addr; + s_state->sensor.xclk_freq_hz = config->xclk_freq_hz; + +#if (CONFIG_OV3660_SUPPORT || CONFIG_OV5640_SUPPORT) + if(s_state->sensor.slv_addr == 0x3c){ + id->PID = SCCB_Read16(s_state->sensor.slv_addr, REG16_CHIDH); + id->VER = SCCB_Read16(s_state->sensor.slv_addr, REG16_CHIDL); + vTaskDelay(10 / portTICK_PERIOD_MS); + ESP_LOGD(TAG, "Camera PID=0x%02x VER=0x%02x", id->PID, id->VER); + } else { +#endif + id->PID = SCCB_Read(s_state->sensor.slv_addr, REG_PID); + id->VER = SCCB_Read(s_state->sensor.slv_addr, REG_VER); + id->MIDL = SCCB_Read(s_state->sensor.slv_addr, REG_MIDL); + id->MIDH = SCCB_Read(s_state->sensor.slv_addr, REG_MIDH); + vTaskDelay(10 / portTICK_PERIOD_MS); + ESP_LOGD(TAG, "Camera PID=0x%02x VER=0x%02x MIDL=0x%02x MIDH=0x%02x", + id->PID, id->VER, id->MIDH, id->MIDL); + +#if (CONFIG_OV3660_SUPPORT || CONFIG_OV5640_SUPPORT) + } +#endif + + + switch (id->PID) { +#if CONFIG_OV2640_SUPPORT + case OV2640_PID: + *out_camera_model = CAMERA_OV2640; + ov2640_init(&s_state->sensor); + break; +#endif +#if CONFIG_OV7725_SUPPORT + case OV7725_PID: + *out_camera_model = CAMERA_OV7725; + ov7725_init(&s_state->sensor); + break; +#endif +#if CONFIG_OV3660_SUPPORT + case OV3660_PID: + *out_camera_model = CAMERA_OV3660; + ov3660_init(&s_state->sensor); + break; +#endif +#if CONFIG_OV5640_SUPPORT + case OV5640_PID: + *out_camera_model = CAMERA_OV5640; + ov5640_init(&s_state->sensor); + break; +#endif + default: + id->PID = 0; + *out_camera_model = CAMERA_UNKNOWN; + camera_disable_out_clock(); + ESP_LOGE(TAG, "Detected camera not supported."); + return ESP_ERR_CAMERA_NOT_SUPPORTED; + } + + ESP_LOGD(TAG, "Doing SW reset of sensor"); + s_state->sensor.reset(&s_state->sensor); + + return ESP_OK; +} + +esp_err_t camera_init(const camera_config_t* config) +{ + if (!s_state) { + return ESP_ERR_INVALID_STATE; + } + if (s_state->sensor.id.PID == 0) { + return ESP_ERR_CAMERA_NOT_SUPPORTED; + } + memcpy(&s_state->config, config, sizeof(*config)); + esp_err_t err = ESP_OK; + framesize_t frame_size = (framesize_t) config->frame_size; + pixformat_t pix_format = (pixformat_t) config->pixel_format; + + switch (s_state->sensor.id.PID) { +#if CONFIG_OV2640_SUPPORT + case OV2640_PID: + if (frame_size > FRAMESIZE_UXGA) { + frame_size = FRAMESIZE_UXGA; + } + break; +#endif +#if CONFIG_OV7725_SUPPORT + case OV7725_PID: + if (frame_size > FRAMESIZE_VGA) { + frame_size = FRAMESIZE_VGA; + } + break; +#endif +#if CONFIG_OV3660_SUPPORT + case OV3660_PID: + if (frame_size > FRAMESIZE_QXGA) { + frame_size = FRAMESIZE_QXGA; + } + break; +#endif +#if CONFIG_OV5640_SUPPORT + case OV5640_PID: + if (frame_size > FRAMESIZE_QSXGA) { + frame_size = FRAMESIZE_QSXGA; + } + break; +#endif + default: + return ESP_ERR_CAMERA_NOT_SUPPORTED; + } + + s_state->width = resolution[frame_size].width; + s_state->height = resolution[frame_size].height; + + if (pix_format == PIXFORMAT_GRAYSCALE) { + s_state->fb_size = s_state->width * s_state->height; + if (s_state->sensor.id.PID == OV3660_PID || s_state->sensor.id.PID == OV5640_PID) { + if (is_hs_mode()) { + s_state->sampling_mode = SM_0A00_0B00; + s_state->dma_filter = &dma_filter_yuyv_highspeed; + } else { + s_state->sampling_mode = SM_0A0B_0C0D; + s_state->dma_filter = &dma_filter_yuyv; + } + s_state->in_bytes_per_pixel = 1; // camera sends Y8 + } else { + if (is_hs_mode() && s_state->sensor.id.PID != OV7725_PID) { + s_state->sampling_mode = SM_0A00_0B00; + s_state->dma_filter = &dma_filter_grayscale_highspeed; + } else { + s_state->sampling_mode = SM_0A0B_0C0D; + s_state->dma_filter = &dma_filter_grayscale; + } + s_state->in_bytes_per_pixel = 2; // camera sends YU/YV + } + s_state->fb_bytes_per_pixel = 1; // frame buffer stores Y8 + } else if (pix_format == PIXFORMAT_YUV422 || pix_format == PIXFORMAT_RGB565) { + s_state->fb_size = s_state->width * s_state->height * 2; + if (is_hs_mode() && s_state->sensor.id.PID != OV7725_PID) { + s_state->sampling_mode = SM_0A00_0B00; + s_state->dma_filter = &dma_filter_yuyv_highspeed; + } else { + s_state->sampling_mode = SM_0A0B_0C0D; + s_state->dma_filter = &dma_filter_yuyv; + } + s_state->in_bytes_per_pixel = 2; // camera sends YU/YV + s_state->fb_bytes_per_pixel = 2; // frame buffer stores YU/YV/RGB565 + } else if (pix_format == PIXFORMAT_RGB888) { + s_state->fb_size = s_state->width * s_state->height * 3; + if (is_hs_mode()) { + s_state->sampling_mode = SM_0A00_0B00; + s_state->dma_filter = &dma_filter_rgb888_highspeed; + } else { + s_state->sampling_mode = SM_0A0B_0C0D; + s_state->dma_filter = &dma_filter_rgb888; + } + s_state->in_bytes_per_pixel = 2; // camera sends RGB565 + s_state->fb_bytes_per_pixel = 3; // frame buffer stores RGB888 + } else if (pix_format == PIXFORMAT_JPEG) { + if (s_state->sensor.id.PID != OV2640_PID && s_state->sensor.id.PID != OV3660_PID && s_state->sensor.id.PID != OV5640_PID) { + ESP_LOGE(TAG, "JPEG format is only supported for ov2640, ov3660 and ov5640"); + err = ESP_ERR_NOT_SUPPORTED; + goto fail; + } + int qp = config->jpeg_quality; + int compression_ratio_bound = 1; + if (qp > 10) { + compression_ratio_bound = 16; + } else if (qp > 5) { + compression_ratio_bound = 10; + } else { + compression_ratio_bound = 4; + } + (*s_state->sensor.set_quality)(&s_state->sensor, qp); + s_state->in_bytes_per_pixel = 2; + s_state->fb_bytes_per_pixel = 2; + s_state->fb_size = (s_state->width * s_state->height * s_state->fb_bytes_per_pixel) / compression_ratio_bound; + s_state->dma_filter = &dma_filter_jpeg; + s_state->sampling_mode = SM_0A00_0B00; + } else { + ESP_LOGE(TAG, "Requested format is not supported"); + err = ESP_ERR_NOT_SUPPORTED; + goto fail; + } + + ESP_LOGD(TAG, "in_bpp: %d, fb_bpp: %d, fb_size: %d, mode: %d, width: %d height: %d", + s_state->in_bytes_per_pixel, s_state->fb_bytes_per_pixel, + s_state->fb_size, s_state->sampling_mode, + s_state->width, s_state->height); + + i2s_init(); + + err = dma_desc_init(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize I2S and DMA"); + goto fail; + } + + //s_state->fb_size = 75 * 1024; + err = camera_fb_init(s_state->config.fb_count); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to allocate frame buffer"); + goto fail; + } + + s_state->data_ready = xQueueCreate(16, sizeof(size_t)); + if (s_state->data_ready == NULL) { + ESP_LOGE(TAG, "Failed to dma queue"); + err = ESP_ERR_NO_MEM; + goto fail; + } + + if(s_state->config.fb_count == 1) { + s_state->frame_ready = xSemaphoreCreateBinary(); + if (s_state->frame_ready == NULL) { + ESP_LOGE(TAG, "Failed to create semaphore"); + err = ESP_ERR_NO_MEM; + goto fail; + } + } else { + s_state->fb_in = xQueueCreate(s_state->config.fb_count, sizeof(camera_fb_t *)); + s_state->fb_out = xQueueCreate(1, sizeof(camera_fb_t *)); + if (s_state->fb_in == NULL || s_state->fb_out == NULL) { + ESP_LOGE(TAG, "Failed to fb queues"); + err = ESP_ERR_NO_MEM; + goto fail; + } + } + + //ToDo: core affinity? +#if CONFIG_CAMERA_CORE0 + if (!xTaskCreatePinnedToCore(&dma_filter_task, "dma_filter", 4096, NULL, 10, &s_state->dma_filter_task, 0)) +#elif CONFIG_CAMERA_CORE1 + if (!xTaskCreatePinnedToCore(&dma_filter_task, "dma_filter", 4096, NULL, 10, &s_state->dma_filter_task, 1)) +#else + if (!xTaskCreate(&dma_filter_task, "dma_filter", 4096, NULL, 10, &s_state->dma_filter_task)) +#endif + { + ESP_LOGE(TAG, "Failed to create DMA filter task"); + err = ESP_ERR_NO_MEM; + goto fail; + } + + vsync_intr_disable(); + err = gpio_install_isr_service(ESP_INTR_FLAG_LEVEL1 | ESP_INTR_FLAG_IRAM); + if (err != ESP_OK) { + 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; + 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 { + 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(); + free(s_state); + s_state = NULL; + camera_disable_out_clock(); + periph_module_disable(PERIPH_I2S0_MODULE); + return ESP_OK; +} + +#define FB_GET_TIMEOUT (4000 / portTICK_PERIOD_MS) + +camera_fb_t* esp_camera_fb_get() +{ + if (s_state == NULL) { + return NULL; + } + if(!I2S0.conf.rx_start) { + if(s_state->config.fb_count > 1) { + ESP_LOGD(TAG, "i2s_run"); + } + if (i2s_run() != 0) { + return NULL; + } + } + bool need_yield = false; + if (s_state->config.fb_count == 1) { + if (xSemaphoreTake(s_state->frame_ready, FB_GET_TIMEOUT) != pdTRUE){ + i2s_stop(&need_yield); + ESP_LOGE(TAG, "Failed to get the frame on time!"); + return NULL; + } + return (camera_fb_t*)s_state->fb; + } + camera_fb_int_t * fb = NULL; + if(s_state->fb_out) { + if (xQueueReceive(s_state->fb_out, &fb, FB_GET_TIMEOUT) != pdTRUE) { + i2s_stop(&need_yield); + ESP_LOGE(TAG, "Failed to get the frame on time!"); + return NULL; + } + } + return (camera_fb_t*)fb; +} + +void esp_camera_fb_return(camera_fb_t * fb) +{ + if(fb == NULL || s_state == NULL || s_state->config.fb_count == 1 || s_state->fb_in == NULL) { + return; + } + xQueueSend(s_state->fb_in, &fb, portMAX_DELAY); +} + +sensor_t * esp_camera_sensor_get() +{ + if (s_state == NULL) { + return NULL; + } + return &s_state->sensor; +} + +esp_err_t esp_camera_save_to_nvs(const char *key) +{ +#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 new file mode 100644 index 0000000..8a2db5c --- /dev/null +++ b/esp32-cam-rtos/camera_common.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include +#include "esp_err.h" +#include "esp_intr_alloc.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/task.h" +#include "esp_camera.h" +#include "sensor.h" + +#include "esp_system.h" +#if ESP_IDF_VERSION_MAJOR >= 4 // IDF 4+ +#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 +#include "esp32/rom/lldesc.h" +#else +#error Target CONFIG_IDF_TARGET is not supported +#endif +#else // ESP32 Before IDF 4.0 +#include "rom/lldesc.h" +#endif + +typedef union { + struct { + uint8_t sample2; + uint8_t unused2; + uint8_t sample1; + uint8_t unused1; + }; + uint32_t val; +} dma_elem_t; + +typedef enum { + /* camera sends byte sequence: s1, s2, s3, s4, ... + * fifo receives: 00 s1 00 s2, 00 s2 00 s3, 00 s3 00 s4, ... + */ + SM_0A0B_0B0C = 0, + /* camera sends byte sequence: s1, s2, s3, s4, ... + * fifo receives: 00 s1 00 s2, 00 s3 00 s4, ... + */ + SM_0A0B_0C0D = 1, + /* camera sends byte sequence: s1, s2, s3, s4, ... + * fifo receives: 00 s1 00 00, 00 s2 00 00, 00 s3 00 00, ... + */ + SM_0A00_0B00 = 3, +} i2s_sampling_mode_t; + diff --git a/esp32-cam-rtos/camera_pins.h b/esp32-cam-rtos/camera_pins.h new file mode 100644 index 0000000..7855722 --- /dev/null +++ b/esp32-cam-rtos/camera_pins.h @@ -0,0 +1,99 @@ + +#if defined(CAMERA_MODEL_WROVER_KIT) +#define PWDN_GPIO_NUM -1 +#define RESET_GPIO_NUM -1 +#define XCLK_GPIO_NUM 21 +#define SIOD_GPIO_NUM 26 +#define SIOC_GPIO_NUM 27 + +#define Y9_GPIO_NUM 35 +#define Y8_GPIO_NUM 34 +#define Y7_GPIO_NUM 39 +#define Y6_GPIO_NUM 36 +#define Y5_GPIO_NUM 19 +#define Y4_GPIO_NUM 18 +#define Y3_GPIO_NUM 5 +#define Y2_GPIO_NUM 4 +#define VSYNC_GPIO_NUM 25 +#define HREF_GPIO_NUM 23 +#define PCLK_GPIO_NUM 22 + +#elif defined(CAMERA_MODEL_ESP_EYE) +#define PWDN_GPIO_NUM -1 +#define RESET_GPIO_NUM -1 +#define XCLK_GPIO_NUM 4 +#define SIOD_GPIO_NUM 18 +#define SIOC_GPIO_NUM 23 + +#define Y9_GPIO_NUM 36 +#define Y8_GPIO_NUM 37 +#define Y7_GPIO_NUM 38 +#define Y6_GPIO_NUM 39 +#define Y5_GPIO_NUM 35 +#define Y4_GPIO_NUM 14 +#define Y3_GPIO_NUM 13 +#define Y2_GPIO_NUM 34 +#define VSYNC_GPIO_NUM 5 +#define HREF_GPIO_NUM 27 +#define PCLK_GPIO_NUM 25 + +#elif defined(CAMERA_MODEL_M5STACK_PSRAM) +#define PWDN_GPIO_NUM -1 +#define RESET_GPIO_NUM 15 +#define XCLK_GPIO_NUM 27 +#define SIOD_GPIO_NUM 25 +#define SIOC_GPIO_NUM 23 + +#define Y9_GPIO_NUM 19 +#define Y8_GPIO_NUM 36 +#define Y7_GPIO_NUM 18 +#define Y6_GPIO_NUM 39 +#define Y5_GPIO_NUM 5 +#define Y4_GPIO_NUM 34 +#define Y3_GPIO_NUM 35 +#define Y2_GPIO_NUM 32 +#define VSYNC_GPIO_NUM 22 +#define HREF_GPIO_NUM 26 +#define PCLK_GPIO_NUM 21 + +#elif defined(CAMERA_MODEL_M5STACK_WIDE) +#define PWDN_GPIO_NUM -1 +#define RESET_GPIO_NUM 15 +#define XCLK_GPIO_NUM 27 +#define SIOD_GPIO_NUM 22 +#define SIOC_GPIO_NUM 23 + +#define Y9_GPIO_NUM 19 +#define Y8_GPIO_NUM 36 +#define Y7_GPIO_NUM 18 +#define Y6_GPIO_NUM 39 +#define Y5_GPIO_NUM 5 +#define Y4_GPIO_NUM 34 +#define Y3_GPIO_NUM 35 +#define Y2_GPIO_NUM 32 +#define VSYNC_GPIO_NUM 25 +#define HREF_GPIO_NUM 26 +#define PCLK_GPIO_NUM 21 + +#elif defined(CAMERA_MODEL_AI_THINKER) +#define PWDN_GPIO_NUM 32 +#define RESET_GPIO_NUM -1 +#define XCLK_GPIO_NUM 0 +#define SIOD_GPIO_NUM 26 +#define SIOC_GPIO_NUM 27 + +#define Y9_GPIO_NUM 35 +#define Y8_GPIO_NUM 34 +#define Y7_GPIO_NUM 39 +#define Y6_GPIO_NUM 36 +#define Y5_GPIO_NUM 21 +#define Y4_GPIO_NUM 19 +#define Y3_GPIO_NUM 18 +#define Y2_GPIO_NUM 5 +#define VSYNC_GPIO_NUM 25 +#define HREF_GPIO_NUM 23 +#define PCLK_GPIO_NUM 22 + +#else +#error "Camera model not selected" +#endif diff --git a/esp32-cam-rtos/esp32-cam-rtos.ino b/esp32-cam-rtos/esp32-cam-rtos.ino new file mode 100644 index 0000000..f7c97c6 --- /dev/null +++ b/esp32-cam-rtos/esp32-cam-rtos.ino @@ -0,0 +1,519 @@ +/* + + This is a simple MJPEG streaming webserver implemented for AI-Thinker ESP32-CAM + and ESP-EYE modules. + This is tested to work with VLC and Blynk video widget and can support up to 10 + simultaneously connected streaming clients. + Simultaneous streaming is implemented with dedicated FreeRTOS tasks. + + Inspired by and based on this Instructable: $9 RTSP Video Streamer Using the ESP32-CAM Board + (https://www.instructables.com/id/9-RTSP-Video-Streamer-Using-the-ESP32-CAM-Board/) + + Board: AI-Thinker ESP32-CAM or ESP-EYE + Compile as: + ESP32 Dev Module + CPU Freq: 240 + Flash Freq: 80 + Flash mode: QIO + Flash Size: 4Mb + Partrition: Minimal SPIFFS + PSRAM: Enabled +*/ + +// ESP32 has two cores: APPlication core and PROcess core (the one that runs ESP32 SDK stack) +#define APP_CPU 1 +#define PRO_CPU 0 + +#include "esp_camera.h" +#include "ov2640.h" +#include +#include +#include + +#include +#include +#include +#include + +// Select camera model +//#define CAMERA_MODEL_WROVER_KIT +#define CAMERA_MODEL_ESP_EYE +//#define CAMERA_MODEL_M5STACK_PSRAM +//#define CAMERA_MODEL_M5STACK_WIDE +//#define CAMERA_MODEL_AI_THINKER + +#define MAX_CLIENTS 10 + +#include "camera_pins.h" + +/* + Next one is an include with wifi credentials. + This is what you need to do: + + 1. Create a file called "home_wifi_multi.h" in the same folder OR under a separate subfolder of the "libraries" folder of Arduino IDE. (You are creating a "fake" library really - I called it "MySettings"). + 2. Place the following text in the file: + #define SSID1 "replace with your wifi ssid" + #define PWD1 "replace your wifi password" + 3. Save. + + Should work then +*/ +#include "home_wifi_multi.h" + +//OV2640 cam; + +WebServer server(80); + +// ===== rtos task handles ========================= +// Streaming is implemented with 3 tasks: +TaskHandle_t tMjpeg; // handles client connections to the webserver +TaskHandle_t tCam; // handles getting picture frames from the camera and storing them locally + +uint8_t noActiveClients; // number of active clients + +// frameSync semaphore is used to prevent streaming buffer as it is replaced with the next frame +SemaphoreHandle_t frameSync = NULL; + +// We will try to achieve 24 FPS frame rate +const int FPS = 24; + +// We will handle web client requests every 100 ms (10 Hz) +const int WSINTERVAL = 100; + + +// ======== Server Connection Handler Task ========================== +void mjpegCB(void* pvParameters) { + TickType_t xLastWakeTime; + const TickType_t xFrequency = pdMS_TO_TICKS(WSINTERVAL); + + // Creating frame synchronization semaphore and initializing it + frameSync = xSemaphoreCreateBinary(); + xSemaphoreGive( frameSync ); + + //=== setup section ================== + + // Creating RTOS task for grabbing frames from the camera + xTaskCreatePinnedToCore( + camCB, // callback + "cam", // name + 4 * 1024, // stacj size + NULL, // parameters + 2, // priority + &tCam, // RTOS task handle + PRO_CPU); // core + + // Registering webserver handling routines + server.on("/mjpeg/1", HTTP_GET, handleJPGSstream); + server.on("/jpg", HTTP_GET, handleJPG); + server.onNotFound(handleNotFound); + + // Starting webserver + server.begin(); + + noActiveClients = 0; + + Serial.printf("\nmjpegCB: free heap (start) : %d\n", ESP.getFreeHeap()); + //=== loop() section =================== + xLastWakeTime = xTaskGetTickCount(); + for (;;) { + server.handleClient(); + + // After every server client handling request, we let other tasks run and then pause + taskYIELD(); + vTaskDelayUntil(&xLastWakeTime, xFrequency); + } +} + + +// Current frame information +volatile uint32_t frameNumber; +volatile size_t camSize; // size of the current frame, byte +volatile char* camBuf; // pointer to the current frame + + +// ==== RTOS task to grab frames from the camera ========================= +void camCB(void* pvParameters) { + + TickType_t xLastWakeTime; + + // A running interval associated with currently desired frame rate + const TickType_t xFrequency = pdMS_TO_TICKS(1000 / FPS); + + // Pointers to the 2 frames, their respective sizes and index of the current frame + char* fbs[2] = { NULL, NULL }; + size_t fSize[2] = { 0, 0 }; + int ifb = 0; + frameNumber = 0; + + //=== loop() section =================== + xLastWakeTime = xTaskGetTickCount(); + + for (;;) { + + // Grab a frame from the camera and query its size + camera_fb_t* fb = NULL; + + fb = esp_camera_fb_get(); + size_t s = fb->len; + + // If frame size is more that we have previously allocated - request 125% of the current frame space + if (s > fSize[ifb]) { + fSize[ifb] = s + s; + fbs[ifb] = allocateMemory(fbs[ifb], fSize[ifb]); + } + + // Copy current frame into local buffer + char* b = (char *)fb->buf; + memcpy(fbs[ifb], b, s); + esp_camera_fb_return(fb); + + // Let other tasks run and wait until the end of the current frame rate interval (if any time left) + taskYIELD(); + vTaskDelayUntil(&xLastWakeTime, xFrequency); + + // Only switch frames around if no frame is currently being streamed to a client + // Wait on a semaphore until client operation completes + // xSemaphoreTake( frameSync, portMAX_DELAY ); + + // Do not allow frame copying while switching the current frame + xSemaphoreTake( frameSync, xFrequency ); + camBuf = fbs[ifb]; + camSize = s; + ifb++; + ifb &= 1; // this should produce 1, 0, 1, 0, 1 ... sequence + frameNumber++; + // Let anyone waiting for a frame know that the frame is ready + xSemaphoreGive( frameSync ); + + // Immediately let other (streaming) tasks run + taskYIELD(); + + // If streaming task has suspended itself (no active clients to stream to) + // there is no need to grab frames from the camera. We can save some juice + // by suspedning the tasks + if ( noActiveClients == 0 ) { + Serial.printf("mjpegCB: free heap : %d\n", ESP.getFreeHeap()); + Serial.printf("mjpegCB: min free heap) : %d\n", ESP.getMinFreeHeap()); + Serial.printf("mjpegCB: max alloc free heap : %d\n", ESP.getMaxAllocHeap()); + Serial.printf("mjpegCB: tCam stack wtrmark : %d\n", uxTaskGetStackHighWaterMark(tCam)); + Serial.flush(); + vTaskSuspend(NULL); // passing NULL means "suspend yourself" + } + } +} + + +// ==== Memory allocator that takes advantage of PSRAM if present ======================= +char* allocateMemory(char* aPtr, size_t aSize) { + + // Since current buffer is too smal, free it + if (aPtr != NULL) free(aPtr); + + char* ptr = NULL; + ptr = (char*) ps_malloc(aSize); + + // If the memory pointer is NULL, we were not able to allocate any memory, and that is a terminal condition. + if (ptr == NULL) { + Serial.println("Out of memory!"); + delay(5000); + ESP.restart(); + } + return ptr; +} + + +// ==== STREAMING ====================================================== +const char HEADER[] = "HTTP/1.1 200 OK\r\n" \ + "Access-Control-Allow-Origin: *\r\n" \ + "Content-Type: multipart/x-mixed-replace; boundary=123456789000000000000987654321\r\n"; +const char BOUNDARY[] = "\r\n--123456789000000000000987654321\r\n"; +const char CTNTTYPE[] = "Content-Type: image/jpeg\r\nContent-Length: "; +const int hdrLen = strlen(HEADER); +const int bdrLen = strlen(BOUNDARY); +const int cntLen = strlen(CTNTTYPE); + + +struct streamInfo { + uint32_t frame; + WiFiClient client; + TaskHandle_t task; + char* buffer; + size_t len; +}; + +// ==== Handle connection request from clients =============================== +void handleJPGSstream(void) +{ + if ( noActiveClients >= MAX_CLIENTS ) return; + Serial.printf("handleJPGSstream start: free heap : %d\n", ESP.getFreeHeap()); + + streamInfo* info = new streamInfo; + + info->frame = frameNumber - 1; + info->client = server.client(); + info->buffer = NULL; + info->len = 0; + + // Creating task to push the stream to all connected clients + int rc = xTaskCreatePinnedToCore( + streamCB, + "strmCB", + 3 * 1024, + (void*) info, + 2, + &info->task, + APP_CPU); + if ( rc != pdPASS ) { + Serial.printf("handleJPGSstream: error creating RTOS task. rc = %d\n", rc); + Serial.printf("handleJPGSstream: free heap : %d\n", ESP.getFreeHeap()); + // Serial.printf("stk high wm: %d\n", uxTaskGetStackHighWaterMark(tSend)); + delete info; + } + + noActiveClients++; + + // Wake up streaming tasks, if they were previously suspended: + if ( eTaskGetState( tCam ) == eSuspended ) vTaskResume( tCam ); +} + + +// ==== Actually stream content to all connected clients ======================== +void streamCB(void * pvParameters) { + char buf[16]; + TickType_t xLastWakeTime; + TickType_t xFrequency; + + streamInfo* info = (streamInfo*) pvParameters; + + if ( info == NULL ) { + Serial.println("streamCB: a NULL pointer passed"); + } + // Immediately send this client a header + info->client.write(HEADER, hdrLen); + info->client.write(BOUNDARY, bdrLen); + taskYIELD(); + + xLastWakeTime = xTaskGetTickCount(); + xFrequency = pdMS_TO_TICKS(1000 / FPS); + + for (;;) { + // Only bother to send anything if there is someone watching + if ( info->client.connected() ) { + + if ( info->frame != frameNumber) { + xSemaphoreTake( frameSync, portMAX_DELAY ); + if ( info->buffer == NULL ) { + info->buffer = allocateMemory (info->buffer, camSize); + info->len = camSize; + } + else { + if ( camSize > info->len ) { + info->buffer = allocateMemory (info->buffer, camSize); + info->len = camSize; + } + } + memcpy(info->buffer, (const void*) camBuf, info->len); + xSemaphoreGive( frameSync ); + taskYIELD(); + + info->frame = frameNumber; + info->client.write(CTNTTYPE, cntLen); + sprintf(buf, "%d\r\n\r\n", info->len); + info->client.write(buf, strlen(buf)); + info->client.write((char*) info->buffer, (size_t)info->len); + info->client.write(BOUNDARY, bdrLen); + info->client.flush(); + } + } + else { + // client disconnected - clean up. + noActiveClients--; + Serial.printf("streamCB: Stream Task stack wtrmark : %d\n", uxTaskGetStackHighWaterMark(info->task)); + Serial.flush(); + info->client.flush(); + info->client.stop(); + if ( info->buffer ) { + free( info->buffer ); + info->buffer = NULL; + } + delete info; + info = NULL; + vTaskDelete(NULL); + } + // Let other tasks run after serving every client + taskYIELD(); + vTaskDelayUntil(&xLastWakeTime, xFrequency); + } +} + + + +const char JHEADER[] = "HTTP/1.1 200 OK\r\n" \ + "Content-disposition: inline; filename=capture.jpg\r\n" \ + "Content-type: image/jpeg\r\n\r\n"; +const int jhdLen = strlen(JHEADER); + +// ==== Serve up one JPEG frame ============================================= +void handleJPG(void) +{ + WiFiClient client = server.client(); + + if (!client.connected()) return; + camera_fb_t* fb = esp_camera_fb_get(); + client.write(JHEADER, jhdLen); + client.write((char*)fb->buf, fb->len); + esp_camera_fb_return(fb); +} + + +// ==== Handle invalid URL requests ============================================ +void handleNotFound() +{ + String message = "Server is running!\n\n"; + message += "URI: "; + message += server.uri(); + message += "\nMethod: "; + message += (server.method() == HTTP_GET) ? "GET" : "POST"; + message += "\nArguments: "; + message += server.args(); + message += "\n"; + server.send(200, "text / plain", message); +} + + + +// ==== SETUP method ================================================================== +void setup() +{ + + // Setup Serial connection: + Serial.begin(115200); + delay(1000); // wait for a second to let Serial connect + Serial.printf("setup: free heap : %d\n", ESP.getFreeHeap()); + + // Configure the camera + // camera_config_t config; + // config.ledc_channel = LEDC_CHANNEL_0; + // config.ledc_timer = LEDC_TIMER_0; + // config.pin_d0 = Y2_GPIO_NUM; + // config.pin_d1 = Y3_GPIO_NUM; + // config.pin_d2 = Y4_GPIO_NUM; + // config.pin_d3 = Y5_GPIO_NUM; + // config.pin_d4 = Y6_GPIO_NUM; + // config.pin_d5 = Y7_GPIO_NUM; + // config.pin_d6 = Y8_GPIO_NUM; + // config.pin_d7 = Y9_GPIO_NUM; + // config.pin_xclk = XCLK_GPIO_NUM; + // config.pin_pclk = PCLK_GPIO_NUM; + // config.pin_vsync = VSYNC_GPIO_NUM; + // config.pin_href = HREF_GPIO_NUM; + // config.pin_sscb_sda = SIOD_GPIO_NUM; + // config.pin_sscb_scl = SIOC_GPIO_NUM; + // config.pin_pwdn = PWDN_GPIO_NUM; + // config.pin_reset = RESET_GPIO_NUM; + // config.xclk_freq_hz = 20000000; + // config.pixel_format = PIXFORMAT_JPEG; + // + // // Frame parameters: pick one + // // config.frame_size = FRAMESIZE_UXGA; + // // config.frame_size = FRAMESIZE_SVGA; + // // config.frame_size = FRAMESIZE_QVGA; + // config.frame_size = FRAMESIZE_VGA; + // config.jpeg_quality = 12; + // config.fb_count = 2; + + static camera_config_t camera_config = { + .pin_pwdn = PWDN_GPIO_NUM, + .pin_reset = RESET_GPIO_NUM, + .pin_xclk = XCLK_GPIO_NUM, + .pin_sscb_sda = SIOD_GPIO_NUM, + .pin_sscb_scl = SIOC_GPIO_NUM, + .pin_d7 = Y9_GPIO_NUM, + .pin_d6 = Y8_GPIO_NUM, + .pin_d5 = Y7_GPIO_NUM, + .pin_d4 = Y6_GPIO_NUM, + .pin_d3 = Y5_GPIO_NUM, + .pin_d2 = Y4_GPIO_NUM, + .pin_d1 = Y3_GPIO_NUM, + .pin_d0 = Y2_GPIO_NUM, + .pin_vsync = VSYNC_GPIO_NUM, + .pin_href = HREF_GPIO_NUM, + .pin_pclk = PCLK_GPIO_NUM, + + .xclk_freq_hz = 20000000, + .ledc_timer = LEDC_TIMER_0, + .ledc_channel = LEDC_CHANNEL_0, + .pixel_format = PIXFORMAT_JPEG, + /* + FRAMESIZE_96X96, // 96x96 + FRAMESIZE_QQVGA, // 160x120 + FRAMESIZE_QCIF, // 176x144 + FRAMESIZE_HQVGA, // 240x176 + FRAMESIZE_240X240, // 240x240 + FRAMESIZE_QVGA, // 320x240 + FRAMESIZE_CIF, // 400x296 + FRAMESIZE_HVGA, // 480x320 + FRAMESIZE_VGA, // 640x480 + FRAMESIZE_SVGA, // 800x600 + FRAMESIZE_XGA, // 1024x768 + FRAMESIZE_HD, // 1280x720 + FRAMESIZE_SXGA, // 1280x1024 + FRAMESIZE_UXGA, // 1600x1200 + */ + // .frame_size = FRAMESIZE_QVGA, + // .frame_size = FRAMESIZE_UXGA, + // .frame_size = FRAMESIZE_SVGA, + // .frame_size = FRAMESIZE_VGA, + .frame_size = FRAMESIZE_UXGA, + .jpeg_quality = 16, + .fb_count = 2 + }; + +#if defined(CAMERA_MODEL_ESP_EYE) + pinMode(13, INPUT_PULLUP); + pinMode(14, INPUT_PULLUP); +#endif + + if (esp_camera_init(&camera_config) != ESP_OK) { + Serial.println("Error initializing the camera"); + delay(10000); + ESP.restart(); + } + + + // Configure and connect to WiFi + IPAddress ip; + + WiFi.mode(WIFI_STA); + WiFi.begin(SSID1, PWD1); + Serial.print("Connecting to WiFi"); + while (WiFi.status() != WL_CONNECTED) + { + delay(500); + Serial.print(F(".")); + } + ip = WiFi.localIP(); + Serial.println(F("WiFi connected")); + Serial.println(""); + Serial.print("Stream Link: http://"); + Serial.print(ip); + Serial.println("/mjpeg/1"); + + + // Start mainstreaming RTOS task + xTaskCreatePinnedToCore( + mjpegCB, + "mjpeg", + 2*1024, + NULL, + 2, + &tMjpeg, + APP_CPU); + + Serial.printf("setup complete: free heap : %d\n", ESP.getFreeHeap()); +} + +void loop() { + // this seems to be necessary to let IDLE task run and do GC + vTaskDelay(1000); +} diff --git a/esp32-cam-rtos/esp32-camera-master.zip b/esp32-cam-rtos/esp32-camera-master.zip new file mode 100644 index 0000000000000000000000000000000000000000..e8118ae8cbde083aa2d898912ca0840cbb1ba02f GIT binary patch literal 99319 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%0DWVpqK+tQ4{dZyTI7z{pE?+UbMA=x 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

ccC}J~5Ws<*6rFR> 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, fb->height, fb->pixformat, fb->buf, fb->len); + + //return the frame buffer back to be reused + esp_camera_fb_return(fb); + + return ESP_OK; + } +*/ + +#pragma once + +#include "esp_err.h" +#include "driver/ledc.h" +#include "sensor.h" +#include "sys/time.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Configuration structure for camera initialization + */ +typedef struct { + int pin_pwdn; /*!< GPIO pin for camera power down line */ + int pin_reset; /*!< GPIO pin for camera reset line */ + int pin_xclk; /*!< GPIO pin for camera XCLK line */ + int pin_sscb_sda; /*!< GPIO pin for camera SDA line */ + int pin_sscb_scl; /*!< GPIO pin for camera SCL line */ + int pin_d7; /*!< GPIO pin for camera D7 line */ + int pin_d6; /*!< GPIO pin for camera D6 line */ + int pin_d5; /*!< GPIO pin for camera D5 line */ + int pin_d4; /*!< GPIO pin for camera D4 line */ + int pin_d3; /*!< GPIO pin for camera D3 line */ + int pin_d2; /*!< GPIO pin for camera D2 line */ + int pin_d1; /*!< GPIO pin for camera D1 line */ + int pin_d0; /*!< GPIO pin for camera D0 line */ + int pin_vsync; /*!< GPIO pin for camera VSYNC line */ + int pin_href; /*!< GPIO pin for camera HREF line */ + int pin_pclk; /*!< GPIO pin for camera PCLK line */ + + int xclk_freq_hz; /*!< Frequency of XCLK signal, in Hz. Either 20KHz or 10KHz for OV2640 double FPS (Experimental) */ + + ledc_timer_t ledc_timer; /*!< LEDC timer to be used for generating XCLK */ + ledc_channel_t ledc_channel; /*!< LEDC channel to be used for generating XCLK */ + + pixformat_t pixel_format; /*!< Format of the pixel data: PIXFORMAT_ + YUV422|GRAYSCALE|RGB565|JPEG */ + framesize_t frame_size; /*!< Size of the output image: FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA */ + + int jpeg_quality; /*!< Quality of JPEG output. 0-63 lower means higher quality */ + size_t fb_count; /*!< Number of frame buffers to be allocated. If more than one, then each frame will be acquired (double speed) */ +} camera_config_t; + +/** + * @brief Data structure of camera frame buffer + */ +typedef struct { + uint8_t * buf; /*!< Pointer to the pixel data */ + size_t len; /*!< Length of the buffer in bytes */ + size_t width; /*!< Width of the buffer in pixels */ + size_t height; /*!< Height of the buffer in pixels */ + pixformat_t format; /*!< Format of the pixel data */ + struct timeval timestamp; /*!< Timestamp since boot of the first DMA buffer of the frame */ +} camera_fb_t; + +#define ESP_ERR_CAMERA_BASE 0x20000 +#define ESP_ERR_CAMERA_NOT_DETECTED (ESP_ERR_CAMERA_BASE + 1) +#define ESP_ERR_CAMERA_FAILED_TO_SET_FRAME_SIZE (ESP_ERR_CAMERA_BASE + 2) +#define ESP_ERR_CAMERA_FAILED_TO_SET_OUT_FORMAT (ESP_ERR_CAMERA_BASE + 3) +#define ESP_ERR_CAMERA_NOT_SUPPORTED (ESP_ERR_CAMERA_BASE + 4) + +/** + * @brief Initialize the camera driver + * + * @note call camera_probe before calling this function + * + * This function detects and configures camera over I2C interface, + * allocates framebuffer and DMA buffers, + * initializes parallel I2S input, and sets up DMA descriptors. + * + * Currently this function can only be called once and there is + * no way to de-initialize this module. + * + * @param config Camera configuration parameters + * + * @return ESP_OK on success + */ +esp_err_t esp_camera_init(const camera_config_t* config); + +/** + * @brief Deinitialize the camera driver + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if the driver hasn't been initialized yet + */ +esp_err_t esp_camera_deinit(); + +/** + * @brief Obtain pointer to a frame buffer. + * + * @return pointer to the frame buffer + */ +camera_fb_t* esp_camera_fb_get(); + +/** + * @brief Return the frame buffer to be reused again. + * + * @param fb Pointer to the frame buffer + */ +void esp_camera_fb_return(camera_fb_t * fb); + +/** + * @brief Get a pointer to the image sensor control structure + * + * @return pointer to the sensor + */ +sensor_t * esp_camera_sensor_get(); + +/** + * @brief Save camera settings to non-volatile-storage (NVS) + * + * @param key A unique nvs key name for the camera settings + */ +esp_err_t esp_camera_save_to_nvs(const char *key); + +/** + * @brief Load camera settings from non-volatile-storage (NVS) + * + * @param key A unique nvs key name for the camera settings + */ +esp_err_t esp_camera_load_from_nvs(const char *key); + +#ifdef __cplusplus +} +#endif + +#include "img_converters.h" + diff --git a/esp32-cam-rtos/esp_jpg_decode.c b/esp32-cam-rtos/esp_jpg_decode.c new file mode 100644 index 0000000..d42794f --- /dev/null +++ b/esp32-cam-rtos/esp_jpg_decode.c @@ -0,0 +1,128 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "esp_jpg_decode.h" + +#include "esp_system.h" +#if ESP_IDF_VERSION_MAJOR >= 4 // IDF 4+ +#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 +#include "esp32/rom/tjpgd.h" +#else +#error Target CONFIG_IDF_TARGET is not supported +#endif +#else // ESP32 Before IDF 4.0 +#include "rom/tjpgd.h" +#endif + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define TAG "" +#else +#include "esp_log.h" +static const char* TAG = "esp_jpg_decode"; +#endif + +typedef struct { + jpg_scale_t scale; + jpg_reader_cb reader; + jpg_writer_cb writer; + void * arg; + size_t len; + size_t index; +} esp_jpg_decoder_t; + +static const char * jd_errors[] = { + "Succeeded", + "Interrupted by output function", + "Device error or wrong termination of input stream", + "Insufficient memory pool for the image", + "Insufficient stream input buffer", + "Parameter error", + "Data format error", + "Right format but not supported", + "Not supported JPEG standard" +}; + +static uint32_t _jpg_write(JDEC *decoder, void *bitmap, JRECT *rect) +{ + uint16_t x = rect->left; + uint16_t y = rect->top; + uint16_t w = rect->right + 1 - x; + uint16_t h = rect->bottom + 1 - y; + uint8_t *data = (uint8_t *)bitmap; + + esp_jpg_decoder_t * jpeg = (esp_jpg_decoder_t *)decoder->device; + + if (jpeg->writer) { + return jpeg->writer(jpeg->arg, x, y, w, h, data); + } + return 0; +} + +static uint32_t _jpg_read(JDEC *decoder, uint8_t *buf, uint32_t len) +{ + esp_jpg_decoder_t * jpeg = (esp_jpg_decoder_t *)decoder->device; + if (jpeg->len && len > (jpeg->len - jpeg->index)) { + len = jpeg->len - jpeg->index; + } + if (len) { + len = jpeg->reader(jpeg->arg, jpeg->index, buf, len); + if (!len) { + ESP_LOGE(TAG, "Read Fail at %u/%u", jpeg->index, jpeg->len); + } + jpeg->index += len; + } + return len; +} + +esp_err_t esp_jpg_decode(size_t len, jpg_scale_t scale, jpg_reader_cb reader, jpg_writer_cb writer, void * arg) +{ + static uint8_t work[3100]; + JDEC decoder; + esp_jpg_decoder_t jpeg; + + jpeg.len = len; + jpeg.reader = reader; + jpeg.writer = writer; + jpeg.arg = arg; + jpeg.scale = scale; + jpeg.index = 0; + + JRESULT jres = jd_prepare(&decoder, _jpg_read, work, 3100, &jpeg); + if(jres != JDR_OK){ + ESP_LOGE(TAG, "JPG Header Parse Failed! %s", jd_errors[jres]); + return ESP_FAIL; + } + + uint16_t output_width = decoder.width / (1 << (uint8_t)(jpeg.scale)); + uint16_t output_height = decoder.height / (1 << (uint8_t)(jpeg.scale)); + + //output start + writer(arg, 0, 0, output_width, output_height, NULL); + //output write + jres = jd_decomp(&decoder, _jpg_write, (uint8_t)jpeg.scale); + //output end + writer(arg, output_width, output_height, output_width, output_height, NULL); + + if (jres != JDR_OK) { + ESP_LOGE(TAG, "JPG Decompression Failed! %s", jd_errors[jres]); + return ESP_FAIL; + } + //check if all data has been consumed. + if (len && jpeg.index < len) { + _jpg_read(&decoder, NULL, len - jpeg.index); + } + + return ESP_OK; +} + diff --git a/esp32-cam-rtos/esp_jpg_decode.h b/esp32-cam-rtos/esp_jpg_decode.h new file mode 100644 index 0000000..f13536e --- /dev/null +++ b/esp32-cam-rtos/esp_jpg_decode.h @@ -0,0 +1,43 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef _ESP_JPG_DECODE_H_ +#define _ESP_JPG_DECODE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include "esp_err.h" + +typedef enum { + JPG_SCALE_NONE, + JPG_SCALE_2X, + JPG_SCALE_4X, + JPG_SCALE_8X, + JPG_SCALE_MAX = JPG_SCALE_8X +} jpg_scale_t; + +typedef size_t (* jpg_reader_cb)(void * arg, size_t index, uint8_t *buf, size_t len); +typedef bool (* jpg_writer_cb)(void * arg, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t *data); + +esp_err_t esp_jpg_decode(size_t len, jpg_scale_t scale, jpg_reader_cb reader, jpg_writer_cb writer, void * arg); + +#ifdef __cplusplus +} +#endif + +#endif /* _ESP_JPG_DECODE_H_ */ diff --git a/esp32-cam-rtos/img_converters.h b/esp32-cam-rtos/img_converters.h new file mode 100644 index 0000000..2b83c4d --- /dev/null +++ b/esp32-cam-rtos/img_converters.h @@ -0,0 +1,126 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef _IMG_CONVERTERS_H_ +#define _IMG_CONVERTERS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include "esp_camera.h" + +typedef size_t (* jpg_out_cb)(void * arg, size_t index, const void* data, size_t len); + +/** + * @brief Convert image buffer to JPEG + * + * @param src Source buffer in RGB565, RGB888, YUYV or GRAYSCALE format + * @param src_len Length in bytes of the source buffer + * @param width Width in pixels of the source image + * @param height Height in pixels of the source image + * @param format Format of the source image + * @param quality JPEG quality of the resulting image + * @param cp Callback to be called to write the bytes of the output JPEG + * @param arg Pointer to be passed to the callback + * + * @return true on success + */ +bool fmt2jpg_cb(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, jpg_out_cb cb, void * arg); + +/** + * @brief Convert camera frame buffer to JPEG + * + * @param fb Source camera frame buffer + * @param quality JPEG quality of the resulting image + * @param cp Callback to be called to write the bytes of the output JPEG + * @param arg Pointer to be passed to the callback + * + * @return true on success + */ +bool frame2jpg_cb(camera_fb_t * fb, uint8_t quality, jpg_out_cb cb, void * arg); + +/** + * @brief Convert image buffer to JPEG buffer + * + * @param src Source buffer in RGB565, RGB888, YUYV or GRAYSCALE format + * @param src_len Length in bytes of the source buffer + * @param width Width in pixels of the source image + * @param height Height in pixels of the source image + * @param format Format of the source image + * @param quality JPEG quality of the resulting image + * @param out Pointer to be populated with the address of the resulting buffer + * @param out_len Pointer to be populated with the length of the output buffer + * + * @return true on success + */ +bool fmt2jpg(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, uint8_t ** out, size_t * out_len); + +/** + * @brief Convert camera frame buffer to JPEG buffer + * + * @param fb Source camera frame buffer + * @param quality JPEG quality of the resulting image + * @param out Pointer to be populated with the address of the resulting buffer + * @param out_len Pointer to be populated with the length of the output buffer + * + * @return true on success + */ +bool frame2jpg(camera_fb_t * fb, uint8_t quality, uint8_t ** out, size_t * out_len); + +/** + * @brief Convert image buffer to BMP buffer + * + * @param src Source buffer in JPEG, RGB565, RGB888, YUYV or GRAYSCALE format + * @param src_len Length in bytes of the source buffer + * @param width Width in pixels of the source image + * @param height Height in pixels of the source image + * @param format Format of the source image + * @param out Pointer to be populated with the address of the resulting buffer + * @param out_len Pointer to be populated with the length of the output buffer + * + * @return true on success + */ +bool fmt2bmp(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t ** out, size_t * out_len); + +/** + * @brief Convert camera frame buffer to BMP buffer + * + * @param fb Source camera frame buffer + * @param out Pointer to be populated with the address of the resulting buffer + * @param out_len Pointer to be populated with the length of the output buffer + * + * @return true on success + */ +bool frame2bmp(camera_fb_t * fb, uint8_t ** out, size_t * out_len); + +/** + * @brief Convert image buffer to RGB888 buffer (used for face detection) + * + * @param src Source buffer in JPEG, RGB565, RGB888, YUYV or GRAYSCALE format + * @param src_len Length in bytes of the source buffer + * @param format Format of the source image + * @param rgb_buf Pointer to the output buffer (width * height * 3) + * + * @return true on success + */ +bool fmt2rgb888(const uint8_t *src_buf, size_t src_len, pixformat_t format, uint8_t * rgb_buf); + +#ifdef __cplusplus +} +#endif + +#endif /* _IMG_CONVERTERS_H_ */ diff --git a/esp32-cam-rtos/jpge.cpp b/esp32-cam-rtos/jpge.cpp new file mode 100644 index 0000000..a8ab93e --- /dev/null +++ b/esp32-cam-rtos/jpge.cpp @@ -0,0 +1,723 @@ +// jpge.cpp - C++ class for JPEG compression. +// Public domain, Rich Geldreich +// v1.01, Dec. 18, 2010 - Initial release +// v1.02, Apr. 6, 2011 - Removed 2x2 ordered dither in H2V1 chroma subsampling method load_block_16_8_8(). (The rounding factor was 2, when it should have been 1. Either way, it wasn't helping.) +// v1.03, Apr. 16, 2011 - Added support for optimized Huffman code tables, optimized dynamic memory allocation down to only 1 alloc. +// Also from Alex Evans: Added RGBA support, linear memory allocator (no longer needed in v1.03). +// v1.04, May. 19, 2012: Forgot to set m_pFile ptr to NULL in cfile_stream::close(). Thanks to Owen Kaluza for reporting this bug. +// Code tweaks to fix VS2008 static code analysis warnings (all looked harmless). +// Code review revealed method load_block_16_8_8() (used for the non-default H2V1 sampling mode to downsample chroma) somehow didn't get the rounding factor fix from v1.02. + +#include "jpge.h" + +#include +#include +#include +#include +#include +#include +#include +#include "esp_heap_caps.h" + +#define JPGE_MAX(a,b) (((a)>(b))?(a):(b)) +#define JPGE_MIN(a,b) (((a)<(b))?(a):(b)) + +namespace jpge { + + static inline void *jpge_malloc(size_t nSize) { + void * b = malloc(nSize); + if(b){ + return b; + } + return heap_caps_malloc(nSize, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + } + static inline void jpge_free(void *p) { free(p); } + + // Various JPEG enums and tables. + enum { M_SOF0 = 0xC0, M_DHT = 0xC4, M_SOI = 0xD8, M_EOI = 0xD9, M_SOS = 0xDA, M_DQT = 0xDB, M_APP0 = 0xE0 }; + enum { DC_LUM_CODES = 12, AC_LUM_CODES = 256, DC_CHROMA_CODES = 12, AC_CHROMA_CODES = 256, MAX_HUFF_SYMBOLS = 257, MAX_HUFF_CODESIZE = 32 }; + + static const uint8 s_zag[64] = { 0,1,8,16,9,2,3,10,17,24,32,25,18,11,4,5,12,19,26,33,40,48,41,34,27,20,13,6,7,14,21,28,35,42,49,56,57,50,43,36,29,22,15,23,30,37,44,51,58,59,52,45,38,31,39,46,53,60,61,54,47,55,62,63 }; + static const int16 s_std_lum_quant[64] = { 16,11,12,14,12,10,16,14,13,14,18,17,16,19,24,40,26,24,22,22,24,49,35,37,29,40,58,51,61,60,57,51,56,55,64,72,92,78,64,68,87,69,55,56,80,109,81,87,95,98,103,104,103,62,77,113,121,112,100,120,92,101,103,99 }; + static const int16 s_std_croma_quant[64] = { 17,18,18,24,21,24,47,26,26,47,99,66,56,66,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99 }; + static const uint8 s_dc_lum_bits[17] = { 0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0 }; + static const uint8 s_dc_lum_val[DC_LUM_CODES] = { 0,1,2,3,4,5,6,7,8,9,10,11 }; + static const uint8 s_ac_lum_bits[17] = { 0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d }; + static const uint8 s_ac_lum_val[AC_LUM_CODES] = { + 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0, + 0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49, + 0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5, + 0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, + 0xf9,0xfa + }; + static const uint8 s_dc_chroma_bits[17] = { 0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0 }; + static const uint8 s_dc_chroma_val[DC_CHROMA_CODES] = { 0,1,2,3,4,5,6,7,8,9,10,11 }; + static const uint8 s_ac_chroma_bits[17] = { 0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77 }; + static const uint8 s_ac_chroma_val[AC_CHROMA_CODES] = { + 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0, + 0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48, + 0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3, + 0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, + 0xf9,0xfa + }; + + const int YR = 19595, YG = 38470, YB = 7471, CB_R = -11059, CB_G = -21709, CB_B = 32768, CR_R = 32768, CR_G = -27439, CR_B = -5329; + + static int32 m_last_quality = 0; + static int32 m_quantization_tables[2][64]; + + static bool m_huff_initialized = false; + static uint m_huff_codes[4][256]; + static uint8 m_huff_code_sizes[4][256]; + static uint8 m_huff_bits[4][17]; + static uint8 m_huff_val[4][256]; + + static inline uint8 clamp(int i) { + if (i < 0) { + i = 0; + } else if (i > 255){ + i = 255; + } + return static_cast(i); + } + + static void RGB_to_YCC(uint8* pDst, const uint8 *pSrc, int num_pixels) { + for ( ; num_pixels; pDst += 3, pSrc += 3, num_pixels--) { + const int r = pSrc[0], g = pSrc[1], b = pSrc[2]; + pDst[0] = static_cast((r * YR + g * YG + b * YB + 32768) >> 16); + pDst[1] = clamp(128 + ((r * CB_R + g * CB_G + b * CB_B + 32768) >> 16)); + pDst[2] = clamp(128 + ((r * CR_R + g * CR_G + b * CR_B + 32768) >> 16)); + } + } + + static void RGB_to_Y(uint8* pDst, const uint8 *pSrc, int num_pixels) { + for ( ; num_pixels; pDst++, pSrc += 3, num_pixels--) { + pDst[0] = static_cast((pSrc[0] * YR + pSrc[1] * YG + pSrc[2] * YB + 32768) >> 16); + } + } + + static void Y_to_YCC(uint8* pDst, const uint8* pSrc, int num_pixels) { + for( ; num_pixels; pDst += 3, pSrc++, num_pixels--) { + pDst[0] = pSrc[0]; + pDst[1] = 128; + pDst[2] = 128; + } + } + + // Forward DCT - DCT derived from jfdctint. + enum { CONST_BITS = 13, ROW_BITS = 2 }; +#define DCT_DESCALE(x, n) (((x) + (((int32)1) << ((n) - 1))) >> (n)) +#define DCT_MUL(var, c) (static_cast(var) * static_cast(c)) +#define DCT1D(s0, s1, s2, s3, s4, s5, s6, s7) \ + int32 t0 = s0 + s7, t7 = s0 - s7, t1 = s1 + s6, t6 = s1 - s6, t2 = s2 + s5, t5 = s2 - s5, t3 = s3 + s4, t4 = s3 - s4; \ + int32 t10 = t0 + t3, t13 = t0 - t3, t11 = t1 + t2, t12 = t1 - t2; \ + int32 u1 = DCT_MUL(t12 + t13, 4433); \ + s2 = u1 + DCT_MUL(t13, 6270); \ + s6 = u1 + DCT_MUL(t12, -15137); \ + u1 = t4 + t7; \ + int32 u2 = t5 + t6, u3 = t4 + t6, u4 = t5 + t7; \ + int32 z5 = DCT_MUL(u3 + u4, 9633); \ + t4 = DCT_MUL(t4, 2446); t5 = DCT_MUL(t5, 16819); \ + t6 = DCT_MUL(t6, 25172); t7 = DCT_MUL(t7, 12299); \ + u1 = DCT_MUL(u1, -7373); u2 = DCT_MUL(u2, -20995); \ + u3 = DCT_MUL(u3, -16069); u4 = DCT_MUL(u4, -3196); \ + u3 += z5; u4 += z5; \ + s0 = t10 + t11; s1 = t7 + u1 + u4; s3 = t6 + u2 + u3; s4 = t10 - t11; s5 = t5 + u2 + u4; s7 = t4 + u1 + u3; + + static void DCT2D(int32 *p) { + int32 c, *q = p; + for (c = 7; c >= 0; c--, q += 8) { + int32 s0 = q[0], s1 = q[1], s2 = q[2], s3 = q[3], s4 = q[4], s5 = q[5], s6 = q[6], s7 = q[7]; + DCT1D(s0, s1, s2, s3, s4, s5, s6, s7); + q[0] = s0 << ROW_BITS; q[1] = DCT_DESCALE(s1, CONST_BITS-ROW_BITS); q[2] = DCT_DESCALE(s2, CONST_BITS-ROW_BITS); q[3] = DCT_DESCALE(s3, CONST_BITS-ROW_BITS); + q[4] = s4 << ROW_BITS; q[5] = DCT_DESCALE(s5, CONST_BITS-ROW_BITS); q[6] = DCT_DESCALE(s6, CONST_BITS-ROW_BITS); q[7] = DCT_DESCALE(s7, CONST_BITS-ROW_BITS); + } + for (q = p, c = 7; c >= 0; c--, q++) { + int32 s0 = q[0*8], s1 = q[1*8], s2 = q[2*8], s3 = q[3*8], s4 = q[4*8], s5 = q[5*8], s6 = q[6*8], s7 = q[7*8]; + DCT1D(s0, s1, s2, s3, s4, s5, s6, s7); + q[0*8] = DCT_DESCALE(s0, ROW_BITS+3); q[1*8] = DCT_DESCALE(s1, CONST_BITS+ROW_BITS+3); q[2*8] = DCT_DESCALE(s2, CONST_BITS+ROW_BITS+3); q[3*8] = DCT_DESCALE(s3, CONST_BITS+ROW_BITS+3); + q[4*8] = DCT_DESCALE(s4, ROW_BITS+3); q[5*8] = DCT_DESCALE(s5, CONST_BITS+ROW_BITS+3); q[6*8] = DCT_DESCALE(s6, CONST_BITS+ROW_BITS+3); q[7*8] = DCT_DESCALE(s7, CONST_BITS+ROW_BITS+3); + } + } + + // Compute the actual canonical Huffman codes/code sizes given the JPEG huff bits and val arrays. + static void compute_huffman_table(uint *codes, uint8 *code_sizes, uint8 *bits, uint8 *val) + { + int i, l, last_p, si; + static uint8 huff_size[257]; + static uint huff_code[257]; + uint code; + + int p = 0; + for (l = 1; l <= 16; l++) { + for (i = 1; i <= bits[l]; i++) { + huff_size[p++] = (char)l; + } + } + + huff_size[p] = 0; + last_p = p; // write sentinel + + code = 0; si = huff_size[0]; p = 0; + + while (huff_size[p]) { + while (huff_size[p] == si) { + huff_code[p++] = code++; + } + code <<= 1; + si++; + } + + memset(codes, 0, sizeof(codes[0])*256); + memset(code_sizes, 0, sizeof(code_sizes[0])*256); + for (p = 0; p < last_p; p++) { + codes[val[p]] = huff_code[p]; + code_sizes[val[p]] = huff_size[p]; + } + } + + void jpeg_encoder::flush_output_buffer() + { + if (m_out_buf_left != JPGE_OUT_BUF_SIZE) { + m_all_stream_writes_succeeded = m_all_stream_writes_succeeded && m_pStream->put_buf(m_out_buf, JPGE_OUT_BUF_SIZE - m_out_buf_left); + } + m_pOut_buf = m_out_buf; + m_out_buf_left = JPGE_OUT_BUF_SIZE; + } + + void jpeg_encoder::emit_byte(uint8 i) + { + *m_pOut_buf++ = i; + if (--m_out_buf_left == 0) { + flush_output_buffer(); + } + } + + void jpeg_encoder::put_bits(uint bits, uint len) + { + uint8 c = 0; + m_bit_buffer |= ((uint32)bits << (24 - (m_bits_in += len))); + while (m_bits_in >= 8) { + c = (uint8)((m_bit_buffer >> 16) & 0xFF); + emit_byte(c); + if (c == 0xFF) { + emit_byte(0); + } + m_bit_buffer <<= 8; + m_bits_in -= 8; + } + } + + void jpeg_encoder::emit_word(uint i) + { + emit_byte(uint8(i >> 8)); emit_byte(uint8(i & 0xFF)); + } + + // JPEG marker generation. + void jpeg_encoder::emit_marker(int marker) + { + emit_byte(uint8(0xFF)); emit_byte(uint8(marker)); + } + + // Emit JFIF marker + void jpeg_encoder::emit_jfif_app0() + { + emit_marker(M_APP0); + emit_word(2 + 4 + 1 + 2 + 1 + 2 + 2 + 1 + 1); + emit_byte(0x4A); emit_byte(0x46); emit_byte(0x49); emit_byte(0x46); /* Identifier: ASCII "JFIF" */ + emit_byte(0); + emit_byte(1); /* Major version */ + emit_byte(1); /* Minor version */ + emit_byte(0); /* Density unit */ + emit_word(1); + emit_word(1); + emit_byte(0); /* No thumbnail image */ + emit_byte(0); + } + + // Emit quantization tables + void jpeg_encoder::emit_dqt() + { + for (int i = 0; i < ((m_num_components == 3) ? 2 : 1); i++) + { + emit_marker(M_DQT); + emit_word(64 + 1 + 2); + emit_byte(static_cast(i)); + for (int j = 0; j < 64; j++) + emit_byte(static_cast(m_quantization_tables[i][j])); + } + } + + // Emit start of frame marker + void jpeg_encoder::emit_sof() + { + emit_marker(M_SOF0); /* baseline */ + emit_word(3 * m_num_components + 2 + 5 + 1); + emit_byte(8); /* precision */ + emit_word(m_image_y); + emit_word(m_image_x); + emit_byte(m_num_components); + for (int i = 0; i < m_num_components; i++) + { + emit_byte(static_cast(i + 1)); /* component ID */ + emit_byte((m_comp_h_samp[i] << 4) + m_comp_v_samp[i]); /* h and v sampling */ + emit_byte(i > 0); /* quant. table num */ + } + } + + // Emit Huffman table. + void jpeg_encoder::emit_dht(uint8 *bits, uint8 *val, int index, bool ac_flag) + { + emit_marker(M_DHT); + + int length = 0; + for (int i = 1; i <= 16; i++) + length += bits[i]; + + emit_word(length + 2 + 1 + 16); + emit_byte(static_cast(index + (ac_flag << 4))); + + for (int i = 1; i <= 16; i++) + emit_byte(bits[i]); + + for (int i = 0; i < length; i++) + emit_byte(val[i]); + } + + // Emit all Huffman tables. + void jpeg_encoder::emit_dhts() + { + emit_dht(m_huff_bits[0+0], m_huff_val[0+0], 0, false); + emit_dht(m_huff_bits[2+0], m_huff_val[2+0], 0, true); + if (m_num_components == 3) { + emit_dht(m_huff_bits[0+1], m_huff_val[0+1], 1, false); + emit_dht(m_huff_bits[2+1], m_huff_val[2+1], 1, true); + } + } + + // emit start of scan + void jpeg_encoder::emit_sos() + { + emit_marker(M_SOS); + emit_word(2 * m_num_components + 2 + 1 + 3); + emit_byte(m_num_components); + for (int i = 0; i < m_num_components; i++) + { + emit_byte(static_cast(i + 1)); + if (i == 0) + emit_byte((0 << 4) + 0); + else + emit_byte((1 << 4) + 1); + } + emit_byte(0); /* spectral selection */ + emit_byte(63); + emit_byte(0); + } + + void jpeg_encoder::load_block_8_8_grey(int x) + { + uint8 *pSrc; + sample_array_t *pDst = m_sample_array; + x <<= 3; + for (int i = 0; i < 8; i++, pDst += 8) + { + pSrc = m_mcu_lines[i] + x; + pDst[0] = pSrc[0] - 128; pDst[1] = pSrc[1] - 128; pDst[2] = pSrc[2] - 128; pDst[3] = pSrc[3] - 128; + pDst[4] = pSrc[4] - 128; pDst[5] = pSrc[5] - 128; pDst[6] = pSrc[6] - 128; pDst[7] = pSrc[7] - 128; + } + } + + void jpeg_encoder::load_block_8_8(int x, int y, int c) + { + uint8 *pSrc; + sample_array_t *pDst = m_sample_array; + x = (x * (8 * 3)) + c; + y <<= 3; + for (int i = 0; i < 8; i++, pDst += 8) + { + pSrc = m_mcu_lines[y + i] + x; + pDst[0] = pSrc[0 * 3] - 128; pDst[1] = pSrc[1 * 3] - 128; pDst[2] = pSrc[2 * 3] - 128; pDst[3] = pSrc[3 * 3] - 128; + pDst[4] = pSrc[4 * 3] - 128; pDst[5] = pSrc[5 * 3] - 128; pDst[6] = pSrc[6 * 3] - 128; pDst[7] = pSrc[7 * 3] - 128; + } + } + + void jpeg_encoder::load_block_16_8(int x, int c) + { + uint8 *pSrc1, *pSrc2; + sample_array_t *pDst = m_sample_array; + x = (x * (16 * 3)) + c; + int a = 0, b = 2; + for (int i = 0; i < 16; i += 2, pDst += 8) + { + pSrc1 = m_mcu_lines[i + 0] + x; + pSrc2 = m_mcu_lines[i + 1] + x; + pDst[0] = ((pSrc1[ 0 * 3] + pSrc1[ 1 * 3] + pSrc2[ 0 * 3] + pSrc2[ 1 * 3] + a) >> 2) - 128; pDst[1] = ((pSrc1[ 2 * 3] + pSrc1[ 3 * 3] + pSrc2[ 2 * 3] + pSrc2[ 3 * 3] + b) >> 2) - 128; + pDst[2] = ((pSrc1[ 4 * 3] + pSrc1[ 5 * 3] + pSrc2[ 4 * 3] + pSrc2[ 5 * 3] + a) >> 2) - 128; pDst[3] = ((pSrc1[ 6 * 3] + pSrc1[ 7 * 3] + pSrc2[ 6 * 3] + pSrc2[ 7 * 3] + b) >> 2) - 128; + pDst[4] = ((pSrc1[ 8 * 3] + pSrc1[ 9 * 3] + pSrc2[ 8 * 3] + pSrc2[ 9 * 3] + a) >> 2) - 128; pDst[5] = ((pSrc1[10 * 3] + pSrc1[11 * 3] + pSrc2[10 * 3] + pSrc2[11 * 3] + b) >> 2) - 128; + pDst[6] = ((pSrc1[12 * 3] + pSrc1[13 * 3] + pSrc2[12 * 3] + pSrc2[13 * 3] + a) >> 2) - 128; pDst[7] = ((pSrc1[14 * 3] + pSrc1[15 * 3] + pSrc2[14 * 3] + pSrc2[15 * 3] + b) >> 2) - 128; + int temp = a; a = b; b = temp; + } + } + + void jpeg_encoder::load_block_16_8_8(int x, int c) + { + uint8 *pSrc1; + sample_array_t *pDst = m_sample_array; + x = (x * (16 * 3)) + c; + for (int i = 0; i < 8; i++, pDst += 8) + { + pSrc1 = m_mcu_lines[i + 0] + x; + pDst[0] = ((pSrc1[ 0 * 3] + pSrc1[ 1 * 3]) >> 1) - 128; pDst[1] = ((pSrc1[ 2 * 3] + pSrc1[ 3 * 3]) >> 1) - 128; + pDst[2] = ((pSrc1[ 4 * 3] + pSrc1[ 5 * 3]) >> 1) - 128; pDst[3] = ((pSrc1[ 6 * 3] + pSrc1[ 7 * 3]) >> 1) - 128; + pDst[4] = ((pSrc1[ 8 * 3] + pSrc1[ 9 * 3]) >> 1) - 128; pDst[5] = ((pSrc1[10 * 3] + pSrc1[11 * 3]) >> 1) - 128; + pDst[6] = ((pSrc1[12 * 3] + pSrc1[13 * 3]) >> 1) - 128; pDst[7] = ((pSrc1[14 * 3] + pSrc1[15 * 3]) >> 1) - 128; + } + } + + void jpeg_encoder::load_quantized_coefficients(int component_num) + { + int32 *q = m_quantization_tables[component_num > 0]; + int16 *pDst = m_coefficient_array; + for (int i = 0; i < 64; i++) + { + sample_array_t j = m_sample_array[s_zag[i]]; + if (j < 0) + { + if ((j = -j + (*q >> 1)) < *q) + *pDst++ = 0; + else + *pDst++ = static_cast(-(j / *q)); + } + else + { + if ((j = j + (*q >> 1)) < *q) + *pDst++ = 0; + else + *pDst++ = static_cast((j / *q)); + } + q++; + } + } + + void jpeg_encoder::code_coefficients_pass_two(int component_num) + { + int i, j, run_len, nbits, temp1, temp2; + int16 *pSrc = m_coefficient_array; + uint *codes[2]; + uint8 *code_sizes[2]; + + if (component_num == 0) + { + codes[0] = m_huff_codes[0 + 0]; codes[1] = m_huff_codes[2 + 0]; + code_sizes[0] = m_huff_code_sizes[0 + 0]; code_sizes[1] = m_huff_code_sizes[2 + 0]; + } + else + { + codes[0] = m_huff_codes[0 + 1]; codes[1] = m_huff_codes[2 + 1]; + code_sizes[0] = m_huff_code_sizes[0 + 1]; code_sizes[1] = m_huff_code_sizes[2 + 1]; + } + + temp1 = temp2 = pSrc[0] - m_last_dc_val[component_num]; + m_last_dc_val[component_num] = pSrc[0]; + + if (temp1 < 0) + { + temp1 = -temp1; temp2--; + } + + nbits = 0; + while (temp1) + { + nbits++; temp1 >>= 1; + } + + put_bits(codes[0][nbits], code_sizes[0][nbits]); + if (nbits) put_bits(temp2 & ((1 << nbits) - 1), nbits); + + for (run_len = 0, i = 1; i < 64; i++) + { + if ((temp1 = m_coefficient_array[i]) == 0) + run_len++; + else + { + while (run_len >= 16) + { + put_bits(codes[1][0xF0], code_sizes[1][0xF0]); + run_len -= 16; + } + if ((temp2 = temp1) < 0) + { + temp1 = -temp1; + temp2--; + } + nbits = 1; + while (temp1 >>= 1) + nbits++; + j = (run_len << 4) + nbits; + put_bits(codes[1][j], code_sizes[1][j]); + put_bits(temp2 & ((1 << nbits) - 1), nbits); + run_len = 0; + } + } + if (run_len) + put_bits(codes[1][0], code_sizes[1][0]); + } + + void jpeg_encoder::code_block(int component_num) + { + DCT2D(m_sample_array); + load_quantized_coefficients(component_num); + code_coefficients_pass_two(component_num); + } + + void jpeg_encoder::process_mcu_row() + { + if (m_num_components == 1) + { + for (int i = 0; i < m_mcus_per_row; i++) + { + load_block_8_8_grey(i); code_block(0); + } + } + else if ((m_comp_h_samp[0] == 1) && (m_comp_v_samp[0] == 1)) + { + for (int i = 0; i < m_mcus_per_row; i++) + { + load_block_8_8(i, 0, 0); code_block(0); load_block_8_8(i, 0, 1); code_block(1); load_block_8_8(i, 0, 2); code_block(2); + } + } + else if ((m_comp_h_samp[0] == 2) && (m_comp_v_samp[0] == 1)) + { + for (int i = 0; i < m_mcus_per_row; i++) + { + load_block_8_8(i * 2 + 0, 0, 0); code_block(0); load_block_8_8(i * 2 + 1, 0, 0); code_block(0); + load_block_16_8_8(i, 1); code_block(1); load_block_16_8_8(i, 2); code_block(2); + } + } + else if ((m_comp_h_samp[0] == 2) && (m_comp_v_samp[0] == 2)) + { + for (int i = 0; i < m_mcus_per_row; i++) + { + load_block_8_8(i * 2 + 0, 0, 0); code_block(0); load_block_8_8(i * 2 + 1, 0, 0); code_block(0); + load_block_8_8(i * 2 + 0, 1, 0); code_block(0); load_block_8_8(i * 2 + 1, 1, 0); code_block(0); + load_block_16_8(i, 1); code_block(1); load_block_16_8(i, 2); code_block(2); + } + } + } + + void jpeg_encoder::load_mcu(const void *pSrc) + { + const uint8* Psrc = reinterpret_cast(pSrc); + + uint8* pDst = m_mcu_lines[m_mcu_y_ofs]; // OK to write up to m_image_bpl_xlt bytes to pDst + + if (m_num_components == 1) { + if (m_image_bpp == 3) + RGB_to_Y(pDst, Psrc, m_image_x); + else + memcpy(pDst, Psrc, m_image_x); + } else { + if (m_image_bpp == 3) + RGB_to_YCC(pDst, Psrc, m_image_x); + else + Y_to_YCC(pDst, Psrc, m_image_x); + } + + // Possibly duplicate pixels at end of scanline if not a multiple of 8 or 16 + if (m_num_components == 1) + memset(m_mcu_lines[m_mcu_y_ofs] + m_image_bpl_xlt, pDst[m_image_bpl_xlt - 1], m_image_x_mcu - m_image_x); + else + { + const uint8 y = pDst[m_image_bpl_xlt - 3 + 0], cb = pDst[m_image_bpl_xlt - 3 + 1], cr = pDst[m_image_bpl_xlt - 3 + 2]; + uint8 *q = m_mcu_lines[m_mcu_y_ofs] + m_image_bpl_xlt; + for (int i = m_image_x; i < m_image_x_mcu; i++) + { + *q++ = y; *q++ = cb; *q++ = cr; + } + } + + if (++m_mcu_y_ofs == m_mcu_y) + { + process_mcu_row(); + m_mcu_y_ofs = 0; + } + } + + // Quantization table generation. + void jpeg_encoder::compute_quant_table(int32 *pDst, const int16 *pSrc) + { + int32 q; + if (m_params.m_quality < 50) + q = 5000 / m_params.m_quality; + else + q = 200 - m_params.m_quality * 2; + for (int i = 0; i < 64; i++) + { + int32 j = *pSrc++; j = (j * q + 50L) / 100L; + *pDst++ = JPGE_MIN(JPGE_MAX(j, 1), 255); + } + } + + // Higher-level methods. + bool jpeg_encoder::jpg_open(int p_x_res, int p_y_res, int src_channels) + { + m_num_components = 3; + switch (m_params.m_subsampling) + { + case Y_ONLY: + { + m_num_components = 1; + m_comp_h_samp[0] = 1; m_comp_v_samp[0] = 1; + m_mcu_x = 8; m_mcu_y = 8; + break; + } + case H1V1: + { + m_comp_h_samp[0] = 1; m_comp_v_samp[0] = 1; + m_comp_h_samp[1] = 1; m_comp_v_samp[1] = 1; + m_comp_h_samp[2] = 1; m_comp_v_samp[2] = 1; + m_mcu_x = 8; m_mcu_y = 8; + break; + } + case H2V1: + { + m_comp_h_samp[0] = 2; m_comp_v_samp[0] = 1; + m_comp_h_samp[1] = 1; m_comp_v_samp[1] = 1; + m_comp_h_samp[2] = 1; m_comp_v_samp[2] = 1; + m_mcu_x = 16; m_mcu_y = 8; + break; + } + case H2V2: + { + m_comp_h_samp[0] = 2; m_comp_v_samp[0] = 2; + m_comp_h_samp[1] = 1; m_comp_v_samp[1] = 1; + m_comp_h_samp[2] = 1; m_comp_v_samp[2] = 1; + m_mcu_x = 16; m_mcu_y = 16; + } + } + + m_image_x = p_x_res; m_image_y = p_y_res; + m_image_bpp = src_channels; + m_image_bpl = m_image_x * src_channels; + m_image_x_mcu = (m_image_x + m_mcu_x - 1) & (~(m_mcu_x - 1)); + m_image_y_mcu = (m_image_y + m_mcu_y - 1) & (~(m_mcu_y - 1)); + m_image_bpl_xlt = m_image_x * m_num_components; + m_image_bpl_mcu = m_image_x_mcu * m_num_components; + m_mcus_per_row = m_image_x_mcu / m_mcu_x; + + if ((m_mcu_lines[0] = static_cast(jpge_malloc(m_image_bpl_mcu * m_mcu_y))) == NULL) { + return false; + } + for (int i = 1; i < m_mcu_y; i++) + m_mcu_lines[i] = m_mcu_lines[i-1] + m_image_bpl_mcu; + + if(m_last_quality != m_params.m_quality){ + m_last_quality = m_params.m_quality; + compute_quant_table(m_quantization_tables[0], s_std_lum_quant); + compute_quant_table(m_quantization_tables[1], s_std_croma_quant); + } + + if(!m_huff_initialized){ + m_huff_initialized = true; + + memcpy(m_huff_bits[0+0], s_dc_lum_bits, 17); memcpy(m_huff_val[0+0], s_dc_lum_val, DC_LUM_CODES); + memcpy(m_huff_bits[2+0], s_ac_lum_bits, 17); memcpy(m_huff_val[2+0], s_ac_lum_val, AC_LUM_CODES); + memcpy(m_huff_bits[0+1], s_dc_chroma_bits, 17); memcpy(m_huff_val[0+1], s_dc_chroma_val, DC_CHROMA_CODES); + memcpy(m_huff_bits[2+1], s_ac_chroma_bits, 17); memcpy(m_huff_val[2+1], s_ac_chroma_val, AC_CHROMA_CODES); + + compute_huffman_table(&m_huff_codes[0+0][0], &m_huff_code_sizes[0+0][0], m_huff_bits[0+0], m_huff_val[0+0]); + compute_huffman_table(&m_huff_codes[2+0][0], &m_huff_code_sizes[2+0][0], m_huff_bits[2+0], m_huff_val[2+0]); + compute_huffman_table(&m_huff_codes[0+1][0], &m_huff_code_sizes[0+1][0], m_huff_bits[0+1], m_huff_val[0+1]); + compute_huffman_table(&m_huff_codes[2+1][0], &m_huff_code_sizes[2+1][0], m_huff_bits[2+1], m_huff_val[2+1]); + } + + m_out_buf_left = JPGE_OUT_BUF_SIZE; + m_pOut_buf = m_out_buf; + m_bit_buffer = 0; + m_bits_in = 0; + m_mcu_y_ofs = 0; + m_pass_num = 2; + memset(m_last_dc_val, 0, 3 * sizeof(m_last_dc_val[0])); + + // Emit all markers at beginning of image file. + emit_marker(M_SOI); + emit_jfif_app0(); + emit_dqt(); + emit_sof(); + emit_dhts(); + emit_sos(); + + return m_all_stream_writes_succeeded; + } + + bool jpeg_encoder::process_end_of_image() + { + if (m_mcu_y_ofs) { + if (m_mcu_y_ofs < 16) { // check here just to shut up static analysis + for (int i = m_mcu_y_ofs; i < m_mcu_y; i++) { + memcpy(m_mcu_lines[i], m_mcu_lines[m_mcu_y_ofs - 1], m_image_bpl_mcu); + } + } + process_mcu_row(); + } + + put_bits(0x7F, 7); + emit_marker(M_EOI); + flush_output_buffer(); + m_all_stream_writes_succeeded = m_all_stream_writes_succeeded && m_pStream->put_buf(NULL, 0); + m_pass_num++; // purposely bump up m_pass_num, for debugging + return true; + } + + void jpeg_encoder::clear() + { + m_mcu_lines[0] = NULL; + m_pass_num = 0; + m_all_stream_writes_succeeded = true; + } + + jpeg_encoder::jpeg_encoder() + { + clear(); + } + + jpeg_encoder::~jpeg_encoder() + { + deinit(); + } + + bool jpeg_encoder::init(output_stream *pStream, int width, int height, int src_channels, const params &comp_params) + { + deinit(); + if (((!pStream) || (width < 1) || (height < 1)) || ((src_channels != 1) && (src_channels != 3) && (src_channels != 4)) || (!comp_params.check())) return false; + m_pStream = pStream; + m_params = comp_params; + return jpg_open(width, height, src_channels); + } + + void jpeg_encoder::deinit() + { + jpge_free(m_mcu_lines[0]); + clear(); + } + + bool jpeg_encoder::process_scanline(const void* pScanline) + { + if ((m_pass_num < 1) || (m_pass_num > 2)) { + return false; + } + if (m_all_stream_writes_succeeded) { + if (!pScanline) { + if (!process_end_of_image()) { + return false; + } + } else { + load_mcu(pScanline); + } + } + return m_all_stream_writes_succeeded; + } + +} // namespace jpge diff --git a/esp32-cam-rtos/jpge.h b/esp32-cam-rtos/jpge.h new file mode 100644 index 0000000..aa295c8 --- /dev/null +++ b/esp32-cam-rtos/jpge.h @@ -0,0 +1,142 @@ +// jpge.h - C++ class for JPEG compression. +// Public domain, Rich Geldreich +// Alex Evans: Added RGBA support, linear memory allocator. +#ifndef JPEG_ENCODER_H +#define JPEG_ENCODER_H + +namespace jpge +{ + typedef unsigned char uint8; + typedef signed short int16; + typedef signed int int32; + typedef unsigned short uint16; + typedef unsigned int uint32; + typedef unsigned int uint; + + // JPEG chroma subsampling factors. Y_ONLY (grayscale images) and H2V2 (color images) are the most common. + enum subsampling_t { Y_ONLY = 0, H1V1 = 1, H2V1 = 2, H2V2 = 3 }; + + // JPEG compression parameters structure. + struct params { + inline params() : m_quality(85), m_subsampling(H2V2) { } + + inline bool check() const { + if ((m_quality < 1) || (m_quality > 100)) { + return false; + } + if ((uint)m_subsampling > (uint)H2V2) { + return false; + } + return true; + } + + // Quality: 1-100, higher is better. Typical values are around 50-95. + int m_quality; + + // m_subsampling: + // 0 = Y (grayscale) only + // 1 = H1V1 subsampling (YCbCr 1x1x1, 3 blocks per MCU) + // 2 = H2V1 subsampling (YCbCr 2x1x1, 4 blocks per MCU) + // 3 = H2V2 subsampling (YCbCr 4x1x1, 6 blocks per MCU-- very common) + subsampling_t m_subsampling; + }; + + // Output stream abstract class - used by the jpeg_encoder class to write to the output stream. + // put_buf() is generally called with len==JPGE_OUT_BUF_SIZE bytes, but for headers it'll be called with smaller amounts. + class output_stream { + public: + virtual ~output_stream() { }; + virtual bool put_buf(const void* Pbuf, int len) = 0; + virtual uint get_size() const = 0; + }; + + // Lower level jpeg_encoder class - useful if more control is needed than the above helper functions. + class jpeg_encoder { + public: + jpeg_encoder(); + ~jpeg_encoder(); + + // Initializes the compressor. + // pStream: The stream object to use for writing compressed data. + // params - Compression parameters structure, defined above. + // width, height - Image dimensions. + // channels - May be 1, or 3. 1 indicates grayscale, 3 indicates RGB source data. + // Returns false on out of memory or if a stream write fails. + bool init(output_stream *pStream, int width, int height, int src_channels, const params &comp_params = params()); + + // Call this method with each source scanline. + // width * src_channels bytes per scanline is expected (RGB or Y format). + // You must call with NULL after all scanlines are processed to finish compression. + // Returns false on out of memory or if a stream write fails. + bool process_scanline(const void* pScanline); + + // Deinitializes the compressor, freeing any allocated memory. May be called at any time. + void deinit(); + + private: + jpeg_encoder(const jpeg_encoder &); + jpeg_encoder &operator =(const jpeg_encoder &); + + typedef int32 sample_array_t; + enum { JPGE_OUT_BUF_SIZE = 512 }; + + output_stream *m_pStream; + params m_params; + uint8 m_num_components; + uint8 m_comp_h_samp[3], m_comp_v_samp[3]; + int m_image_x, m_image_y, m_image_bpp, m_image_bpl; + int m_image_x_mcu, m_image_y_mcu; + int m_image_bpl_xlt, m_image_bpl_mcu; + int m_mcus_per_row; + int m_mcu_x, m_mcu_y; + uint8 *m_mcu_lines[16]; + uint8 m_mcu_y_ofs; + sample_array_t m_sample_array[64]; + int16 m_coefficient_array[64]; + + int m_last_dc_val[3]; + uint8 m_out_buf[JPGE_OUT_BUF_SIZE]; + uint8 *m_pOut_buf; + uint m_out_buf_left; + uint32 m_bit_buffer; + uint m_bits_in; + uint8 m_pass_num; + bool m_all_stream_writes_succeeded; + + bool jpg_open(int p_x_res, int p_y_res, int src_channels); + + void flush_output_buffer(); + void put_bits(uint bits, uint len); + + void emit_byte(uint8 i); + void emit_word(uint i); + void emit_marker(int marker); + + void emit_jfif_app0(); + void emit_dqt(); + void emit_sof(); + void emit_dht(uint8 *bits, uint8 *val, int index, bool ac_flag); + void emit_dhts(); + void emit_sos(); + + void compute_quant_table(int32 *dst, const int16 *src); + void load_quantized_coefficients(int component_num); + + void load_block_8_8_grey(int x); + void load_block_8_8(int x, int y, int c); + void load_block_16_8(int x, int c); + void load_block_16_8_8(int x, int c); + + void code_coefficients_pass_two(int component_num); + void code_block(int component_num); + + void process_mcu_row(); + bool process_end_of_image(); + void load_mcu(const void* src); + void clear(); + void init(); + }; + +} // namespace jpge + +#endif // JPEG_ENCODER diff --git a/esp32-cam-rtos/ov2640.c b/esp32-cam-rtos/ov2640.c new file mode 100644 index 0000000..811023c --- /dev/null +++ b/esp32-cam-rtos/ov2640.c @@ -0,0 +1,580 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * OV2640 driver. + * + */ +#include +#include +#include +#include "sccb.h" +#include "ov2640.h" +#include "ov2640_regs.h" +#include "ov2640_settings.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#else +#include "esp_log.h" +static const char* TAG = "ov2640"; +#endif + +static volatile ov2640_bank_t reg_bank = BANK_MAX; +static int set_bank(sensor_t *sensor, ov2640_bank_t bank) +{ + int res = 0; + if (bank != reg_bank) { + reg_bank = bank; + res = SCCB_Write(sensor->slv_addr, BANK_SEL, bank); + } + return res; +} + +static int write_regs(sensor_t *sensor, const uint8_t (*regs)[2]) +{ + int i=0, res = 0; + while (regs[i][0]) { + if (regs[i][0] == BANK_SEL) { + res = set_bank(sensor, regs[i][1]); + } else { + res = SCCB_Write(sensor->slv_addr, regs[i][0], regs[i][1]); + } + if (res) { + return res; + } + i++; + } + return res; +} + +static int write_reg(sensor_t *sensor, ov2640_bank_t bank, uint8_t reg, uint8_t value) +{ + int ret = set_bank(sensor, bank); + if(!ret) { + ret = SCCB_Write(sensor->slv_addr, reg, value); + } + return ret; +} + +static int set_reg_bits(sensor_t *sensor, uint8_t bank, uint8_t reg, uint8_t offset, uint8_t mask, uint8_t value) +{ + int ret = 0; + uint8_t c_value, new_value; + + ret = set_bank(sensor, bank); + if(ret) { + return ret; + } + c_value = SCCB_Read(sensor->slv_addr, reg); + new_value = (c_value & ~(mask << offset)) | ((value & mask) << offset); + ret = SCCB_Write(sensor->slv_addr, reg, new_value); + return ret; +} + +static int read_reg(sensor_t *sensor, ov2640_bank_t bank, uint8_t reg) +{ + if(set_bank(sensor, bank)){ + return 0; + } + return SCCB_Read(sensor->slv_addr, reg); +} + +static uint8_t get_reg_bits(sensor_t *sensor, uint8_t bank, uint8_t reg, uint8_t offset, uint8_t mask) +{ + return (read_reg(sensor, bank, reg) >> offset) & mask; +} + +static int write_reg_bits(sensor_t *sensor, uint8_t bank, uint8_t reg, uint8_t mask, int enable) +{ + return set_reg_bits(sensor, bank, reg, 0, mask, enable?mask:0); +} + +#define WRITE_REGS_OR_RETURN(regs) ret = write_regs(sensor, regs); if(ret){return ret;} +#define WRITE_REG_OR_RETURN(bank, reg, val) ret = write_reg(sensor, bank, reg, val); if(ret){return ret;} +#define SET_REG_BITS_OR_RETURN(bank, reg, offset, mask, val) ret = set_reg_bits(sensor, bank, reg, offset, mask, val); if(ret){return ret;} + +static int reset(sensor_t *sensor) +{ + int ret = 0; + WRITE_REG_OR_RETURN(BANK_SENSOR, COM7, COM7_SRST); + vTaskDelay(10 / portTICK_PERIOD_MS); + WRITE_REGS_OR_RETURN(ov2640_settings_cif); + return ret; +} + +static int set_pixformat(sensor_t *sensor, pixformat_t pixformat) +{ + int ret = 0; + sensor->pixformat = pixformat; + switch (pixformat) { + case PIXFORMAT_RGB565: + case PIXFORMAT_RGB888: + WRITE_REGS_OR_RETURN(ov2640_settings_rgb565); + break; + case PIXFORMAT_YUV422: + case PIXFORMAT_GRAYSCALE: + WRITE_REGS_OR_RETURN(ov2640_settings_yuv422); + break; + case PIXFORMAT_JPEG: + WRITE_REGS_OR_RETURN(ov2640_settings_jpeg3); + break; + default: + ret = -1; + break; + } + if(!ret) { + vTaskDelay(10 / portTICK_PERIOD_MS); + } + + return ret; +} + +static int set_window(sensor_t *sensor, ov2640_sensor_mode_t mode, int offset_x, int offset_y, int max_x, int max_y, int w, int h){ + int ret = 0; + const uint8_t (*regs)[2]; + ov2640_clk_t c; + c.reserved = 0; + + max_x /= 4; + max_y /= 4; + w /= 4; + h /= 4; + uint8_t win_regs[][2] = { + {BANK_SEL, BANK_DSP}, + {HSIZE, max_x & 0xFF}, + {VSIZE, max_y & 0xFF}, + {XOFFL, offset_x & 0xFF}, + {YOFFL, offset_y & 0xFF}, + {VHYX, ((max_y >> 1) & 0X80) | ((offset_y >> 4) & 0X70) | ((max_x >> 5) & 0X08) | ((offset_y >> 8) & 0X07)}, + {TEST, (max_x >> 2) & 0X80}, + {ZMOW, (w)&0xFF}, + {ZMOH, (h)&0xFF}, + {ZMHH, ((h>>6)&0x04)|((w>>8)&0x03)}, + {0, 0} + }; + + c.pclk_auto = 0; + c.pclk_div = 8; + c.clk_2x = 0; + c.clk_div = 0; + + if(sensor->pixformat != PIXFORMAT_JPEG){ + c.pclk_auto = 1; + c.clk_div = 7; + } + + if (mode == OV2640_MODE_CIF) { + regs = ov2640_settings_to_cif; + if(sensor->pixformat != PIXFORMAT_JPEG){ + c.clk_div = 3; + } + } else if (mode == OV2640_MODE_SVGA) { + regs = ov2640_settings_to_svga; + } else { + regs = ov2640_settings_to_uxga; + c.pclk_div = 12; + } + + WRITE_REG_OR_RETURN(BANK_DSP, R_BYPASS, R_BYPASS_DSP_BYPAS); + WRITE_REGS_OR_RETURN(regs); + WRITE_REGS_OR_RETURN(win_regs); + WRITE_REG_OR_RETURN(BANK_SENSOR, CLKRC, c.clk); + WRITE_REG_OR_RETURN(BANK_DSP, R_DVP_SP, c.pclk); + WRITE_REG_OR_RETURN(BANK_DSP, R_BYPASS, R_BYPASS_DSP_EN); + + vTaskDelay(10 / portTICK_PERIOD_MS); + //required when changing resolution + set_pixformat(sensor, sensor->pixformat); + + return ret; +} + +static int set_framesize(sensor_t *sensor, framesize_t framesize) +{ + int ret = 0; + uint16_t w = resolution[framesize].width; + uint16_t h = resolution[framesize].height; + aspect_ratio_t ratio = resolution[framesize].aspect_ratio; + uint16_t max_x = ratio_table[ratio].max_x; + uint16_t max_y = ratio_table[ratio].max_y; + uint16_t offset_x = ratio_table[ratio].offset_x; + uint16_t offset_y = ratio_table[ratio].offset_y; + ov2640_sensor_mode_t mode = OV2640_MODE_UXGA; + + sensor->status.framesize = framesize; + + + + if (framesize <= FRAMESIZE_CIF) { + mode = OV2640_MODE_CIF; + max_x /= 4; + max_y /= 4; + offset_x /= 4; + offset_y /= 4; + if(max_y > 296){ + max_y = 296; + } + } else if (framesize <= FRAMESIZE_SVGA) { + mode = OV2640_MODE_SVGA; + max_x /= 2; + max_y /= 2; + offset_x /= 2; + offset_y /= 2; + } + + ret = set_window(sensor, mode, offset_x, offset_y, max_x, max_y, w, h); + return ret; +} + +static int set_contrast(sensor_t *sensor, int level) +{ + int ret=0; + level += 3; + if (level <= 0 || level > NUM_CONTRAST_LEVELS) { + return -1; + } + sensor->status.contrast = level-3; + for (int i=0; i<7; i++) { + WRITE_REG_OR_RETURN(BANK_DSP, contrast_regs[0][i], contrast_regs[level][i]); + } + return ret; +} + +static int set_brightness(sensor_t *sensor, int level) +{ + int ret=0; + level += 3; + if (level <= 0 || level > NUM_BRIGHTNESS_LEVELS) { + return -1; + } + sensor->status.brightness = level-3; + for (int i=0; i<5; i++) { + WRITE_REG_OR_RETURN(BANK_DSP, brightness_regs[0][i], brightness_regs[level][i]); + } + return ret; +} + +static int set_saturation(sensor_t *sensor, int level) +{ + int ret=0; + level += 3; + if (level <= 0 || level > NUM_SATURATION_LEVELS) { + return -1; + } + sensor->status.saturation = level-3; + for (int i=0; i<5; i++) { + WRITE_REG_OR_RETURN(BANK_DSP, saturation_regs[0][i], saturation_regs[level][i]); + } + return ret; +} + +static int set_special_effect(sensor_t *sensor, int effect) +{ + int ret=0; + effect++; + if (effect <= 0 || effect > NUM_SPECIAL_EFFECTS) { + return -1; + } + sensor->status.special_effect = effect-1; + for (int i=0; i<5; i++) { + WRITE_REG_OR_RETURN(BANK_DSP, special_effects_regs[0][i], special_effects_regs[effect][i]); + } + return ret; +} + +static int set_wb_mode(sensor_t *sensor, int mode) +{ + int ret=0; + if (mode < 0 || mode > NUM_WB_MODES) { + return -1; + } + sensor->status.wb_mode = mode; + SET_REG_BITS_OR_RETURN(BANK_DSP, 0XC7, 6, 1, mode?1:0); + if(mode) { + for (int i=0; i<3; i++) { + WRITE_REG_OR_RETURN(BANK_DSP, wb_modes_regs[0][i], wb_modes_regs[mode][i]); + } + } + return ret; +} + +static int set_ae_level(sensor_t *sensor, int level) +{ + int ret=0; + level += 3; + if (level <= 0 || level > NUM_AE_LEVELS) { + return -1; + } + sensor->status.ae_level = level-3; + for (int i=0; i<3; i++) { + WRITE_REG_OR_RETURN(BANK_SENSOR, ae_levels_regs[0][i], ae_levels_regs[level][i]); + } + return ret; +} + +static int set_quality(sensor_t *sensor, int quality) +{ + if(quality < 0) { + quality = 0; + } else if(quality > 63) { + quality = 63; + } + sensor->status.quality = quality; + return write_reg(sensor, BANK_DSP, QS, quality); +} + +static int set_agc_gain(sensor_t *sensor, int gain) +{ + if(gain < 0) { + gain = 0; + } else if(gain > 30) { + gain = 30; + } + sensor->status.agc_gain = gain; + return write_reg(sensor, BANK_SENSOR, GAIN, agc_gain_tbl[gain]); +} + +static int set_gainceiling_sensor(sensor_t *sensor, gainceiling_t gainceiling) +{ + sensor->status.gainceiling = gainceiling; + //return write_reg(sensor, BANK_SENSOR, COM9, COM9_AGC_SET(gainceiling)); + return set_reg_bits(sensor, BANK_SENSOR, COM9, 5, 7, gainceiling); +} + +static int set_aec_value(sensor_t *sensor, int value) +{ + if(value < 0) { + value = 0; + } else if(value > 1200) { + value = 1200; + } + sensor->status.aec_value = value; + return set_reg_bits(sensor, BANK_SENSOR, REG04, 0, 3, value & 0x3) + || write_reg(sensor, BANK_SENSOR, AEC, (value >> 2) & 0xFF) + || set_reg_bits(sensor, BANK_SENSOR, REG45, 0, 0x3F, value >> 10); +} + +static int set_aec2(sensor_t *sensor, int enable) +{ + sensor->status.aec2 = enable; + return set_reg_bits(sensor, BANK_DSP, CTRL0, 6, 1, enable?0:1); +} + +static int set_colorbar(sensor_t *sensor, int enable) +{ + sensor->status.colorbar = enable; + return write_reg_bits(sensor, BANK_SENSOR, COM7, COM7_COLOR_BAR, enable?1:0); +} + +static int set_agc_sensor(sensor_t *sensor, int enable) +{ + sensor->status.agc = enable; + return write_reg_bits(sensor, BANK_SENSOR, COM8, COM8_AGC_EN, enable?1:0); +} + +static int set_aec_sensor(sensor_t *sensor, int enable) +{ + sensor->status.aec = enable; + return write_reg_bits(sensor, BANK_SENSOR, COM8, COM8_AEC_EN, enable?1:0); +} + +static int set_hmirror_sensor(sensor_t *sensor, int enable) +{ + sensor->status.hmirror = enable; + return write_reg_bits(sensor, BANK_SENSOR, REG04, REG04_HFLIP_IMG, enable?1:0); +} + +static int set_vflip_sensor(sensor_t *sensor, int enable) +{ + int ret = 0; + sensor->status.vflip = enable; + ret = write_reg_bits(sensor, BANK_SENSOR, REG04, REG04_VREF_EN, enable?1:0); + return ret & write_reg_bits(sensor, BANK_SENSOR, REG04, REG04_VFLIP_IMG, enable?1:0); +} + +static int set_raw_gma_dsp(sensor_t *sensor, int enable) +{ + sensor->status.raw_gma = enable; + return set_reg_bits(sensor, BANK_DSP, CTRL1, 5, 1, enable?1:0); +} + +static int set_awb_dsp(sensor_t *sensor, int enable) +{ + sensor->status.awb = enable; + return set_reg_bits(sensor, BANK_DSP, CTRL1, 3, 1, enable?1:0); +} + +static int set_awb_gain_dsp(sensor_t *sensor, int enable) +{ + sensor->status.awb_gain = enable; + return set_reg_bits(sensor, BANK_DSP, CTRL1, 2, 1, enable?1:0); +} + +static int set_lenc_dsp(sensor_t *sensor, int enable) +{ + sensor->status.lenc = enable; + return set_reg_bits(sensor, BANK_DSP, CTRL1, 1, 1, enable?1:0); +} + +static int set_dcw_dsp(sensor_t *sensor, int enable) +{ + sensor->status.dcw = enable; + return set_reg_bits(sensor, BANK_DSP, CTRL2, 5, 1, enable?1:0); +} + +static int set_bpc_dsp(sensor_t *sensor, int enable) +{ + sensor->status.bpc = enable; + return set_reg_bits(sensor, BANK_DSP, CTRL3, 7, 1, enable?1:0); +} + +static int set_wpc_dsp(sensor_t *sensor, int enable) +{ + sensor->status.wpc = enable; + return set_reg_bits(sensor, BANK_DSP, CTRL3, 6, 1, enable?1:0); +} + +//unsupported +static int set_sharpness(sensor_t *sensor, int level) +{ + return -1; +} + +static int set_denoise(sensor_t *sensor, int level) +{ + return -1; +} + +static int get_reg(sensor_t *sensor, int reg, int mask) +{ + int ret = read_reg(sensor, (reg >> 8) & 0x01, reg & 0xFF); + if(ret > 0){ + ret &= mask; + } + return ret; +} + +static int set_reg(sensor_t *sensor, int reg, int mask, int value) +{ + int ret = 0; + ret = read_reg(sensor, (reg >> 8) & 0x01, reg & 0xFF); + if(ret < 0){ + return ret; + } + value = (ret & ~mask) | (value & mask); + ret = write_reg(sensor, (reg >> 8) & 0x01, reg & 0xFF, value); + return ret; +} + +static int set_res_raw(sensor_t *sensor, int startX, int startY, int endX, int endY, int offsetX, int offsetY, int totalX, int totalY, int outputX, int outputY, bool scale, bool binning) +{ + return set_window(sensor, (ov2640_sensor_mode_t)startX, offsetX, offsetY, totalX, totalY, outputX, outputY); +} + +static int _set_pll(sensor_t *sensor, int bypass, int multiplier, int sys_div, int root_2x, int pre_div, int seld5, int pclk_manual, int pclk_div) +{ + return -1; +} + +esp_err_t xclk_timer_conf(int ledc_timer, int xclk_freq_hz); +static int set_xclk(sensor_t *sensor, int timer, int xclk) +{ + int ret = 0; + sensor->xclk_freq_hz = xclk * 1000000U; + ret = xclk_timer_conf(timer, sensor->xclk_freq_hz); + return ret; +} + +static int init_status(sensor_t *sensor){ + sensor->status.brightness = 0; + sensor->status.contrast = 0; + sensor->status.saturation = 0; + sensor->status.ae_level = 0; + sensor->status.special_effect = 0; + sensor->status.wb_mode = 0; + + sensor->status.agc_gain = 30; + int agc_gain = read_reg(sensor, BANK_SENSOR, GAIN); + for (int i=0; i<30; i++){ + if(agc_gain >= agc_gain_tbl[i] && agc_gain < agc_gain_tbl[i+1]){ + sensor->status.agc_gain = i; + break; + } + } + + sensor->status.aec_value = ((uint16_t)get_reg_bits(sensor, BANK_SENSOR, REG45, 0, 0x3F) << 10) + | ((uint16_t)read_reg(sensor, BANK_SENSOR, AEC) << 2) + | get_reg_bits(sensor, BANK_SENSOR, REG04, 0, 3);//0 - 1200 + sensor->status.quality = read_reg(sensor, BANK_DSP, QS); + sensor->status.gainceiling = get_reg_bits(sensor, BANK_SENSOR, COM9, 5, 7); + + sensor->status.awb = get_reg_bits(sensor, BANK_DSP, CTRL1, 3, 1); + sensor->status.awb_gain = get_reg_bits(sensor, BANK_DSP, CTRL1, 2, 1); + sensor->status.aec = get_reg_bits(sensor, BANK_SENSOR, COM8, 0, 1); + sensor->status.aec2 = get_reg_bits(sensor, BANK_DSP, CTRL0, 6, 1); + sensor->status.agc = get_reg_bits(sensor, BANK_SENSOR, COM8, 2, 1); + sensor->status.bpc = get_reg_bits(sensor, BANK_DSP, CTRL3, 7, 1); + sensor->status.wpc = get_reg_bits(sensor, BANK_DSP, CTRL3, 6, 1); + sensor->status.raw_gma = get_reg_bits(sensor, BANK_DSP, CTRL1, 5, 1); + sensor->status.lenc = get_reg_bits(sensor, BANK_DSP, CTRL1, 1, 1); + sensor->status.hmirror = get_reg_bits(sensor, BANK_SENSOR, REG04, 7, 1); + sensor->status.vflip = get_reg_bits(sensor, BANK_SENSOR, REG04, 6, 1); + sensor->status.dcw = get_reg_bits(sensor, BANK_DSP, CTRL2, 5, 1); + sensor->status.colorbar = get_reg_bits(sensor, BANK_SENSOR, COM7, 1, 1); + + sensor->status.sharpness = 0;//not supported + sensor->status.denoise = 0; + return 0; +} + +int ov2640_init(sensor_t *sensor) +{ + sensor->reset = reset; + sensor->init_status = init_status; + sensor->set_pixformat = set_pixformat; + sensor->set_framesize = set_framesize; + sensor->set_contrast = set_contrast; + sensor->set_brightness= set_brightness; + sensor->set_saturation= set_saturation; + + sensor->set_quality = set_quality; + sensor->set_colorbar = set_colorbar; + + sensor->set_gainceiling = set_gainceiling_sensor; + sensor->set_gain_ctrl = set_agc_sensor; + sensor->set_exposure_ctrl = set_aec_sensor; + sensor->set_hmirror = set_hmirror_sensor; + sensor->set_vflip = set_vflip_sensor; + + sensor->set_whitebal = set_awb_dsp; + sensor->set_aec2 = set_aec2; + sensor->set_aec_value = set_aec_value; + sensor->set_special_effect = set_special_effect; + sensor->set_wb_mode = set_wb_mode; + sensor->set_ae_level = set_ae_level; + + sensor->set_dcw = set_dcw_dsp; + sensor->set_bpc = set_bpc_dsp; + sensor->set_wpc = set_wpc_dsp; + sensor->set_awb_gain = set_awb_gain_dsp; + sensor->set_agc_gain = set_agc_gain; + + sensor->set_raw_gma = set_raw_gma_dsp; + sensor->set_lenc = set_lenc_dsp; + + //not supported + sensor->set_sharpness = set_sharpness; + sensor->set_denoise = set_denoise; + + sensor->get_reg = get_reg; + sensor->set_reg = set_reg; + sensor->set_res_raw = set_res_raw; + sensor->set_pll = _set_pll; + sensor->set_xclk = set_xclk; + ESP_LOGD(TAG, "OV2640 Attached"); + return 0; +} diff --git a/esp32-cam-rtos/ov2640.h b/esp32-cam-rtos/ov2640.h new file mode 100644 index 0000000..a890499 --- /dev/null +++ b/esp32-cam-rtos/ov2640.h @@ -0,0 +1,13 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * OV2640 driver. + * + */ +#ifndef __OV2640_H__ +#define __OV2640_H__ +#include "sensor.h" +int ov2640_init(sensor_t *sensor); +#endif // __OV2640_H__ diff --git a/esp32-cam-rtos/ov2640_regs.h b/esp32-cam-rtos/ov2640_regs.h new file mode 100644 index 0000000..eb096b4 --- /dev/null +++ b/esp32-cam-rtos/ov2640_regs.h @@ -0,0 +1,216 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * OV2640 register definitions. + */ +#ifndef __REG_REGS_H__ +#define __REG_REGS_H__ +/* DSP register bank FF=0x00*/ +#define R_BYPASS 0x05 +#define QS 0x44 +#define CTRLI 0x50 +#define HSIZE 0x51 +#define VSIZE 0x52 +#define XOFFL 0x53 +#define YOFFL 0x54 +#define VHYX 0x55 +#define DPRP 0x56 +#define TEST 0x57 +#define ZMOW 0x5A +#define ZMOH 0x5B +#define ZMHH 0x5C +#define BPADDR 0x7C +#define BPDATA 0x7D +#define CTRL2 0x86 +#define CTRL3 0x87 +#define SIZEL 0x8C +#define HSIZE8 0xC0 +#define VSIZE8 0xC1 +#define CTRL0 0xC2 +#define CTRL1 0xC3 +#define R_DVP_SP 0xD3 +#define IMAGE_MODE 0xDA +#define RESET 0xE0 +#define MS_SP 0xF0 +#define SS_ID 0xF7 +#define SS_CTRL 0xF7 +#define MC_BIST 0xF9 +#define MC_AL 0xFA +#define MC_AH 0xFB +#define MC_D 0xFC +#define P_CMD 0xFD +#define P_STATUS 0xFE +#define BANK_SEL 0xFF + +#define CTRLI_LP_DP 0x80 +#define CTRLI_ROUND 0x40 + +#define CTRL0_AEC_EN 0x80 +#define CTRL0_AEC_SEL 0x40 +#define CTRL0_STAT_SEL 0x20 +#define CTRL0_VFIRST 0x10 +#define CTRL0_YUV422 0x08 +#define CTRL0_YUV_EN 0x04 +#define CTRL0_RGB_EN 0x02 +#define CTRL0_RAW_EN 0x01 + +#define CTRL2_DCW_EN 0x20 +#define CTRL2_SDE_EN 0x10 +#define CTRL2_UV_ADJ_EN 0x08 +#define CTRL2_UV_AVG_EN 0x04 +#define CTRL2_CMX_EN 0x01 + +#define CTRL3_BPC_EN 0x80 +#define CTRL3_WPC_EN 0x40 + +#define R_DVP_SP_AUTO_MODE 0x80 + +#define R_BYPASS_DSP_EN 0x00 +#define R_BYPASS_DSP_BYPAS 0x01 + +#define IMAGE_MODE_Y8_DVP_EN 0x40 +#define IMAGE_MODE_JPEG_EN 0x10 +#define IMAGE_MODE_YUV422 0x00 +#define IMAGE_MODE_RAW10 0x04 +#define IMAGE_MODE_RGB565 0x08 +#define IMAGE_MODE_HREF_VSYNC 0x02 +#define IMAGE_MODE_LBYTE_FIRST 0x01 + +#define RESET_MICROC 0x40 +#define RESET_SCCB 0x20 +#define RESET_JPEG 0x10 +#define RESET_DVP 0x04 +#define RESET_IPU 0x02 +#define RESET_CIF 0x01 + +#define MC_BIST_RESET 0x80 +#define MC_BIST_BOOT_ROM_SEL 0x40 +#define MC_BIST_12KB_SEL 0x20 +#define MC_BIST_12KB_MASK 0x30 +#define MC_BIST_512KB_SEL 0x08 +#define MC_BIST_512KB_MASK 0x0C +#define MC_BIST_BUSY_BIT_R 0x02 +#define MC_BIST_MC_RES_ONE_SH_W 0x02 +#define MC_BIST_LAUNCH 0x01 + + +typedef enum { + BANK_DSP, BANK_SENSOR, BANK_MAX +} ov2640_bank_t; + +/* Sensor register bank FF=0x01*/ +#define GAIN 0x00 +#define COM1 0x03 +#define REG04 0x04 +#define REG08 0x08 +#define COM2 0x09 +#define REG_PID 0x0A +#define REG_VER 0x0B +#define COM3 0x0C +#define COM4 0x0D +#define AEC 0x10 +#define CLKRC 0x11 +#define COM7 0x12 +#define COM8 0x13 +#define COM9 0x14 /* AGC gain ceiling */ +#define COM10 0x15 +#define HSTART 0x17 +#define HSTOP 0x18 +#define VSTART 0x19 +#define VSTOP 0x1A +#define MIDH 0x1C +#define MIDL 0x1D +#define AEW 0x24 +#define AEB 0x25 +#define VV 0x26 +#define REG2A 0x2A +#define FRARL 0x2B +#define ADDVSL 0x2D +#define ADDVSH 0x2E +#define YAVG 0x2F +#define HSDY 0x30 +#define HEDY 0x31 +#define REG32 0x32 +#define ARCOM2 0x34 +#define REG45 0x45 +#define FLL 0x46 +#define FLH 0x47 +#define COM19 0x48 +#define ZOOMS 0x49 +#define COM22 0x4B +#define COM25 0x4E +#define BD50 0x4F +#define BD60 0x50 +#define REG5D 0x5D +#define REG5E 0x5E +#define REG5F 0x5F +#define REG60 0x60 +#define HISTO_LOW 0x61 +#define HISTO_HIGH 0x62 + +#define REG04_DEFAULT 0x28 +#define REG04_HFLIP_IMG 0x80 +#define REG04_VFLIP_IMG 0x40 +#define REG04_VREF_EN 0x10 +#define REG04_HREF_EN 0x08 +#define REG04_SET(x) (REG04_DEFAULT|x) + +#define COM2_STDBY 0x10 +#define COM2_OUT_DRIVE_1x 0x00 +#define COM2_OUT_DRIVE_2x 0x01 +#define COM2_OUT_DRIVE_3x 0x02 +#define COM2_OUT_DRIVE_4x 0x03 + +#define COM3_DEFAULT 0x38 +#define COM3_BAND_50Hz 0x04 +#define COM3_BAND_60Hz 0x00 +#define COM3_BAND_AUTO 0x02 +#define COM3_BAND_SET(x) (COM3_DEFAULT|x) + +#define COM7_SRST 0x80 +#define COM7_RES_UXGA 0x00 /* UXGA */ +#define COM7_RES_SVGA 0x40 /* SVGA */ +#define COM7_RES_CIF 0x20 /* CIF */ +#define COM7_ZOOM_EN 0x04 /* Enable Zoom */ +#define COM7_COLOR_BAR 0x02 /* Enable Color Bar Test */ + +#define COM8_DEFAULT 0xC0 +#define COM8_BNDF_EN 0x20 /* Enable Banding filter */ +#define COM8_AGC_EN 0x04 /* AGC Auto/Manual control selection */ +#define COM8_AEC_EN 0x01 /* Auto/Manual Exposure control */ +#define COM8_SET(x) (COM8_DEFAULT|x) + +#define COM9_DEFAULT 0x08 +#define COM9_AGC_GAIN_2x 0x00 /* AGC: 2x */ +#define COM9_AGC_GAIN_4x 0x01 /* AGC: 4x */ +#define COM9_AGC_GAIN_8x 0x02 /* AGC: 8x */ +#define COM9_AGC_GAIN_16x 0x03 /* AGC: 16x */ +#define COM9_AGC_GAIN_32x 0x04 /* AGC: 32x */ +#define COM9_AGC_GAIN_64x 0x05 /* AGC: 64x */ +#define COM9_AGC_GAIN_128x 0x06 /* AGC: 128x */ +#define COM9_AGC_SET(x) (COM9_DEFAULT|(x<<5)) + +#define COM10_HREF_EN 0x80 /* HSYNC changes to HREF */ +#define COM10_HSYNC_EN 0x40 /* HREF changes to HSYNC */ +#define COM10_PCLK_FREE 0x20 /* PCLK output option: free running PCLK */ +#define COM10_PCLK_EDGE 0x10 /* Data is updated at the rising edge of PCLK */ +#define COM10_HREF_NEG 0x08 /* HREF negative */ +#define COM10_VSYNC_NEG 0x02 /* VSYNC negative */ +#define COM10_HSYNC_NEG 0x01 /* HSYNC negative */ + +#define CTRL1_AWB 0x08 /* Enable AWB */ + +#define VV_AGC_TH_SET(h,l) ((h<<4)|(l&0x0F)) + +#define REG32_UXGA 0x36 +#define REG32_SVGA 0x09 +#define REG32_CIF 0x89 + +#define CLKRC_2X 0x80 +#define CLKRC_2X_UXGA (0x01 | CLKRC_2X) +#define CLKRC_2X_SVGA CLKRC_2X +#define CLKRC_2X_CIF CLKRC_2X + +#endif //__REG_REGS_H__ diff --git a/esp32-cam-rtos/ov2640_settings.h b/esp32-cam-rtos/ov2640_settings.h new file mode 100644 index 0000000..f151f0a --- /dev/null +++ b/esp32-cam-rtos/ov2640_settings.h @@ -0,0 +1,485 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef _OV2640_SETTINGS_H_ +#define _OV2640_SETTINGS_H_ + +#include +#include +#include "esp_attr.h" +#include "ov2640_regs.h" + +typedef enum { + OV2640_MODE_UXGA, OV2640_MODE_SVGA, OV2640_MODE_CIF, OV2640_MODE_MAX +} ov2640_sensor_mode_t; + +typedef struct { + union { + struct { + uint8_t pclk_div:7; + uint8_t pclk_auto:1; + }; + uint8_t pclk; + }; + union { + struct { + uint8_t clk_div:6; + uint8_t reserved:1; + uint8_t clk_2x:1; + }; + uint8_t clk; + }; +} ov2640_clk_t; + +typedef struct { + uint16_t offset_x; + uint16_t offset_y; + uint16_t max_x; + uint16_t max_y; +} ov2640_ratio_settings_t; + +static const DRAM_ATTR ov2640_ratio_settings_t ratio_table[] = { + // ox, oy, mx, my + { 0, 0, 1600, 1200 }, //4x3 + { 8, 72, 1584, 1056 }, //3x2 + { 0, 100, 1600, 1000 }, //16x10 + { 0, 120, 1600, 960 }, //5x3 + { 0, 150, 1600, 900 }, //16x9 + { 2, 258, 1596, 684 }, //21x9 + { 50, 0, 1500, 1200 }, //5x4 + { 200, 0, 1200, 1200 }, //1x1 + { 462, 0, 676, 1200 } //9x16 +}; + +// 30fps@24MHz +const DRAM_ATTR uint8_t ov2640_settings_cif[][2] = { + {BANK_SEL, BANK_DSP}, + {0x2c, 0xff}, + {0x2e, 0xdf}, + {BANK_SEL, BANK_SENSOR}, + {0x3c, 0x32}, + {CLKRC, 0x01}, + {COM2, COM2_OUT_DRIVE_3x}, + {REG04, REG04_DEFAULT}, + {COM8, COM8_DEFAULT | COM8_BNDF_EN | COM8_AGC_EN | COM8_AEC_EN}, + {COM9, COM9_AGC_SET(COM9_AGC_GAIN_8x)}, + {0x2c, 0x0c}, + {0x33, 0x78}, + {0x3a, 0x33}, + {0x3b, 0xfB}, + {0x3e, 0x00}, + {0x43, 0x11}, + {0x16, 0x10}, + {0x39, 0x92}, + {0x35, 0xda}, + {0x22, 0x1a}, + {0x37, 0xc3}, + {0x23, 0x00}, + {ARCOM2, 0xc0}, + {0x06, 0x88}, + {0x07, 0xc0}, + {COM4, 0x87}, + {0x0e, 0x41}, + {0x4c, 0x00}, + {0x4a, 0x81}, + {0x21, 0x99}, + {AEW, 0x40}, + {AEB, 0x38}, + {VV, VV_AGC_TH_SET(8,2)}, + {0x5c, 0x00}, + {0x63, 0x00}, + {HISTO_LOW, 0x70}, + {HISTO_HIGH, 0x80}, + {0x7c, 0x05}, + {0x20, 0x80}, + {0x28, 0x30}, + {0x6c, 0x00}, + {0x6d, 0x80}, + {0x6e, 0x00}, + {0x70, 0x02}, + {0x71, 0x94}, + {0x73, 0xc1}, + {0x3d, 0x34}, + {0x5a, 0x57}, + {BD50, 0xbb}, + {BD60, 0x9c}, + {COM7, COM7_RES_CIF}, + {HSTART, 0x11}, + {HSTOP, 0x43}, + {VSTART, 0x00}, + {VSTOP, 0x25}, + {REG32, 0x89}, + {0x37, 0xc0}, + {BD50, 0xca}, + {BD60, 0xa8}, + {0x6d, 0x00}, + {0x3d, 0x38}, + {BANK_SEL, BANK_DSP}, + {0xe5, 0x7f}, + {MC_BIST, MC_BIST_RESET | MC_BIST_BOOT_ROM_SEL}, + {0x41, 0x24}, + {RESET, RESET_JPEG | RESET_DVP}, + {0x76, 0xff}, + {0x33, 0xa0}, + {0x42, 0x20}, + {0x43, 0x18}, + {0x4c, 0x00}, + {CTRL3, CTRL3_WPC_EN | 0x10 }, + {0x88, 0x3f}, + {0xd7, 0x03}, + {0xd9, 0x10}, + {R_DVP_SP, R_DVP_SP_AUTO_MODE | 0x02}, + {0xc8, 0x08}, + {0xc9, 0x80}, + {BPADDR, 0x00}, + {BPDATA, 0x00}, + {BPADDR, 0x03}, + {BPDATA, 0x48}, + {BPDATA, 0x48}, + {BPADDR, 0x08}, + {BPDATA, 0x20}, + {BPDATA, 0x10}, + {BPDATA, 0x0e}, + {0x90, 0x00}, + {0x91, 0x0e}, + {0x91, 0x1a}, + {0x91, 0x31}, + {0x91, 0x5a}, + {0x91, 0x69}, + {0x91, 0x75}, + {0x91, 0x7e}, + {0x91, 0x88}, + {0x91, 0x8f}, + {0x91, 0x96}, + {0x91, 0xa3}, + {0x91, 0xaf}, + {0x91, 0xc4}, + {0x91, 0xd7}, + {0x91, 0xe8}, + {0x91, 0x20}, + {0x92, 0x00}, + {0x93, 0x06}, + {0x93, 0xe3}, + {0x93, 0x05}, + {0x93, 0x05}, + {0x93, 0x00}, + {0x93, 0x04}, + {0x93, 0x00}, + {0x93, 0x00}, + {0x93, 0x00}, + {0x93, 0x00}, + {0x93, 0x00}, + {0x93, 0x00}, + {0x93, 0x00}, + {0x96, 0x00}, + {0x97, 0x08}, + {0x97, 0x19}, + {0x97, 0x02}, + {0x97, 0x0c}, + {0x97, 0x24}, + {0x97, 0x30}, + {0x97, 0x28}, + {0x97, 0x26}, + {0x97, 0x02}, + {0x97, 0x98}, + {0x97, 0x80}, + {0x97, 0x00}, + {0x97, 0x00}, + {0xa4, 0x00}, + {0xa8, 0x00}, + {0xc5, 0x11}, + {0xc6, 0x51}, + {0xbf, 0x80}, + {0xc7, 0x10}, + {0xb6, 0x66}, + {0xb8, 0xA5}, + {0xb7, 0x64}, + {0xb9, 0x7C}, + {0xb3, 0xaf}, + {0xb4, 0x97}, + {0xb5, 0xFF}, + {0xb0, 0xC5}, + {0xb1, 0x94}, + {0xb2, 0x0f}, + {0xc4, 0x5c}, + {CTRL1, 0xfd}, + {0x7f, 0x00}, + {0xe5, 0x1f}, + {0xe1, 0x67}, + {0xdd, 0x7f}, + {IMAGE_MODE, 0x00}, + {RESET, 0x00}, + {R_BYPASS, R_BYPASS_DSP_EN}, + {0, 0} +}; + +const DRAM_ATTR uint8_t ov2640_settings_to_cif[][2] = { + {BANK_SEL, BANK_SENSOR}, + {COM7, COM7_RES_CIF}, + + //Set the sensor output window + {COM1, 0x0A}, + {REG32, REG32_CIF}, + {HSTART, 0x11}, + {HSTOP, 0x43}, + {VSTART, 0x00}, + {VSTOP, 0x25}, + + //{CLKRC, 0x00}, + {BD50, 0xca}, + {BD60, 0xa8}, + {0x5a, 0x23}, + {0x6d, 0x00}, + {0x3d, 0x38}, + {0x39, 0x92}, + {0x35, 0xda}, + {0x22, 0x1a}, + {0x37, 0xc3}, + {0x23, 0x00}, + {ARCOM2, 0xc0}, + {0x06, 0x88}, + {0x07, 0xc0}, + {COM4, 0x87}, + {0x0e, 0x41}, + {0x4c, 0x00}, + {BANK_SEL, BANK_DSP}, + {RESET, RESET_DVP}, + + //Set the sensor resolution (UXGA, SVGA, CIF) + {HSIZE8, 0x32}, + {VSIZE8, 0x25}, + {SIZEL, 0x00}, + + //Set the image window size >= output size + {HSIZE, 0x64}, + {VSIZE, 0x4a}, + {XOFFL, 0x00}, + {YOFFL, 0x00}, + {VHYX, 0x00}, + {TEST, 0x00}, + + {CTRL2, CTRL2_DCW_EN | 0x1D}, + {CTRLI, CTRLI_LP_DP | 0x00}, + //{R_DVP_SP, 0x08}, + {0, 0} +}; + +const DRAM_ATTR uint8_t ov2640_settings_to_svga[][2] = { + {BANK_SEL, BANK_SENSOR}, + {COM7, COM7_RES_SVGA}, + + //Set the sensor output window + {COM1, 0x0A}, + {REG32, REG32_SVGA}, + {HSTART, 0x11}, + {HSTOP, 0x43}, + {VSTART, 0x00}, + {VSTOP, 0x4b}, + + //{CLKRC, 0x00}, + {0x37, 0xc0}, + {BD50, 0xca}, + {BD60, 0xa8}, + {0x5a, 0x23}, + {0x6d, 0x00}, + {0x3d, 0x38}, + {0x39, 0x92}, + {0x35, 0xda}, + {0x22, 0x1a}, + {0x37, 0xc3}, + {0x23, 0x00}, + {ARCOM2, 0xc0}, + {0x06, 0x88}, + {0x07, 0xc0}, + {COM4, 0x87}, + {0x0e, 0x41}, + {0x42, 0x03}, + {0x4c, 0x00}, + {BANK_SEL, BANK_DSP}, + {RESET, RESET_DVP}, + + //Set the sensor resolution (UXGA, SVGA, CIF) + {HSIZE8, 0x64}, + {VSIZE8, 0x4B}, + {SIZEL, 0x00}, + + //Set the image window size >= output size + {HSIZE, 0xC8}, + {VSIZE, 0x96}, + {XOFFL, 0x00}, + {YOFFL, 0x00}, + {VHYX, 0x00}, + {TEST, 0x00}, + + {CTRL2, CTRL2_DCW_EN | 0x1D}, + {CTRLI, CTRLI_LP_DP | 0x00}, + //{R_DVP_SP, 0x08}, + {0, 0} +}; + +const DRAM_ATTR uint8_t ov2640_settings_to_uxga[][2] = { + {BANK_SEL, BANK_SENSOR}, + {COM7, COM7_RES_UXGA}, + + //Set the sensor output window + {COM1, 0x0F}, + {REG32, REG32_UXGA}, + {HSTART, 0x11}, + {HSTOP, 0x75}, + {VSTART, 0x01}, + {VSTOP, 0x97}, + + //{CLKRC, 0x00}, + {0x3d, 0x34}, + {BD50, 0xbb}, + {BD60, 0x9c}, + {0x5a, 0x57}, + {0x6d, 0x80}, + {0x39, 0x82}, + {0x23, 0x00}, + {0x07, 0xc0}, + {0x4c, 0x00}, + {0x35, 0x88}, + {0x22, 0x0a}, + {0x37, 0x40}, + {ARCOM2, 0xa0}, + {0x06, 0x02}, + {COM4, 0xb7}, + {0x0e, 0x01}, + {0x42, 0x83}, + {BANK_SEL, BANK_DSP}, + {RESET, RESET_DVP}, + + //Set the sensor resolution (UXGA, SVGA, CIF) + {HSIZE8, 0xc8}, + {VSIZE8, 0x96}, + {SIZEL, 0x00}, + + //Set the image window size >= output size + {HSIZE, 0x90}, + {VSIZE, 0x2c}, + {XOFFL, 0x00}, + {YOFFL, 0x00}, + {VHYX, 0x88}, + {TEST, 0x00}, + + {CTRL2, CTRL2_DCW_EN | 0x1d}, + {CTRLI, 0x00}, + //{R_DVP_SP, 0x06}, + {0, 0} +}; + +const DRAM_ATTR uint8_t ov2640_settings_jpeg3[][2] = { + {BANK_SEL, BANK_DSP}, + {RESET, RESET_JPEG | RESET_DVP}, + {IMAGE_MODE, IMAGE_MODE_JPEG_EN | IMAGE_MODE_HREF_VSYNC}, + {0xD7, 0x03}, + {0xE1, 0x77}, + {0xE5, 0x1F}, + {0xD9, 0x10}, + {0xDF, 0x80}, + {0x33, 0x80}, + {0x3C, 0x10}, + {0xEB, 0x30}, + {0xDD, 0x7F}, + {RESET, 0x00}, + {0, 0} +}; + +static const uint8_t ov2640_settings_yuv422[][2] = { + {BANK_SEL, BANK_DSP}, + {RESET, RESET_DVP}, + {IMAGE_MODE, IMAGE_MODE_YUV422}, + {0xD7, 0x01}, + {0xE1, 0x67}, + {RESET, 0x00}, + {0, 0}, +}; + +static const uint8_t ov2640_settings_rgb565[][2] = { + {BANK_SEL, BANK_DSP}, + {RESET, RESET_DVP}, + {IMAGE_MODE, IMAGE_MODE_RGB565}, + {0xD7, 0x03}, + {0xE1, 0x77}, + {RESET, 0x00}, + {0, 0}, +}; + +#define NUM_BRIGHTNESS_LEVELS (5) +static const uint8_t brightness_regs[NUM_BRIGHTNESS_LEVELS + 1][5] = { + {BPADDR, BPDATA, BPADDR, BPDATA, BPDATA }, + {0x00, 0x04, 0x09, 0x00, 0x00 }, /* -2 */ + {0x00, 0x04, 0x09, 0x10, 0x00 }, /* -1 */ + {0x00, 0x04, 0x09, 0x20, 0x00 }, /* 0 */ + {0x00, 0x04, 0x09, 0x30, 0x00 }, /* +1 */ + {0x00, 0x04, 0x09, 0x40, 0x00 }, /* +2 */ +}; + +#define NUM_CONTRAST_LEVELS (5) +static const uint8_t contrast_regs[NUM_CONTRAST_LEVELS + 1][7] = { + {BPADDR, BPDATA, BPADDR, BPDATA, BPDATA, BPDATA, BPDATA }, + {0x00, 0x04, 0x07, 0x20, 0x18, 0x34, 0x06 }, /* -2 */ + {0x00, 0x04, 0x07, 0x20, 0x1c, 0x2a, 0x06 }, /* -1 */ + {0x00, 0x04, 0x07, 0x20, 0x20, 0x20, 0x06 }, /* 0 */ + {0x00, 0x04, 0x07, 0x20, 0x24, 0x16, 0x06 }, /* +1 */ + {0x00, 0x04, 0x07, 0x20, 0x28, 0x0c, 0x06 }, /* +2 */ +}; + +#define NUM_SATURATION_LEVELS (5) +static const uint8_t saturation_regs[NUM_SATURATION_LEVELS + 1][5] = { + {BPADDR, BPDATA, BPADDR, BPDATA, BPDATA }, + {0x00, 0x02, 0x03, 0x28, 0x28 }, /* -2 */ + {0x00, 0x02, 0x03, 0x38, 0x38 }, /* -1 */ + {0x00, 0x02, 0x03, 0x48, 0x48 }, /* 0 */ + {0x00, 0x02, 0x03, 0x58, 0x58 }, /* +1 */ + {0x00, 0x02, 0x03, 0x68, 0x68 }, /* +2 */ +}; + +#define NUM_SPECIAL_EFFECTS (7) +static const uint8_t special_effects_regs[NUM_SPECIAL_EFFECTS + 1][5] = { + {BPADDR, BPDATA, BPADDR, BPDATA, BPDATA }, + {0x00, 0X00, 0x05, 0X80, 0X80 }, /* no effect */ + {0x00, 0X40, 0x05, 0X80, 0X80 }, /* negative */ + {0x00, 0X18, 0x05, 0X80, 0X80 }, /* black and white */ + {0x00, 0X18, 0x05, 0X40, 0XC0 }, /* reddish */ + {0x00, 0X18, 0x05, 0X40, 0X40 }, /* greenish */ + {0x00, 0X18, 0x05, 0XA0, 0X40 }, /* blue */ + {0x00, 0X18, 0x05, 0X40, 0XA6 }, /* retro */ +}; + +#define NUM_WB_MODES (4) +static const uint8_t wb_modes_regs[NUM_WB_MODES + 1][3] = { + {0XCC, 0XCD, 0XCE }, + {0x5E, 0X41, 0x54 }, /* sunny */ + {0x65, 0X41, 0x4F }, /* cloudy */ + {0x52, 0X41, 0x66 }, /* office */ + {0x42, 0X3F, 0x71 }, /* home */ +}; + +#define NUM_AE_LEVELS (5) +static const uint8_t ae_levels_regs[NUM_AE_LEVELS + 1][3] = { + { AEW, AEB, VV }, + {0x20, 0X18, 0x60 }, + {0x34, 0X1C, 0x00 }, + {0x3E, 0X38, 0x81 }, + {0x48, 0X40, 0x81 }, + {0x58, 0X50, 0x92 }, +}; + +const uint8_t agc_gain_tbl[31] = { + 0x00, 0x10, 0x18, 0x30, 0x34, 0x38, 0x3C, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7A, 0x7C, 0x7E, 0xF0, + 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF +}; + +#endif /* _OV2640_SETTINGS_H_ */ diff --git a/esp32-cam-rtos/ov3660.c b/esp32-cam-rtos/ov3660.c new file mode 100644 index 0000000..723ec5c --- /dev/null +++ b/esp32-cam-rtos/ov3660.c @@ -0,0 +1,1033 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * OV3660 driver. + * + */ +#include +#include +#include +#include "sccb.h" +#include "ov3660.h" +#include "ov3660_regs.h" +#include "ov3660_settings.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#else +#include "esp_log.h" +static const char *TAG = "ov3660"; +#endif + +//#define REG_DEBUG_ON + +static int read_reg(uint8_t slv_addr, const uint16_t reg){ + int ret = SCCB_Read16(slv_addr, reg); +#ifdef REG_DEBUG_ON + if (ret < 0) { + ESP_LOGE(TAG, "READ REG 0x%04x FAILED: %d", reg, ret); + } +#endif + return ret; +} + +static int check_reg_mask(uint8_t slv_addr, uint16_t reg, uint8_t mask){ + return (read_reg(slv_addr, reg) & mask) == mask; +} + +static int read_reg16(uint8_t slv_addr, const uint16_t reg){ + int ret = 0, ret2 = 0; + ret = read_reg(slv_addr, reg); + if (ret >= 0) { + ret = (ret & 0xFF) << 8; + ret2 = read_reg(slv_addr, reg+1); + if (ret2 < 0) { + ret = ret2; + } else { + ret |= ret2 & 0xFF; + } + } + return ret; +} + + +static int write_reg(uint8_t slv_addr, const uint16_t reg, uint8_t value){ + int ret = 0; +#ifndef REG_DEBUG_ON + ret = SCCB_Write16(slv_addr, reg, value); +#else + int old_value = read_reg(slv_addr, reg); + if (old_value < 0) { + return old_value; + } + if ((uint8_t)old_value != value) { + ESP_LOGI(TAG, "NEW REG 0x%04x: 0x%02x to 0x%02x", reg, (uint8_t)old_value, value); + ret = SCCB_Write16(slv_addr, reg, value); + } else { + ESP_LOGD(TAG, "OLD REG 0x%04x: 0x%02x", reg, (uint8_t)old_value); + ret = SCCB_Write16(slv_addr, reg, value);//maybe not? + } + if (ret < 0) { + ESP_LOGE(TAG, "WRITE REG 0x%04x FAILED: %d", reg, ret); + } +#endif + return ret; +} + +static int set_reg_bits(uint8_t slv_addr, uint16_t reg, uint8_t offset, uint8_t mask, uint8_t value) +{ + int ret = 0; + uint8_t c_value, new_value; + ret = read_reg(slv_addr, reg); + if(ret < 0) { + return ret; + } + c_value = ret; + new_value = (c_value & ~(mask << offset)) | ((value & mask) << offset); + ret = write_reg(slv_addr, reg, new_value); + return ret; +} + +static int write_regs(uint8_t slv_addr, const uint16_t (*regs)[2]) +{ + int i = 0, ret = 0; + while (!ret && regs[i][0] != REGLIST_TAIL) { + if (regs[i][0] == REG_DLY) { + vTaskDelay(regs[i][1] / portTICK_PERIOD_MS); + } else { + ret = write_reg(slv_addr, regs[i][0], regs[i][1]); + } + i++; + } + return ret; +} + +static int write_reg16(uint8_t slv_addr, const uint16_t reg, uint16_t value) +{ + if (write_reg(slv_addr, reg, value >> 8) || write_reg(slv_addr, reg + 1, value)) { + return -1; + } + return 0; +} + +static int write_addr_reg(uint8_t slv_addr, const uint16_t reg, uint16_t x_value, uint16_t y_value) +{ + if (write_reg16(slv_addr, reg, x_value) || write_reg16(slv_addr, reg + 2, y_value)) { + return -1; + } + return 0; +} + +#define write_reg_bits(slv_addr, reg, mask, enable) set_reg_bits(slv_addr, reg, 0, mask, enable?mask:0) + +static int calc_sysclk(int xclk, bool pll_bypass, int pll_multiplier, int pll_sys_div, int pll_pre_div, bool pll_root_2x, int pll_seld5, bool pclk_manual, int pclk_div) +{ + const int pll_pre_div2x_map[] = { 2, 3, 4, 6 };//values are multiplied by two to avoid floats + const int pll_seld52x_map[] = { 2, 2, 4, 5 }; + + if(!pll_sys_div) { + pll_sys_div = 1; + } + + int pll_pre_div2x = pll_pre_div2x_map[pll_pre_div]; + int pll_root_div = pll_root_2x?2:1; + int pll_seld52x = pll_seld52x_map[pll_seld5]; + + int VCO = (xclk / 1000) * pll_multiplier * pll_root_div * 2 / pll_pre_div2x; + int PLLCLK = pll_bypass?(xclk):(VCO * 1000 * 2 / pll_sys_div / pll_seld52x); + int PCLK = PLLCLK / 2 / ((pclk_manual && pclk_div)?pclk_div:1); + int SYSCLK = PLLCLK / 4; + + ESP_LOGD(TAG, "Calculated VCO: %d Hz, PLLCLK: %d Hz, SYSCLK: %d Hz, PCLK: %d Hz", VCO*1000, PLLCLK, SYSCLK, PCLK); + return SYSCLK; +} + +static int set_pll(sensor_t *sensor, bool bypass, uint8_t multiplier, uint8_t sys_div, uint8_t pre_div, bool root_2x, uint8_t seld5, bool pclk_manual, uint8_t pclk_div){ + int ret = 0; + if(multiplier > 31 || sys_div > 15 || pre_div > 3 || pclk_div > 31 || seld5 > 3){ + ESP_LOGE(TAG, "Invalid arguments"); + return -1; + } + + calc_sysclk(sensor->xclk_freq_hz, bypass, multiplier, sys_div, pre_div, root_2x, seld5, pclk_manual, pclk_div); + + ret = write_reg(sensor->slv_addr, SC_PLLS_CTRL0, bypass?0x80:0x00); + if (ret == 0) { + ret = write_reg(sensor->slv_addr, SC_PLLS_CTRL1, multiplier & 0x1f); + } + if (ret == 0) { + ret = write_reg(sensor->slv_addr, SC_PLLS_CTRL2, 0x10 | (sys_div & 0x0f)); + } + if (ret == 0) { + ret = write_reg(sensor->slv_addr, SC_PLLS_CTRL3, (pre_div & 0x3) << 4 | seld5 | (root_2x?0x40:0x00)); + } + if (ret == 0) { + ret = write_reg(sensor->slv_addr, PCLK_RATIO, pclk_div & 0x1f); + } + if (ret == 0) { + ret = write_reg(sensor->slv_addr, VFIFO_CTRL0C, pclk_manual?0x22:0x20); + } + if(ret){ + ESP_LOGE(TAG, "set_sensor_pll FAILED!"); + } + return ret; +} + +static int set_ae_level(sensor_t *sensor, int level); + +static int reset(sensor_t *sensor) +{ + int ret = 0; + // Software Reset: clear all registers and reset them to their default values + ret = write_reg(sensor->slv_addr, SYSTEM_CTROL0, 0x82); + if(ret){ + ESP_LOGE(TAG, "Software Reset FAILED!"); + return ret; + } + vTaskDelay(100 / portTICK_PERIOD_MS); + ret = write_regs(sensor->slv_addr, sensor_default_regs); + if (ret == 0) { + ESP_LOGD(TAG, "Camera defaults loaded"); + ret = set_ae_level(sensor, 0); + vTaskDelay(100 / portTICK_PERIOD_MS); + } + return ret; +} + +static int set_pixformat(sensor_t *sensor, pixformat_t pixformat) +{ + int ret = 0; + const uint16_t (*regs)[2]; + + switch (pixformat) { + case PIXFORMAT_YUV422: + regs = sensor_fmt_yuv422; + break; + + case PIXFORMAT_GRAYSCALE: + regs = sensor_fmt_grayscale; + break; + + case PIXFORMAT_RGB565: + case PIXFORMAT_RGB888: + regs = sensor_fmt_rgb565; + break; + + case PIXFORMAT_JPEG: + regs = sensor_fmt_jpeg; + break; + + case PIXFORMAT_RAW: + regs = sensor_fmt_raw; + break; + + default: + ESP_LOGE(TAG, "Unsupported pixformat: %u", pixformat); + return -1; + } + + ret = write_regs(sensor->slv_addr, regs); + if(ret == 0) { + sensor->pixformat = pixformat; + ESP_LOGD(TAG, "Set pixformat to: %u", pixformat); + } + return ret; +} + +static int set_image_options(sensor_t *sensor) +{ + int ret = 0; + uint8_t reg20 = 0; + uint8_t reg21 = 0; + uint8_t reg4514 = 0; + uint8_t reg4514_test = 0; + + // compression + if (sensor->pixformat == PIXFORMAT_JPEG) { + reg21 |= 0x20; + } + + // binning + if (sensor->status.binning) { + reg20 |= 0x01; + reg21 |= 0x01; + reg4514_test |= 4; + } else { + reg20 |= 0x40; + } + + // V-Flip + if (sensor->status.vflip) { + reg20 |= 0x06; + reg4514_test |= 1; + } + + // H-Mirror + if (sensor->status.hmirror) { + reg21 |= 0x06; + reg4514_test |= 2; + } + + switch (reg4514_test) { + //no binning + case 0: reg4514 = 0x88; break;//normal + case 1: reg4514 = 0x88; break;//v-flip + case 2: reg4514 = 0xbb; break;//h-mirror + case 3: reg4514 = 0xbb; break;//v-flip+h-mirror + //binning + case 4: reg4514 = 0xaa; break;//normal + case 5: reg4514 = 0xbb; break;//v-flip + case 6: reg4514 = 0xbb; break;//h-mirror + case 7: reg4514 = 0xaa; break;//v-flip+h-mirror + } + + if(write_reg(sensor->slv_addr, TIMING_TC_REG20, reg20) + || write_reg(sensor->slv_addr, TIMING_TC_REG21, reg21) + || write_reg(sensor->slv_addr, 0x4514, reg4514)){ + ESP_LOGE(TAG, "Setting Image Options Failed"); + ret = -1; + } + + if (sensor->status.binning) { + ret = write_reg(sensor->slv_addr, 0x4520, 0x0b) + || write_reg(sensor->slv_addr, X_INCREMENT, 0x31)//odd:3, even: 1 + || write_reg(sensor->slv_addr, Y_INCREMENT, 0x31);//odd:3, even: 1 + } else { + ret = write_reg(sensor->slv_addr, 0x4520, 0xb0) + || write_reg(sensor->slv_addr, X_INCREMENT, 0x11)//odd:1, even: 1 + || write_reg(sensor->slv_addr, Y_INCREMENT, 0x11);//odd:1, even: 1 + } + + ESP_LOGD(TAG, "Set Image Options: Compression: %u, Binning: %u, V-Flip: %u, H-Mirror: %u, Reg-4514: 0x%02x", + sensor->pixformat == PIXFORMAT_JPEG, sensor->status.binning, sensor->status.vflip, sensor->status.hmirror, reg4514); + return ret; +} + +static int set_framesize(sensor_t *sensor, framesize_t framesize) +{ + int ret = 0; + framesize_t old_framesize = sensor->status.framesize; + sensor->status.framesize = framesize; + + if(framesize > FRAMESIZE_QXGA){ + ESP_LOGE(TAG, "Invalid framesize: %u", framesize); + return -1; + } + uint16_t w = resolution[framesize].width; + uint16_t h = resolution[framesize].height; + aspect_ratio_t ratio = resolution[sensor->status.framesize].aspect_ratio; + ratio_settings_t settings = ratio_table[ratio]; + + sensor->status.binning = (w <= (settings.max_width / 2) && h <= (settings.max_height / 2)); + sensor->status.scale = !((w == settings.max_width && h == settings.max_height) + || (w == (settings.max_width / 2) && h == (settings.max_height / 2))); + + ret = write_addr_reg(sensor->slv_addr, X_ADDR_ST_H, settings.start_x, settings.start_y) + || write_addr_reg(sensor->slv_addr, X_ADDR_END_H, settings.end_x, settings.end_y) + || write_addr_reg(sensor->slv_addr, X_OUTPUT_SIZE_H, w, h); + + if (ret) { + goto fail; + } + + if (sensor->status.binning) { + ret = write_addr_reg(sensor->slv_addr, X_TOTAL_SIZE_H, settings.total_x, (settings.total_y / 2) + 1) + || write_addr_reg(sensor->slv_addr, X_OFFSET_H, 8, 2); + } else { + ret = write_addr_reg(sensor->slv_addr, X_TOTAL_SIZE_H, settings.total_x, settings.total_y) + || write_addr_reg(sensor->slv_addr, X_OFFSET_H, 16, 6); + } + + if (ret == 0) { + ret = write_reg_bits(sensor->slv_addr, ISP_CONTROL_01, 0x20, sensor->status.scale); + } + + if (ret == 0) { + ret = set_image_options(sensor); + } + + if (ret) { + goto fail; + } + + if (sensor->pixformat == PIXFORMAT_JPEG) { + if (framesize == FRAMESIZE_QXGA) { + //40MHz SYSCLK and 10MHz PCLK + ret = set_pll(sensor, false, 24, 1, 3, false, 0, true, 8); + } else { + //50MHz SYSCLK and 10MHz PCLK + ret = set_pll(sensor, false, 30, 1, 3, false, 0, true, 10); + } + } else { + if (framesize > FRAMESIZE_CIF) { + //10MHz SYSCLK and 10MHz PCLK (6.19 FPS) + ret = set_pll(sensor, false, 2, 1, 0, false, 0, true, 2); + } else { + //25MHz SYSCLK and 10MHz PCLK (15.45 FPS) + ret = set_pll(sensor, false, 5, 1, 0, false, 0, true, 5); + } + } + + if (ret == 0) { + ESP_LOGD(TAG, "Set framesize to: %ux%u", w, h); + } + return ret; + +fail: + sensor->status.framesize = old_framesize; + ESP_LOGE(TAG, "Setting framesize to: %ux%u failed", w, h); + return ret; +} + +static int set_hmirror(sensor_t *sensor, int enable) +{ + int ret = 0; + sensor->status.hmirror = enable; + ret = set_image_options(sensor); + if (ret == 0) { + ESP_LOGD(TAG, "Set h-mirror to: %d", enable); + } + return ret; +} + +static int set_vflip(sensor_t *sensor, int enable) +{ + int ret = 0; + sensor->status.vflip = enable; + ret = set_image_options(sensor); + if (ret == 0) { + ESP_LOGD(TAG, "Set v-flip to: %d", enable); + } + return ret; +} + +static int set_quality(sensor_t *sensor, int qs) +{ + int ret = 0; + ret = write_reg(sensor->slv_addr, COMPRESSION_CTRL07, qs & 0x3f); + if (ret == 0) { + sensor->status.quality = qs; + ESP_LOGD(TAG, "Set quality to: %d", qs); + } + return ret; +} + +static int set_colorbar(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, PRE_ISP_TEST_SETTING_1, TEST_COLOR_BAR, enable); + if (ret == 0) { + sensor->status.colorbar = enable; + ESP_LOGD(TAG, "Set colorbar to: %d", enable); + } + return ret; +} + +static int set_gain_ctrl(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, AEC_PK_MANUAL, AEC_PK_MANUAL_AGC_MANUALEN, !enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set gain_ctrl to: %d", enable); + sensor->status.agc = enable; + } + return ret; +} + +static int set_exposure_ctrl(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, AEC_PK_MANUAL, AEC_PK_MANUAL_AEC_MANUALEN, !enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set exposure_ctrl to: %d", enable); + sensor->status.aec = enable; + } + return ret; +} + +static int set_whitebal(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, ISP_CONTROL_01, 0x01, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set awb to: %d", enable); + sensor->status.awb = enable; + } + return ret; +} + +//Advanced AWB +static int set_dcw_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, 0x5183, 0x80, !enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set dcw to: %d", enable); + sensor->status.dcw = enable; + } + return ret; +} + +//night mode enable +static int set_aec2(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, 0x3a00, 0x04, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set aec2 to: %d", enable); + sensor->status.aec2 = enable; + } + return ret; +} + +static int set_bpc_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, 0x5000, 0x04, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set bpc to: %d", enable); + sensor->status.bpc = enable; + } + return ret; +} + +static int set_wpc_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, 0x5000, 0x02, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set wpc to: %d", enable); + sensor->status.wpc = enable; + } + return ret; +} + +//Gamma enable +static int set_raw_gma_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, 0x5000, 0x20, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set raw_gma to: %d", enable); + sensor->status.raw_gma = enable; + } + return ret; +} + +static int set_lenc_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, 0x5000, 0x80, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set lenc to: %d", enable); + sensor->status.lenc = enable; + } + return ret; +} + +static int get_agc_gain(sensor_t *sensor) +{ + int ra = read_reg(sensor->slv_addr, 0x350a); + if (ra < 0) { + return 0; + } + int rb = read_reg(sensor->slv_addr, 0x350b); + if (rb < 0) { + return 0; + } + int res = (rb & 0xF0) >> 4 | (ra & 0x03) << 4; + if (rb & 0x0F) { + res += 1; + } + return res; +} + +//real gain +static int set_agc_gain(sensor_t *sensor, int gain) +{ + int ret = 0; + if(gain < 0) { + gain = 0; + } else if(gain > 64) { + gain = 64; + } + + //gain value is 6.4 bits float + //in order to use the max range, we deduct 1/16 + int gainv = gain << 4; + if(gainv){ + gainv -= 1; + } + + ret = write_reg(sensor->slv_addr, 0x350a, gainv >> 8) || write_reg(sensor->slv_addr, 0x350b, gainv & 0xff); + if (ret == 0) { + ESP_LOGD(TAG, "Set agc_gain to: %d", gain); + sensor->status.agc_gain = gain; + } + return ret; +} + +static int get_aec_value(sensor_t *sensor) +{ + int ra = read_reg(sensor->slv_addr, 0x3500); + if (ra < 0) { + return 0; + } + int rb = read_reg(sensor->slv_addr, 0x3501); + if (rb < 0) { + return 0; + } + int rc = read_reg(sensor->slv_addr, 0x3502); + if (rc < 0) { + return 0; + } + int res = (ra & 0x0F) << 12 | (rb & 0xFF) << 4 | (rc & 0xF0) >> 4; + return res; +} + +static int set_aec_value(sensor_t *sensor, int value) +{ + int ret = 0, max_val = 0; + max_val = read_reg16(sensor->slv_addr, 0x380e); + if (max_val < 0) { + ESP_LOGE(TAG, "Could not read max aec_value"); + return -1; + } + if (value > max_val) { + value =max_val; + } + + ret = write_reg(sensor->slv_addr, 0x3500, (value >> 12) & 0x0F) + || write_reg(sensor->slv_addr, 0x3501, (value >> 4) & 0xFF) + || write_reg(sensor->slv_addr, 0x3502, (value << 4) & 0xF0); + + if (ret == 0) { + ESP_LOGD(TAG, "Set aec_value to: %d / %d", value, max_val); + sensor->status.aec_value = value; + } + return ret; +} + +static int set_ae_level(sensor_t *sensor, int level) +{ + int ret = 0; + if (level < -5 || level > 5) { + return -1; + } + //good targets are between 5 and 115 + int target_level = ((level + 5) * 10) + 5; + + int level_high, level_low; + int fast_high, fast_low; + + level_low = target_level * 23 / 25; //0.92 (0.46) + level_high = target_level * 27 / 25; //1.08 (2.08) + + fast_low = level_low >> 1; + fast_high = level_high << 1; + + if(fast_high>255) { + fast_high = 255; + } + + ret = write_reg(sensor->slv_addr, 0x3a0f, level_high) + || write_reg(sensor->slv_addr, 0x3a10, level_low) + || write_reg(sensor->slv_addr, 0x3a1b, level_high) + || write_reg(sensor->slv_addr, 0x3a1e, level_low) + || write_reg(sensor->slv_addr, 0x3a11, fast_high) + || write_reg(sensor->slv_addr, 0x3a1f, fast_low); + + if (ret == 0) { + ESP_LOGD(TAG, "Set ae_level to: %d", level); + sensor->status.ae_level = level; + } + return ret; +} + +static int set_wb_mode(sensor_t *sensor, int mode) +{ + int ret = 0; + if (mode < 0 || mode > 4) { + return -1; + } + + ret = write_reg(sensor->slv_addr, 0x3406, (mode != 0)); + if (ret) { + return ret; + } + switch (mode) { + case 1://Sunny + ret = write_reg16(sensor->slv_addr, 0x3400, 0x5e0) //AWB R GAIN + || write_reg16(sensor->slv_addr, 0x3402, 0x410) //AWB G GAIN + || write_reg16(sensor->slv_addr, 0x3404, 0x540);//AWB B GAIN + break; + case 2://Cloudy + ret = write_reg16(sensor->slv_addr, 0x3400, 0x650) //AWB R GAIN + || write_reg16(sensor->slv_addr, 0x3402, 0x410) //AWB G GAIN + || write_reg16(sensor->slv_addr, 0x3404, 0x4f0);//AWB B GAIN + break; + case 3://Office + ret = write_reg16(sensor->slv_addr, 0x3400, 0x520) //AWB R GAIN + || write_reg16(sensor->slv_addr, 0x3402, 0x410) //AWB G GAIN + || write_reg16(sensor->slv_addr, 0x3404, 0x660);//AWB B GAIN + break; + case 4://HOME + ret = write_reg16(sensor->slv_addr, 0x3400, 0x420) //AWB R GAIN + || write_reg16(sensor->slv_addr, 0x3402, 0x3f0) //AWB G GAIN + || write_reg16(sensor->slv_addr, 0x3404, 0x710);//AWB B GAIN + break; + default://AUTO + break; + } + + if (ret == 0) { + ESP_LOGD(TAG, "Set wb_mode to: %d", mode); + sensor->status.wb_mode = mode; + } + return ret; +} + +static int set_awb_gain_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + int old_mode = sensor->status.wb_mode; + int mode = enable?old_mode:0; + + ret = set_wb_mode(sensor, mode); + + if (ret == 0) { + sensor->status.wb_mode = old_mode; + ESP_LOGD(TAG, "Set awb_gain to: %d", enable); + sensor->status.awb_gain = enable; + } + return ret; +} + +static int set_special_effect(sensor_t *sensor, int effect) +{ + int ret=0; + if (effect < 0 || effect > 6) { + return -1; + } + + uint8_t * regs = (uint8_t *)sensor_special_effects[effect]; + ret = write_reg(sensor->slv_addr, 0x5580, regs[0]) + || write_reg(sensor->slv_addr, 0x5583, regs[1]) + || write_reg(sensor->slv_addr, 0x5584, regs[2]) + || write_reg(sensor->slv_addr, 0x5003, regs[3]); + + if (ret == 0) { + ESP_LOGD(TAG, "Set special_effect to: %d", effect); + sensor->status.special_effect = effect; + } + return ret; +} + +static int set_brightness(sensor_t *sensor, int level) +{ + int ret = 0; + uint8_t value = 0; + bool negative = false; + + switch (level) { + case 3: + value = 0x30; + break; + case 2: + value = 0x20; + break; + case 1: + value = 0x10; + break; + case -1: + value = 0x10; + negative = true; + break; + case -2: + value = 0x20; + negative = true; + break; + case -3: + value = 0x30; + negative = true; + break; + default: // 0 + break; + } + + ret = write_reg(sensor->slv_addr, 0x5587, value); + if (ret == 0) { + ret = write_reg_bits(sensor->slv_addr, 0x5588, 0x08, negative); + } + + if (ret == 0) { + ESP_LOGD(TAG, "Set brightness to: %d", level); + sensor->status.brightness = level; + } + return ret; +} + +static int set_contrast(sensor_t *sensor, int level) +{ + int ret = 0; + if(level > 3 || level < -3) { + return -1; + } + ret = write_reg(sensor->slv_addr, 0x5586, (level + 4) << 3); + + if (ret == 0) { + ESP_LOGD(TAG, "Set contrast to: %d", level); + sensor->status.contrast = level; + } + return ret; +} + +static int set_saturation(sensor_t *sensor, int level) +{ + int ret = 0; + if(level > 4 || level < -4) { + return -1; + } + + uint8_t * regs = (uint8_t *)sensor_saturation_levels[level+4]; + for(int i=0; i<11; i++) { + ret = write_reg(sensor->slv_addr, 0x5381 + i, regs[i]); + if (ret) { + break; + } + } + + if (ret == 0) { + ESP_LOGD(TAG, "Set saturation to: %d", level); + sensor->status.saturation = level; + } + return ret; +} + +static int set_sharpness(sensor_t *sensor, int level) +{ + int ret = 0; + if(level > 3 || level < -3) { + return -1; + } + + uint8_t mt_offset_2 = (level + 3) * 8; + uint8_t mt_offset_1 = mt_offset_2 + 1; + + ret = write_reg_bits(sensor->slv_addr, 0x5308, 0x40, false)//0x40 means auto + || write_reg(sensor->slv_addr, 0x5300, 0x10) + || write_reg(sensor->slv_addr, 0x5301, 0x10) + || write_reg(sensor->slv_addr, 0x5302, mt_offset_1) + || write_reg(sensor->slv_addr, 0x5303, mt_offset_2) + || write_reg(sensor->slv_addr, 0x5309, 0x10) + || write_reg(sensor->slv_addr, 0x530a, 0x10) + || write_reg(sensor->slv_addr, 0x530b, 0x04) + || write_reg(sensor->slv_addr, 0x530c, 0x06); + + if (ret == 0) { + ESP_LOGD(TAG, "Set sharpness to: %d", level); + sensor->status.sharpness = level; + } + return ret; +} + +static int set_gainceiling(sensor_t *sensor, gainceiling_t level) +{ + int ret = 0, l = (int)level; + + ret = write_reg(sensor->slv_addr, 0x3A18, (l >> 8) & 3) + || write_reg(sensor->slv_addr, 0x3A19, l & 0xFF); + + if (ret == 0) { + ESP_LOGD(TAG, "Set gainceiling to: %d", l); + sensor->status.gainceiling = l; + } + return ret; +} + +static int get_denoise(sensor_t *sensor) +{ + if (!check_reg_mask(sensor->slv_addr, 0x5308, 0x10)) { + return 0; + } + return (read_reg(sensor->slv_addr, 0x5306) / 4) + 1; +} + +static int set_denoise(sensor_t *sensor, int level) +{ + int ret = 0; + if (level < 0 || level > 8) { + return -1; + } + + ret = write_reg_bits(sensor->slv_addr, 0x5308, 0x10, level > 0); + if (ret == 0 && level > 0) { + ret = write_reg(sensor->slv_addr, 0x5306, (level - 1) * 4); + } + + if (ret == 0) { + ESP_LOGD(TAG, "Set denoise to: %d", level); + sensor->status.denoise = level; + } + return ret; +} + +static int get_reg(sensor_t *sensor, int reg, int mask) +{ + int ret = 0, ret2 = 0; + if(mask > 0xFF){ + ret = read_reg16(sensor->slv_addr, reg); + if(ret >= 0 && mask > 0xFFFF){ + ret2 = read_reg(sensor->slv_addr, reg+2); + if(ret2 >= 0){ + ret = (ret << 8) | ret2 ; + } else { + ret = ret2; + } + } + } else { + ret = read_reg(sensor->slv_addr, reg); + } + if(ret > 0){ + ret &= mask; + } + return ret; +} + +static int set_reg(sensor_t *sensor, int reg, int mask, int value) +{ + int ret = 0, ret2 = 0; + if(mask > 0xFF){ + ret = read_reg16(sensor->slv_addr, reg); + if(ret >= 0 && mask > 0xFFFF){ + ret2 = read_reg(sensor->slv_addr, reg+2); + if(ret2 >= 0){ + ret = (ret << 8) | ret2 ; + } else { + ret = ret2; + } + } + } else { + ret = read_reg(sensor->slv_addr, reg); + } + if(ret < 0){ + return ret; + } + value = (ret & ~mask) | (value & mask); + if(mask > 0xFFFF){ + ret = write_reg16(sensor->slv_addr, reg, value >> 8); + if(ret >= 0){ + ret = write_reg(sensor->slv_addr, reg+2, value & 0xFF); + } + } else if(mask > 0xFF){ + ret = write_reg16(sensor->slv_addr, reg, value); + } else { + ret = write_reg(sensor->slv_addr, reg, value); + } + return ret; +} + +static int set_res_raw(sensor_t *sensor, int startX, int startY, int endX, int endY, int offsetX, int offsetY, int totalX, int totalY, int outputX, int outputY, bool scale, bool binning) +{ + int ret = 0; + ret = write_addr_reg(sensor->slv_addr, X_ADDR_ST_H, startX, startY) + || write_addr_reg(sensor->slv_addr, X_ADDR_END_H, endX, endY) + || write_addr_reg(sensor->slv_addr, X_OFFSET_H, offsetX, offsetY) + || write_addr_reg(sensor->slv_addr, X_TOTAL_SIZE_H, totalX, totalY) + || write_addr_reg(sensor->slv_addr, X_OUTPUT_SIZE_H, outputX, outputY) + || write_reg_bits(sensor->slv_addr, ISP_CONTROL_01, 0x20, scale); + if(!ret){ + sensor->status.scale = scale; + sensor->status.binning = binning; + ret = set_image_options(sensor); + } + return ret; +} + +static int _set_pll(sensor_t *sensor, int bypass, int multiplier, int sys_div, int root_2x, int pre_div, int seld5, int pclk_manual, int pclk_div) +{ + return set_pll(sensor, bypass > 0, multiplier, sys_div, pre_div, root_2x > 0, seld5, pclk_manual > 0, pclk_div); +} + +esp_err_t xclk_timer_conf(int ledc_timer, int xclk_freq_hz); +static int set_xclk(sensor_t *sensor, int timer, int xclk) +{ + int ret = 0; + sensor->xclk_freq_hz = xclk * 1000000U; + ret = xclk_timer_conf(timer, sensor->xclk_freq_hz); + return ret; +} + +static int init_status(sensor_t *sensor) +{ + sensor->status.brightness = 0; + sensor->status.contrast = 0; + sensor->status.saturation = 0; + sensor->status.sharpness = (read_reg(sensor->slv_addr, 0x5303) / 8) - 3; + sensor->status.denoise = get_denoise(sensor); + sensor->status.ae_level = 0; + sensor->status.gainceiling = read_reg16(sensor->slv_addr, 0x3A18) & 0x3FF; + sensor->status.awb = check_reg_mask(sensor->slv_addr, ISP_CONTROL_01, 0x01); + sensor->status.dcw = !check_reg_mask(sensor->slv_addr, 0x5183, 0x80); + sensor->status.agc = !check_reg_mask(sensor->slv_addr, AEC_PK_MANUAL, AEC_PK_MANUAL_AGC_MANUALEN); + sensor->status.aec = !check_reg_mask(sensor->slv_addr, AEC_PK_MANUAL, AEC_PK_MANUAL_AEC_MANUALEN); + sensor->status.hmirror = check_reg_mask(sensor->slv_addr, TIMING_TC_REG21, TIMING_TC_REG21_HMIRROR); + sensor->status.vflip = check_reg_mask(sensor->slv_addr, TIMING_TC_REG20, TIMING_TC_REG20_VFLIP); + sensor->status.colorbar = check_reg_mask(sensor->slv_addr, PRE_ISP_TEST_SETTING_1, TEST_COLOR_BAR); + sensor->status.bpc = check_reg_mask(sensor->slv_addr, 0x5000, 0x04); + sensor->status.wpc = check_reg_mask(sensor->slv_addr, 0x5000, 0x02); + sensor->status.raw_gma = check_reg_mask(sensor->slv_addr, 0x5000, 0x20); + sensor->status.lenc = check_reg_mask(sensor->slv_addr, 0x5000, 0x80); + sensor->status.quality = read_reg(sensor->slv_addr, COMPRESSION_CTRL07) & 0x3f; + sensor->status.special_effect = 0; + sensor->status.wb_mode = 0; + sensor->status.awb_gain = check_reg_mask(sensor->slv_addr, 0x3406, 0x01); + sensor->status.agc_gain = get_agc_gain(sensor); + sensor->status.aec_value = get_aec_value(sensor); + sensor->status.aec2 = check_reg_mask(sensor->slv_addr, 0x3a00, 0x04); + return 0; +} + +int ov3660_init(sensor_t *sensor) +{ + sensor->reset = reset; + sensor->set_pixformat = set_pixformat; + sensor->set_framesize = set_framesize; + sensor->set_contrast = set_contrast; + sensor->set_brightness = set_brightness; + sensor->set_saturation = set_saturation; + sensor->set_sharpness = set_sharpness; + sensor->set_gainceiling = set_gainceiling; + sensor->set_quality = set_quality; + sensor->set_colorbar = set_colorbar; + sensor->set_gain_ctrl = set_gain_ctrl; + sensor->set_exposure_ctrl = set_exposure_ctrl; + sensor->set_whitebal = set_whitebal; + sensor->set_hmirror = set_hmirror; + sensor->set_vflip = set_vflip; + sensor->init_status = init_status; + sensor->set_aec2 = set_aec2; + sensor->set_aec_value = set_aec_value; + sensor->set_special_effect = set_special_effect; + sensor->set_wb_mode = set_wb_mode; + sensor->set_ae_level = set_ae_level; + sensor->set_dcw = set_dcw_dsp; + sensor->set_bpc = set_bpc_dsp; + sensor->set_wpc = set_wpc_dsp; + sensor->set_awb_gain = set_awb_gain_dsp; + sensor->set_agc_gain = set_agc_gain; + sensor->set_raw_gma = set_raw_gma_dsp; + sensor->set_lenc = set_lenc_dsp; + sensor->set_denoise = set_denoise; + + sensor->get_reg = get_reg; + sensor->set_reg = set_reg; + sensor->set_res_raw = set_res_raw; + sensor->set_pll = _set_pll; + sensor->set_xclk = set_xclk; + return 0; +} diff --git a/esp32-cam-rtos/ov3660.h b/esp32-cam-rtos/ov3660.h new file mode 100644 index 0000000..8e5ae3c --- /dev/null +++ b/esp32-cam-rtos/ov3660.h @@ -0,0 +1,16 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * OV3660 driver. + * + */ +#ifndef __OV3660_H__ +#define __OV3660_H__ + +#include "sensor.h" + +int ov3660_init(sensor_t *sensor); + +#endif // __OV3660_H__ diff --git a/esp32-cam-rtos/ov3660_regs.h b/esp32-cam-rtos/ov3660_regs.h new file mode 100644 index 0000000..b5cf30a --- /dev/null +++ b/esp32-cam-rtos/ov3660_regs.h @@ -0,0 +1,211 @@ +/* + * OV3660 register definitions. + */ +#ifndef __OV3660_REG_REGS_H__ +#define __OV3660_REG_REGS_H__ + +/* system control registers */ +#define SYSTEM_CTROL0 0x3008 // Bit[7]: Software reset + // Bit[6]: Software power down + // Bit[5]: Reserved + // Bit[4]: SRB clock SYNC enable + // Bit[3]: Isolation suspend select + // Bit[2:0]: Not used + +/* output format control registers */ +#define FORMAT_CTRL 0x501F // Format select + // Bit[2:0]: + // 000: YUV422 + // 001: RGB + // 010: Dither + // 011: RAW after DPC + // 101: RAW after CIP + +/* format control registers */ +#define FORMAT_CTRL00 0x4300 + +/* frame control registers */ +#define FRAME_CTRL01 0x4201 // Control Passed Frame Number When both ON and OFF number set to 0x00,frame control is in bypass mode + // Bit[7:4]: Not used + // Bit[3:0]: Frame ON number +#define FRAME_CTRL02 0x4202 // Control Masked Frame Number When both ON and OFF number set to 0x00,frame control is in bypass mode + // Bit[7:4]: Not used + // BIT[3:0]: Frame OFF number + +/* ISP top control registers */ +#define PRE_ISP_TEST_SETTING_1 0x503D // Bit[7]: Test enable + // 0: Test disable + // 1: Color bar enable + // Bit[6]: Rolling + // Bit[5]: Transparent + // Bit[4]: Square black and white + // Bit[3:2]: Color bar style + // 00: Standard 8 color bar + // 01: Gradual change at vertical mode 1 + // 10: Gradual change at horizontal + // 11: Gradual change at vertical mode 2 + // Bit[1:0]: Test select + // 00: Color bar + // 01: Random data + // 10: Square data + // 11: Black image + +//exposure = {0x3500[3:0], 0x3501[7:0], 0x3502[7:0]} / 16 × tROW + +/* AEC/AGC control functions */ +#define AEC_PK_MANUAL 0x3503 // AEC Manual Mode Control + // Bit[7:6]: Reserved + // Bit[5]: Gain delay option + // Valid when 0x3503[4]=1’b0 + // 0: Delay one frame latch + // 1: One frame latch + // Bit[4:2]: Reserved + // Bit[1]: AGC manual + // 0: Auto enable + // 1: Manual enable + // Bit[0]: AEC manual + // 0: Auto enable + // 1: Manual enable + +//gain = {0x350A[1:0], 0x350B[7:0]} / 16 + +/* mirror and flip registers */ +#define TIMING_TC_REG20 0x3820 // Timing Control Register + // Bit[2:1]: Vertical flip enable + // 00: Normal + // 11: Vertical flip + // Bit[0]: Vertical binning enable +#define TIMING_TC_REG21 0x3821 // Timing Control Register + // Bit[5]: Compression Enable + // Bit[2:1]: Horizontal mirror enable + // 00: Normal + // 11: Horizontal mirror + // Bit[0]: Horizontal binning enable + +#define CLOCK_POL_CONTROL 0x4740// Bit[5]: PCLK polarity 0: active low + // 1: active high + // Bit[3]: Gate PCLK under VSYNC + // Bit[2]: Gate PCLK under HREF + // Bit[1]: HREF polarity + // 0: active low + // 1: active high + // Bit[0] VSYNC polarity + // 0: active low + // 1: active high +#define DRIVE_CAPABILITY 0x302c // Bit[7:6]: + // 00: 1x + // 01: 2x + // 10: 3x + // 11: 4x + + +#define X_ADDR_ST_H 0x3800 //Bit[3:0]: X address start[11:8] +#define X_ADDR_ST_L 0x3801 //Bit[7:0]: X address start[7:0] +#define Y_ADDR_ST_H 0x3802 //Bit[2:0]: Y address start[10:8] +#define Y_ADDR_ST_L 0x3803 //Bit[7:0]: Y address start[7:0] +#define X_ADDR_END_H 0x3804 //Bit[3:0]: X address end[11:8] +#define X_ADDR_END_L 0x3805 //Bit[7:0]: +#define Y_ADDR_END_H 0x3806 //Bit[2:0]: Y address end[10:8] +#define Y_ADDR_END_L 0x3807 //Bit[7:0]: +// Size after scaling +#define X_OUTPUT_SIZE_H 0x3808 //Bit[3:0]: DVP output horizontal width[11:8] +#define X_OUTPUT_SIZE_L 0x3809 //Bit[7:0]: +#define Y_OUTPUT_SIZE_H 0x380a //Bit[2:0]: DVP output vertical height[10:8] +#define Y_OUTPUT_SIZE_L 0x380b //Bit[7:0]: +#define X_TOTAL_SIZE_H 0x380c //Bit[3:0]: Total horizontal size[11:8] +#define X_TOTAL_SIZE_L 0x380d //Bit[7:0]: +#define Y_TOTAL_SIZE_H 0x380e //Bit[7:0]: Total vertical size[15:8] +#define Y_TOTAL_SIZE_L 0x380f //Bit[7:0]: +#define X_OFFSET_H 0x3810 //Bit[3:0]: ISP horizontal offset[11:8] +#define X_OFFSET_L 0x3811 //Bit[7:0]: +#define Y_OFFSET_H 0x3812 //Bit[2:0]: ISP vertical offset[10:8] +#define Y_OFFSET_L 0x3813 //Bit[7:0]: +#define X_INCREMENT 0x3814 //Bit[7:4]: Horizontal odd subsample increment + //Bit[3:0]: Horizontal even subsample increment +#define Y_INCREMENT 0x3815 //Bit[7:4]: Vertical odd subsample increment + //Bit[3:0]: Vertical even subsample increment +// Size before scaling +//#define X_INPUT_SIZE (X_ADDR_END - X_ADDR_ST + 1 - (2 * X_OFFSET)) +//#define Y_INPUT_SIZE (Y_ADDR_END - Y_ADDR_ST + 1 - (2 * Y_OFFSET)) + +#define ISP_CONTROL_01 0x5001 // Bit[5]: Scale enable + // 0: Disable + // 1: Enable + +#define SCALE_CTRL_1 0x5601 // Bit[6:4]: HDIV RW + // DCW scale times + // 000: DCW 1 time + // 001: DCW 2 times + // 010: DCW 4 times + // 100: DCW 8 times + // 101: DCW 16 times + // Others: DCW 16 times + // Bit[2:0]: VDIV RW + // DCW scale times + // 000: DCW 1 time + // 001: DCW 2 times + // 010: DCW 4 times + // 100: DCW 8 times + // 101: DCW 16 times + // Others: DCW 16 times + +#define SCALE_CTRL_2 0x5602 // X_SCALE High Bits +#define SCALE_CTRL_3 0x5603 // X_SCALE Low Bits +#define SCALE_CTRL_4 0x5604 // Y_SCALE High Bits +#define SCALE_CTRL_5 0x5605 // Y_SCALE Low Bits +#define SCALE_CTRL_6 0x5606 // Bit[3:0]: V Offset + +#define PCLK_RATIO 0x3824 // Bit[4:0]: PCLK ratio manual +#define VFIFO_CTRL0C 0x460C // Bit[1]: PCLK manual enable + // 0: Auto + // 1: Manual by PCLK_RATIO + +#define VFIFO_X_SIZE_H 0x4602 +#define VFIFO_X_SIZE_L 0x4603 +#define VFIFO_Y_SIZE_H 0x4604 +#define VFIFO_Y_SIZE_L 0x4605 + +#define SC_PLLS_CTRL0 0x303a // Bit[7]: PLLS bypass +#define SC_PLLS_CTRL1 0x303b // Bit[4:0]: PLLS multiplier +#define SC_PLLS_CTRL2 0x303c // Bit[6:4]: PLLS charge pump control + // Bit[3:0]: PLLS system divider +#define SC_PLLS_CTRL3 0x303d // Bit[5:4]: PLLS pre-divider + // 00: 1 + // 01: 1.5 + // 10: 2 + // 11: 3 + // Bit[2]: PLLS root-divider - 1 + // Bit[1:0]: PLLS seld5 + // 00: 1 + // 01: 1 + // 10: 2 + // 11: 2.5 + +#define COMPRESSION_CTRL00 0x4400 // +#define COMPRESSION_CTRL01 0x4401 // +#define COMPRESSION_CTRL02 0x4402 // +#define COMPRESSION_CTRL03 0x4403 // +#define COMPRESSION_CTRL04 0x4404 // +#define COMPRESSION_CTRL05 0x4405 // +#define COMPRESSION_CTRL06 0x4406 // +#define COMPRESSION_CTRL07 0x4407 // Bit[5:0]: QS +#define COMPRESSION_ISI_CTRL 0x4408 // +#define COMPRESSION_CTRL09 0x4409 // +#define COMPRESSION_CTRL0a 0x440a // +#define COMPRESSION_CTRL0b 0x440b // +#define COMPRESSION_CTRL0c 0x440c // +#define COMPRESSION_CTRL0d 0x440d // +#define COMPRESSION_CTRL0E 0x440e // + +/** + * @brief register value + */ +#define TEST_COLOR_BAR 0xC0 /* Enable Color Bar roling Test */ + +#define AEC_PK_MANUAL_AGC_MANUALEN 0x02 /* Enable AGC Manual enable */ +#define AEC_PK_MANUAL_AEC_MANUALEN 0x01 /* Enable AEC Manual enable */ + +#define TIMING_TC_REG20_VFLIP 0x06 /* Vertical flip enable */ +#define TIMING_TC_REG21_HMIRROR 0x06 /* Horizontal mirror enable */ + +#endif // __OV3660_REG_REGS_H__ diff --git a/esp32-cam-rtos/ov3660_settings.h b/esp32-cam-rtos/ov3660_settings.h new file mode 100644 index 0000000..97c4e03 --- /dev/null +++ b/esp32-cam-rtos/ov3660_settings.h @@ -0,0 +1,318 @@ +#ifndef _OV3660_SETTINGS_H_ +#define _OV3660_SETTINGS_H_ + +#include +#include +#include "esp_attr.h" +#include "ov3660_regs.h" + +static const ratio_settings_t ratio_table[] = { + // mw, mh, sx, sy, ex, ey, ox, oy, tx, ty + { 2048, 1536, 0, 0, 2079, 1547, 16, 6, 2300, 1564 }, //4x3 + { 1920, 1280, 64, 128, 2015, 1419, 16, 6, 2172, 1436 }, //3x2 + { 2048, 1280, 0, 128, 2079, 1419, 16, 6, 2300, 1436 }, //16x10 + { 1920, 1152, 64, 192, 2015, 1355, 16, 6, 2172, 1372 }, //5x3 + { 1920, 1080, 64, 242, 2015, 1333, 16, 6, 2172, 1322 }, //16x9 + { 2048, 880, 0, 328, 2079, 1219, 16, 6, 2300, 1236 }, //21x9 + { 1920, 1536, 64, 0, 2015, 1547, 16, 6, 2172, 1564 }, //5x4 + { 1536, 1536, 256, 0, 1823, 1547, 16, 6, 2044, 1564 }, //1x1 + { 864, 1536, 592, 0, 1487, 1547, 16, 6, 2044, 1564 } //9x16 +}; + +#define REG_DLY 0xffff +#define REGLIST_TAIL 0x0000 + +static const DRAM_ATTR uint16_t sensor_default_regs[][2] = { + {SYSTEM_CTROL0, 0x82}, // software reset + {REG_DLY, 10}, // delay 10ms + + {0x3103, 0x13}, + {SYSTEM_CTROL0, 0x42}, + {0x3017, 0xff}, + {0x3018, 0xff}, + {DRIVE_CAPABILITY, 0xc3}, + {CLOCK_POL_CONTROL, 0x21}, + + {0x3611, 0x01}, + {0x3612, 0x2d}, + + {0x3032, 0x00}, + {0x3614, 0x80}, + {0x3618, 0x00}, + {0x3619, 0x75}, + {0x3622, 0x80}, + {0x3623, 0x00}, + {0x3624, 0x03}, + {0x3630, 0x52}, + {0x3632, 0x07}, + {0x3633, 0xd2}, + {0x3704, 0x80}, + {0x3708, 0x66}, + {0x3709, 0x12}, + {0x370b, 0x12}, + {0x3717, 0x00}, + {0x371b, 0x60}, + {0x371c, 0x00}, + {0x3901, 0x13}, + + {0x3600, 0x08}, + {0x3620, 0x43}, + {0x3702, 0x20}, + {0x3739, 0x48}, + {0x3730, 0x20}, + {0x370c, 0x0c}, + + {0x3a18, 0x00}, + {0x3a19, 0xf8}, + + {0x3000, 0x10}, + {0x3004, 0xef}, + + {0x6700, 0x05}, + {0x6701, 0x19}, + {0x6702, 0xfd}, + {0x6703, 0xd1}, + {0x6704, 0xff}, + {0x6705, 0xff}, + + {0x3c01, 0x80}, + {0x3c00, 0x04}, + {0x3a08, 0x00}, {0x3a09, 0x62}, //50Hz Band Width Step (10bit) + {0x3a0e, 0x08}, //50Hz Max Bands in One Frame (6 bit) + {0x3a0a, 0x00}, {0x3a0b, 0x52}, //60Hz Band Width Step (10bit) + {0x3a0d, 0x09}, //60Hz Max Bands in One Frame (6 bit) + + {0x3a00, 0x3a},//night mode off + {0x3a14, 0x09}, + {0x3a15, 0x30}, + {0x3a02, 0x09}, + {0x3a03, 0x30}, + + {COMPRESSION_CTRL0E, 0x08}, + {0x4520, 0x0b}, + {0x460b, 0x37}, + {0x4713, 0x02}, + {0x471c, 0xd0}, + {0x5086, 0x00}, + + {0x5002, 0x00}, + {0x501f, 0x00}, + + {SYSTEM_CTROL0, 0x02}, + + {0x5180, 0xff}, + {0x5181, 0xf2}, + {0x5182, 0x00}, + {0x5183, 0x14}, + {0x5184, 0x25}, + {0x5185, 0x24}, + {0x5186, 0x16}, + {0x5187, 0x16}, + {0x5188, 0x16}, + {0x5189, 0x68}, + {0x518a, 0x60}, + {0x518b, 0xe0}, + {0x518c, 0xb2}, + {0x518d, 0x42}, + {0x518e, 0x35}, + {0x518f, 0x56}, + {0x5190, 0x56}, + {0x5191, 0xf8}, + {0x5192, 0x04}, + {0x5193, 0x70}, + {0x5194, 0xf0}, + {0x5195, 0xf0}, + {0x5196, 0x03}, + {0x5197, 0x01}, + {0x5198, 0x04}, + {0x5199, 0x12}, + {0x519a, 0x04}, + {0x519b, 0x00}, + {0x519c, 0x06}, + {0x519d, 0x82}, + {0x519e, 0x38}, + + {0x5381, 0x1d}, + {0x5382, 0x60}, + {0x5383, 0x03}, + {0x5384, 0x0c}, + {0x5385, 0x78}, + {0x5386, 0x84}, + {0x5387, 0x7d}, + {0x5388, 0x6b}, + {0x5389, 0x12}, + {0x538a, 0x01}, + {0x538b, 0x98}, + + {0x5480, 0x01}, +// {0x5481, 0x05}, +// {0x5482, 0x09}, +// {0x5483, 0x10}, +// {0x5484, 0x3a}, +// {0x5485, 0x4c}, +// {0x5486, 0x5a}, +// {0x5487, 0x68}, +// {0x5488, 0x74}, +// {0x5489, 0x80}, +// {0x548a, 0x8e}, +// {0x548b, 0xa4}, +// {0x548c, 0xb4}, +// {0x548d, 0xc8}, +// {0x548e, 0xde}, +// {0x548f, 0xf0}, +// {0x5490, 0x15}, + + {0x5000, 0xa7}, + {0x5800, 0x0C}, + {0x5801, 0x09}, + {0x5802, 0x0C}, + {0x5803, 0x0C}, + {0x5804, 0x0D}, + {0x5805, 0x17}, + {0x5806, 0x06}, + {0x5807, 0x05}, + {0x5808, 0x04}, + {0x5809, 0x06}, + {0x580a, 0x09}, + {0x580b, 0x0E}, + {0x580c, 0x05}, + {0x580d, 0x01}, + {0x580e, 0x01}, + {0x580f, 0x01}, + {0x5810, 0x05}, + {0x5811, 0x0D}, + {0x5812, 0x05}, + {0x5813, 0x01}, + {0x5814, 0x01}, + {0x5815, 0x01}, + {0x5816, 0x05}, + {0x5817, 0x0D}, + {0x5818, 0x08}, + {0x5819, 0x06}, + {0x581a, 0x05}, + {0x581b, 0x07}, + {0x581c, 0x0B}, + {0x581d, 0x0D}, + {0x581e, 0x12}, + {0x581f, 0x0D}, + {0x5820, 0x0E}, + {0x5821, 0x10}, + {0x5822, 0x10}, + {0x5823, 0x1E}, + {0x5824, 0x53}, + {0x5825, 0x15}, + {0x5826, 0x05}, + {0x5827, 0x14}, + {0x5828, 0x54}, + {0x5829, 0x25}, + {0x582a, 0x33}, + {0x582b, 0x33}, + {0x582c, 0x34}, + {0x582d, 0x16}, + {0x582e, 0x24}, + {0x582f, 0x41}, + {0x5830, 0x50}, + {0x5831, 0x42}, + {0x5832, 0x15}, + {0x5833, 0x25}, + {0x5834, 0x34}, + {0x5835, 0x33}, + {0x5836, 0x24}, + {0x5837, 0x26}, + {0x5838, 0x54}, + {0x5839, 0x25}, + {0x583a, 0x15}, + {0x583b, 0x25}, + {0x583c, 0x53}, + {0x583d, 0xCF}, + + {0x3a0f, 0x30}, + {0x3a10, 0x28}, + {0x3a1b, 0x30}, + {0x3a1e, 0x28}, + {0x3a11, 0x60}, + {0x3a1f, 0x14}, + + {0x5302, 0x28}, + {0x5303, 0x20}, + + {0x5306, 0x1c}, //de-noise offset 1 + {0x5307, 0x28}, //de-noise offset 2 + + {0x4002, 0xc5}, + {0x4003, 0x81}, + {0x4005, 0x12}, + + {0x5688, 0x11}, + {0x5689, 0x11}, + {0x568a, 0x11}, + {0x568b, 0x11}, + {0x568c, 0x11}, + {0x568d, 0x11}, + {0x568e, 0x11}, + {0x568f, 0x11}, + + {0x5580, 0x06}, + {0x5588, 0x00}, + {0x5583, 0x40}, + {0x5584, 0x2c}, + + {ISP_CONTROL_01, 0x83}, // turn color matrix, awb and SDE + {REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_fmt_jpeg[][2] = { + {FORMAT_CTRL, 0x00}, // YUV422 + {FORMAT_CTRL00, 0x30}, // YUYV + {0x3002, 0x00},//0x1c to 0x00 !!! + {0x3006, 0xff},//0xc3 to 0xff !!! + {0x471c, 0x50},//0xd0 to 0x50 !!! + {REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_fmt_raw[][2] = { + {FORMAT_CTRL00, 0x00}, // RAW + {REGLIST_TAIL, 0x00} +}; + +static const DRAM_ATTR uint16_t sensor_fmt_grayscale[][2] = { + {FORMAT_CTRL, 0x00}, // YUV422 + {FORMAT_CTRL00, 0x10}, // Y8 + {REGLIST_TAIL, 0x00} +}; + +static const DRAM_ATTR uint16_t sensor_fmt_yuv422[][2] = { + {FORMAT_CTRL, 0x00}, // YUV422 + {FORMAT_CTRL00, 0x30}, // YUYV + {REGLIST_TAIL, 0x00} +}; + +static const DRAM_ATTR uint16_t sensor_fmt_rgb565[][2] = { + {FORMAT_CTRL, 0x01}, // RGB + {FORMAT_CTRL00, 0x61}, // RGB565 (BGR) + {REGLIST_TAIL, 0x00} +}; + +static const DRAM_ATTR uint8_t sensor_saturation_levels[9][11] = { + {0x1d, 0x60, 0x03, 0x07, 0x48, 0x4f, 0x4b, 0x40, 0x0b, 0x01, 0x98},//-4 + {0x1d, 0x60, 0x03, 0x08, 0x54, 0x5c, 0x58, 0x4b, 0x0d, 0x01, 0x98},//-3 + {0x1d, 0x60, 0x03, 0x0a, 0x60, 0x6a, 0x64, 0x56, 0x0e, 0x01, 0x98},//-2 + {0x1d, 0x60, 0x03, 0x0b, 0x6c, 0x77, 0x70, 0x60, 0x10, 0x01, 0x98},//-1 + {0x1d, 0x60, 0x03, 0x0c, 0x78, 0x84, 0x7d, 0x6b, 0x12, 0x01, 0x98},//0 + {0x1d, 0x60, 0x03, 0x0d, 0x84, 0x91, 0x8a, 0x76, 0x14, 0x01, 0x98},//+1 + {0x1d, 0x60, 0x03, 0x0e, 0x90, 0x9e, 0x96, 0x80, 0x16, 0x01, 0x98},//+2 + {0x1d, 0x60, 0x03, 0x10, 0x9c, 0xac, 0xa2, 0x8b, 0x17, 0x01, 0x98},//+3 + {0x1d, 0x60, 0x03, 0x11, 0xa8, 0xb9, 0xaf, 0x96, 0x19, 0x01, 0x98},//+4 +}; + +static const DRAM_ATTR uint8_t sensor_special_effects[7][4] = { + {0x06, 0x40, 0x2c, 0x08},//Normal + {0x46, 0x40, 0x28, 0x08},//Negative + {0x1e, 0x80, 0x80, 0x08},//Grayscale + {0x1e, 0x80, 0xc0, 0x08},//Red Tint + {0x1e, 0x60, 0x60, 0x08},//Green Tint + {0x1e, 0xa0, 0x40, 0x08},//Blue Tint + {0x1e, 0x40, 0xa0, 0x08},//Sepia +}; + +#endif diff --git a/esp32-cam-rtos/ov5640.c b/esp32-cam-rtos/ov5640.c new file mode 100644 index 0000000..e7adcf4 --- /dev/null +++ b/esp32-cam-rtos/ov5640.c @@ -0,0 +1,1105 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * OV3660 driver. + * + */ +#include +#include +#include +#include "sccb.h" +#include "ov5640.h" +#include "ov5640_regs.h" +#include "ov5640_settings.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#else +#include "esp_log.h" +static const char *TAG = "ov5640"; +#endif + +//#define REG_DEBUG_ON + +static int read_reg(uint8_t slv_addr, const uint16_t reg){ + int ret = SCCB_Read16(slv_addr, reg); +#ifdef REG_DEBUG_ON + if (ret < 0) { + ESP_LOGE(TAG, "READ REG 0x%04x FAILED: %d", reg, ret); + } +#endif + return ret; +} + +static int check_reg_mask(uint8_t slv_addr, uint16_t reg, uint8_t mask){ + return (read_reg(slv_addr, reg) & mask) == mask; +} + +static int read_reg16(uint8_t slv_addr, const uint16_t reg){ + int ret = 0, ret2 = 0; + ret = read_reg(slv_addr, reg); + if (ret >= 0) { + ret = (ret & 0xFF) << 8; + ret2 = read_reg(slv_addr, reg+1); + if (ret2 < 0) { + ret = ret2; + } else { + ret |= ret2 & 0xFF; + } + } + return ret; +} + +//static void dump_reg(sensor_t *sensor, const uint16_t reg){ +// int v = SCCB_Read16(sensor->slv_addr, reg); +// if(v < 0){ +// ets_printf(" 0x%04x: FAIL[%d]\n", reg, v); +// } else { +// ets_printf(" 0x%04x: 0x%02X\n", reg, v); +// } +//} +// +//static void dump_range(sensor_t *sensor, const char * name, const uint16_t start_reg, const uint16_t end_reg){ +// ets_printf("%s: 0x%04x - 0x%04X\n", name, start_reg, end_reg); +// for(uint16_t reg = start_reg; reg <= end_reg; reg++){ +// dump_reg(sensor, reg); +// } +//} +// +//static void dump_regs(sensor_t *sensor){ +//// dump_range(sensor, "All Regs", 0x3000, 0x6100); +//// dump_range(sensor, "system and IO pad control", 0x3000, 0x3052); +//// dump_range(sensor, "SCCB control", 0x3100, 0x3108); +//// dump_range(sensor, "SRB control", 0x3200, 0x3211); +//// dump_range(sensor, "AWB gain control", 0x3400, 0x3406); +//// dump_range(sensor, "AEC/AGC control", 0x3500, 0x350D); +//// dump_range(sensor, "VCM control", 0x3600, 0x3606); +//// dump_range(sensor, "timing control", 0x3800, 0x3821); +//// dump_range(sensor, "AEC/AGC power down domain control", 0x3A00, 0x3A25); +//// dump_range(sensor, "strobe control", 0x3B00, 0x3B0C); +//// dump_range(sensor, "50/60Hz detector control", 0x3C00, 0x3C1E); +//// dump_range(sensor, "OTP control", 0x3D00, 0x3D21); +//// dump_range(sensor, "MC control", 0x3F00, 0x3F0D); +//// dump_range(sensor, "BLC control", 0x4000, 0x4033); +//// dump_range(sensor, "frame control", 0x4201, 0x4202); +//// dump_range(sensor, "format control", 0x4300, 0x430D); +//// dump_range(sensor, "JPEG control", 0x4400, 0x4431); +//// dump_range(sensor, "VFIFO control", 0x4600, 0x460D); +//// dump_range(sensor, "DVP control", 0x4709, 0x4745); +//// dump_range(sensor, "MIPI control", 0x4800, 0x4837); +//// dump_range(sensor, "ISP frame control", 0x4901, 0x4902); +//// dump_range(sensor, "ISP top control", 0x5000, 0x5063); +//// dump_range(sensor, "AWB control", 0x5180, 0x51D0); +//// dump_range(sensor, "CIP control", 0x5300, 0x530F); +//// dump_range(sensor, "CMX control", 0x5380, 0x538B); +//// dump_range(sensor, "gamma control", 0x5480, 0x5490); +//// dump_range(sensor, "SDE control", 0x5580, 0x558C); +//// dump_range(sensor, "scale control", 0x5600, 0x5606); +//// dump_range(sensor, "AVG control", 0x5680, 0x56A2); +//// dump_range(sensor, "LENC control", 0x5800, 0x5849); +//// dump_range(sensor, "AFC control", 0x6000, 0x603F); +//} + +static int write_reg(uint8_t slv_addr, const uint16_t reg, uint8_t value){ + int ret = 0; +#ifndef REG_DEBUG_ON + ret = SCCB_Write16(slv_addr, reg, value); +#else + int old_value = read_reg(slv_addr, reg); + if (old_value < 0) { + return old_value; + } + if ((uint8_t)old_value != value) { + ESP_LOGI(TAG, "NEW REG 0x%04x: 0x%02x to 0x%02x", reg, (uint8_t)old_value, value); + ret = SCCB_Write16(slv_addr, reg, value); + } else { + ESP_LOGD(TAG, "OLD REG 0x%04x: 0x%02x", reg, (uint8_t)old_value); + ret = SCCB_Write16(slv_addr, reg, value);//maybe not? + } + if (ret < 0) { + ESP_LOGE(TAG, "WRITE REG 0x%04x FAILED: %d", reg, ret); + } +#endif + return ret; +} + +static int set_reg_bits(uint8_t slv_addr, uint16_t reg, uint8_t offset, uint8_t mask, uint8_t value) +{ + int ret = 0; + uint8_t c_value, new_value; + ret = read_reg(slv_addr, reg); + if(ret < 0) { + return ret; + } + c_value = ret; + new_value = (c_value & ~(mask << offset)) | ((value & mask) << offset); + ret = write_reg(slv_addr, reg, new_value); + return ret; +} + +static int write_regs(uint8_t slv_addr, const uint16_t (*regs)[2]) +{ + int i = 0, ret = 0; + while (!ret && regs[i][0] != REGLIST_TAIL) { + if (regs[i][0] == REG_DLY) { + vTaskDelay(regs[i][1] / portTICK_PERIOD_MS); + } else { + ret = write_reg(slv_addr, regs[i][0], regs[i][1]); + } + i++; + } + return ret; +} + +static int write_reg16(uint8_t slv_addr, const uint16_t reg, uint16_t value) +{ + if (write_reg(slv_addr, reg, value >> 8) || write_reg(slv_addr, reg + 1, value)) { + return -1; + } + return 0; +} + +static int write_addr_reg(uint8_t slv_addr, const uint16_t reg, uint16_t x_value, uint16_t y_value) +{ + if (write_reg16(slv_addr, reg, x_value) || write_reg16(slv_addr, reg + 2, y_value)) { + return -1; + } + return 0; +} + +#define write_reg_bits(slv_addr, reg, mask, enable) set_reg_bits(slv_addr, reg, 0, mask, (enable)?(mask):0) + +static int calc_sysclk(int xclk, bool pll_bypass, int pll_multiplier, int pll_sys_div, int pre_div, bool root_2x, int pclk_root_div, bool pclk_manual, int pclk_div) +{ + const float pll_pre_div2x_map[] = { 1, 1, 2, 3, 4, 1.5, 6, 2.5, 8}; + const int pll_pclk_root_div_map[] = { 1, 2, 4, 8 }; + + if(!pll_sys_div) { + pll_sys_div = 1; + } + + float pll_pre_div = pll_pre_div2x_map[pre_div]; + unsigned int root_2x_div = root_2x?2:1; + unsigned int pll_pclk_root_div = pll_pclk_root_div_map[pclk_root_div]; + + unsigned int REFIN = xclk / pll_pre_div; + + unsigned int VCO = REFIN * pll_multiplier / root_2x_div; + + unsigned int PLL_CLK = pll_bypass?(xclk):(VCO / pll_sys_div * 2 / 5);//5 here is 10bit mode / 2, for 8bit it should be 4 (reg 0x3034) + + unsigned int PCLK = PLL_CLK / pll_pclk_root_div / ((pclk_manual && pclk_div)?pclk_div:2); + + unsigned int SYSCLK = PLL_CLK / 4; + + ESP_LOGD(TAG, "Calculated XVCLK: %d Hz, REFIN: %u Hz, VCO: %u Hz, PLL_CLK: %u Hz, SYSCLK: %u Hz, PCLK: %u Hz", xclk, REFIN, VCO, PLL_CLK, SYSCLK, PCLK); + return SYSCLK; +} + +static int set_pll(sensor_t *sensor, bool bypass, uint8_t multiplier, uint8_t sys_div, uint8_t pre_div, bool root_2x, uint8_t pclk_root_div, bool pclk_manual, uint8_t pclk_div){ + int ret = 0; + if(multiplier > 252 || multiplier < 4 || sys_div > 15 || pre_div > 8 || pclk_div > 31 || pclk_root_div > 3){ + ESP_LOGE(TAG, "Invalid arguments"); + return -1; + } + if(multiplier > 127){ + multiplier &= 0xFE;//only even integers above 127 + } + + calc_sysclk(sensor->xclk_freq_hz, bypass, multiplier, sys_div, pre_div, root_2x, pclk_root_div, pclk_manual, pclk_div); + + ret = write_reg(sensor->slv_addr, 0x3039, bypass?0x80:0x00); + if (ret == 0) { + ret = write_reg(sensor->slv_addr, 0x3034, 0x1A);//10bit mode + } + if (ret == 0) { + ret = write_reg(sensor->slv_addr, 0x3035, 0x01 | ((sys_div & 0x0f) << 4)); + } + if (ret == 0) { + ret = write_reg(sensor->slv_addr, 0x3036, multiplier & 0xff); + } + if (ret == 0) { + ret = write_reg(sensor->slv_addr, 0x3037, (pre_div & 0xf) | (root_2x?0x10:0x00)); + } + if (ret == 0) { + ret = write_reg(sensor->slv_addr, 0x3108, (pclk_root_div & 0x3) << 4 | 0x06); + } + if (ret == 0) { + ret = write_reg(sensor->slv_addr, 0x3824, pclk_div & 0x1f); + } + if (ret == 0) { + ret = write_reg(sensor->slv_addr, 0x460C, pclk_manual?0x22:0x20); + } + if (ret == 0) { + ret = write_reg(sensor->slv_addr, 0x3103, 0x13);// system clock from pll, bit[1] + } + if(ret){ + ESP_LOGE(TAG, "set_sensor_pll FAILED!"); + } + return ret; +} + +static int set_ae_level(sensor_t *sensor, int level); + +static int reset(sensor_t *sensor) +{ + //dump_regs(sensor); + vTaskDelay(100 / portTICK_PERIOD_MS); + int ret = 0; + // Software Reset: clear all registers and reset them to their default values + ret = write_reg(sensor->slv_addr, SYSTEM_CTROL0, 0x82); + if(ret){ + ESP_LOGE(TAG, "Software Reset FAILED!"); + return ret; + } + vTaskDelay(100 / portTICK_PERIOD_MS); + ret = write_regs(sensor->slv_addr, sensor_default_regs); + if (ret == 0) { + ESP_LOGD(TAG, "Camera defaults loaded"); + vTaskDelay(100 / portTICK_PERIOD_MS); + //write_regs(sensor->slv_addr, sensor_regs_awb0); + //write_regs(sensor->slv_addr, sensor_regs_gamma1); + } + return ret; +} + +static int set_pixformat(sensor_t *sensor, pixformat_t pixformat) +{ + int ret = 0; + const uint16_t (*regs)[2]; + + switch (pixformat) { + case PIXFORMAT_YUV422: + regs = sensor_fmt_yuv422; + break; + + case PIXFORMAT_GRAYSCALE: + regs = sensor_fmt_grayscale; + break; + + case PIXFORMAT_RGB565: + case PIXFORMAT_RGB888: + regs = sensor_fmt_rgb565; + break; + + case PIXFORMAT_JPEG: + regs = sensor_fmt_jpeg; + break; + + case PIXFORMAT_RAW: + regs = sensor_fmt_raw; + break; + + default: + ESP_LOGE(TAG, "Unsupported pixformat: %u", pixformat); + return -1; + } + + ret = write_regs(sensor->slv_addr, regs); + if(ret == 0) { + sensor->pixformat = pixformat; + ESP_LOGD(TAG, "Set pixformat to: %u", pixformat); + } + return ret; +} + +static int set_image_options(sensor_t *sensor) +{ + int ret = 0; + uint8_t reg20 = 0; + uint8_t reg21 = 0; + uint8_t reg4514 = 0; + uint8_t reg4514_test = 0; + + // compression + if (sensor->pixformat == PIXFORMAT_JPEG) { + reg21 |= 0x20; + } + + // binning + if (!sensor->status.binning) { + reg20 |= 0x40; + } else { + reg20 |= 0x01; + reg21 |= 0x01; + reg4514_test |= 4; + } + + // V-Flip + if (sensor->status.vflip) { + reg20 |= 0x06; + reg4514_test |= 1; + } + + // H-Mirror + if (sensor->status.hmirror) { + reg21 |= 0x06; + reg4514_test |= 2; + } + + switch (reg4514_test) { + //no binning + case 0: reg4514 = 0x88; break;//normal + case 1: reg4514 = 0x00; break;//v-flip + case 2: reg4514 = 0xbb; break;//h-mirror + case 3: reg4514 = 0x00; break;//v-flip+h-mirror + //binning + case 4: reg4514 = 0xaa; break;//normal + case 5: reg4514 = 0xbb; break;//v-flip + case 6: reg4514 = 0xbb; break;//h-mirror + case 7: reg4514 = 0xaa; break;//v-flip+h-mirror + } + + if(write_reg(sensor->slv_addr, TIMING_TC_REG20, reg20) + || write_reg(sensor->slv_addr, TIMING_TC_REG21, reg21) + || write_reg(sensor->slv_addr, 0x4514, reg4514)){ + ESP_LOGE(TAG, "Setting Image Options Failed"); + return -1; + } + + if (!sensor->status.binning) { + ret = write_reg(sensor->slv_addr, 0x4520, 0x10) + || write_reg(sensor->slv_addr, X_INCREMENT, 0x11)//odd:1, even: 1 + || write_reg(sensor->slv_addr, Y_INCREMENT, 0x11);//odd:1, even: 1 + } else { + ret = write_reg(sensor->slv_addr, 0x4520, 0x0b) + || write_reg(sensor->slv_addr, X_INCREMENT, 0x31)//odd:3, even: 1 + || write_reg(sensor->slv_addr, Y_INCREMENT, 0x31);//odd:3, even: 1 + } + + ESP_LOGD(TAG, "Set Image Options: Compression: %u, Binning: %u, V-Flip: %u, H-Mirror: %u, Reg-4514: 0x%02x", + sensor->pixformat == PIXFORMAT_JPEG, sensor->status.binning, sensor->status.vflip, sensor->status.hmirror, reg4514); + return ret; +} + +static int set_framesize(sensor_t *sensor, framesize_t framesize) +{ + int ret = 0; + framesize_t old_framesize = sensor->status.framesize; + sensor->status.framesize = framesize; + + if(framesize > FRAMESIZE_QSXGA){ + ESP_LOGE(TAG, "Invalid framesize: %u", framesize); + return -1; + } + uint16_t w = resolution[framesize].width; + uint16_t h = resolution[framesize].height; + aspect_ratio_t ratio = resolution[framesize].aspect_ratio; + ratio_settings_t settings = ratio_table[ratio]; + + sensor->status.binning = (w <= (settings.max_width / 2) && h <= (settings.max_height / 2)); + sensor->status.scale = !((w == settings.max_width && h == settings.max_height) + || (w == (settings.max_width / 2) && h == (settings.max_height / 2))); + + ret = write_addr_reg(sensor->slv_addr, X_ADDR_ST_H, settings.start_x, settings.start_y) + || write_addr_reg(sensor->slv_addr, X_ADDR_END_H, settings.end_x, settings.end_y) + || write_addr_reg(sensor->slv_addr, X_OUTPUT_SIZE_H, w, h); + + if (ret) { + goto fail; + } + + if (!sensor->status.binning) { + ret = write_addr_reg(sensor->slv_addr, X_TOTAL_SIZE_H, settings.total_x, settings.total_y) + || write_addr_reg(sensor->slv_addr, X_OFFSET_H, settings.offset_x, settings.offset_y); + } else { + if (w > 920) { + ret = write_addr_reg(sensor->slv_addr, X_TOTAL_SIZE_H, settings.total_x - 200, settings.total_y / 2); + } else { + ret = write_addr_reg(sensor->slv_addr, X_TOTAL_SIZE_H, 2060, settings.total_y / 2); + } + if (ret == 0) { + ret = write_addr_reg(sensor->slv_addr, X_OFFSET_H, settings.offset_x / 2, settings.offset_y / 2); + } + } + + if (ret == 0) { + ret = write_reg_bits(sensor->slv_addr, ISP_CONTROL_01, 0x20, sensor->status.scale); + } + + if (ret == 0) { + ret = set_image_options(sensor); + } + + if (ret) { + goto fail; + } + + if (sensor->pixformat == PIXFORMAT_JPEG) { + //10MHz PCLK + uint8_t sys_mul = 200; + if(framesize < FRAMESIZE_QVGA){ + sys_mul = 160; + } else if(framesize < FRAMESIZE_XGA){ + sys_mul = 180; + } + ret = set_pll(sensor, false, sys_mul, 4, 2, false, 2, true, 4); + } else { + ret = set_pll(sensor, false, 10, 1, 1, false, 1, true, 4); + } + + if (ret == 0) { + ESP_LOGD(TAG, "Set framesize to: %ux%u", w, h); + } + return ret; + +fail: + sensor->status.framesize = old_framesize; + ESP_LOGE(TAG, "Setting framesize to: %ux%u failed", w, h); + return ret; +} + +static int set_hmirror(sensor_t *sensor, int enable) +{ + int ret = 0; + sensor->status.hmirror = enable; + ret = set_image_options(sensor); + if (ret == 0) { + ESP_LOGD(TAG, "Set h-mirror to: %d", enable); + } + return ret; +} + +static int set_vflip(sensor_t *sensor, int enable) +{ + int ret = 0; + sensor->status.vflip = enable; + ret = set_image_options(sensor); + if (ret == 0) { + ESP_LOGD(TAG, "Set v-flip to: %d", enable); + } + return ret; +} + +static int set_quality(sensor_t *sensor, int qs) +{ + int ret = 0; + ret = write_reg(sensor->slv_addr, COMPRESSION_CTRL07, qs & 0x3f); + if (ret == 0) { + sensor->status.quality = qs; + ESP_LOGD(TAG, "Set quality to: %d", qs); + } + return ret; +} + +static int set_colorbar(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, PRE_ISP_TEST_SETTING_1, TEST_COLOR_BAR, enable); + if (ret == 0) { + sensor->status.colorbar = enable; + ESP_LOGD(TAG, "Set colorbar to: %d", enable); + } + return ret; +} + +static int set_gain_ctrl(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, AEC_PK_MANUAL, AEC_PK_MANUAL_AGC_MANUALEN, !enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set gain_ctrl to: %d", enable); + sensor->status.agc = enable; + } + return ret; +} + +static int set_exposure_ctrl(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, AEC_PK_MANUAL, AEC_PK_MANUAL_AEC_MANUALEN, !enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set exposure_ctrl to: %d", enable); + sensor->status.aec = enable; + } + return ret; +} + +static int set_whitebal(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, ISP_CONTROL_01, 0x01, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set awb to: %d", enable); + sensor->status.awb = enable; + } + return ret; +} + +//Advanced AWB +static int set_dcw_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, 0x5183, 0x80, !enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set dcw to: %d", enable); + sensor->status.dcw = enable; + } + return ret; +} + +//night mode enable +static int set_aec2(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, 0x3a00, 0x04, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set aec2 to: %d", enable); + sensor->status.aec2 = enable; + } + return ret; +} + +static int set_bpc_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, 0x5000, 0x04, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set bpc to: %d", enable); + sensor->status.bpc = enable; + } + return ret; +} + +static int set_wpc_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, 0x5000, 0x02, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set wpc to: %d", enable); + sensor->status.wpc = enable; + } + return ret; +} + +//Gamma enable +static int set_raw_gma_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, 0x5000, 0x20, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set raw_gma to: %d", enable); + sensor->status.raw_gma = enable; + } + return ret; +} + +static int set_lenc_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg_bits(sensor->slv_addr, 0x5000, 0x80, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set lenc to: %d", enable); + sensor->status.lenc = enable; + } + return ret; +} + +static int get_agc_gain(sensor_t *sensor) +{ + int ra = read_reg(sensor->slv_addr, 0x350a); + if (ra < 0) { + return 0; + } + int rb = read_reg(sensor->slv_addr, 0x350b); + if (rb < 0) { + return 0; + } + int res = (rb & 0xF0) >> 4 | (ra & 0x03) << 4; + if (rb & 0x0F) { + res += 1; + } + return res; +} + +//real gain +static int set_agc_gain(sensor_t *sensor, int gain) +{ + int ret = 0; + if(gain < 0) { + gain = 0; + } else if(gain > 64) { + gain = 64; + } + + //gain value is 6.4 bits float + //in order to use the max range, we deduct 1/16 + int gainv = gain << 4; + if(gainv){ + gainv -= 1; + } + + ret = write_reg(sensor->slv_addr, 0x350a, gainv >> 8) || write_reg(sensor->slv_addr, 0x350b, gainv & 0xff); + if (ret == 0) { + ESP_LOGD(TAG, "Set agc_gain to: %d", gain); + sensor->status.agc_gain = gain; + } + return ret; +} + +static int get_aec_value(sensor_t *sensor) +{ + int ra = read_reg(sensor->slv_addr, 0x3500); + if (ra < 0) { + return 0; + } + int rb = read_reg(sensor->slv_addr, 0x3501); + if (rb < 0) { + return 0; + } + int rc = read_reg(sensor->slv_addr, 0x3502); + if (rc < 0) { + return 0; + } + int res = (ra & 0x0F) << 12 | (rb & 0xFF) << 4 | (rc & 0xF0) >> 4; + return res; +} + +static int set_aec_value(sensor_t *sensor, int value) +{ + int ret = 0, max_val = 0; + max_val = read_reg16(sensor->slv_addr, 0x380e); + if (max_val < 0) { + ESP_LOGE(TAG, "Could not read max aec_value"); + return -1; + } + if (value > max_val) { + value =max_val; + } + + ret = write_reg(sensor->slv_addr, 0x3500, (value >> 12) & 0x0F) + || write_reg(sensor->slv_addr, 0x3501, (value >> 4) & 0xFF) + || write_reg(sensor->slv_addr, 0x3502, (value << 4) & 0xF0); + + if (ret == 0) { + ESP_LOGD(TAG, "Set aec_value to: %d / %d", value, max_val); + sensor->status.aec_value = value; + } + return ret; +} + +static int set_ae_level(sensor_t *sensor, int level) +{ + int ret = 0; + if (level < -5 || level > 5) { + return -1; + } + //good targets are between 5 and 115 + int target_level = ((level + 5) * 10) + 5; + + int level_high, level_low; + int fast_high, fast_low; + + level_low = target_level * 23 / 25; //0.92 (0.46) + level_high = target_level * 27 / 25; //1.08 (2.08) + + fast_low = level_low >> 1; + fast_high = level_high << 1; + + if(fast_high>255) { + fast_high = 255; + } + + ret = write_reg(sensor->slv_addr, 0x3a0f, level_high) + || write_reg(sensor->slv_addr, 0x3a10, level_low) + || write_reg(sensor->slv_addr, 0x3a1b, level_high) + || write_reg(sensor->slv_addr, 0x3a1e, level_low) + || write_reg(sensor->slv_addr, 0x3a11, fast_high) + || write_reg(sensor->slv_addr, 0x3a1f, fast_low); + + if (ret == 0) { + ESP_LOGD(TAG, "Set ae_level to: %d", level); + sensor->status.ae_level = level; + } + return ret; +} + +static int set_wb_mode(sensor_t *sensor, int mode) +{ + int ret = 0; + if (mode < 0 || mode > 4) { + return -1; + } + + ret = write_reg(sensor->slv_addr, 0x3406, (mode != 0)); + if (ret) { + return ret; + } + switch (mode) { + case 1://Sunny + ret = write_reg16(sensor->slv_addr, 0x3400, 0x5e0) //AWB R GAIN + || write_reg16(sensor->slv_addr, 0x3402, 0x410) //AWB G GAIN + || write_reg16(sensor->slv_addr, 0x3404, 0x540);//AWB B GAIN + break; + case 2://Cloudy + ret = write_reg16(sensor->slv_addr, 0x3400, 0x650) //AWB R GAIN + || write_reg16(sensor->slv_addr, 0x3402, 0x410) //AWB G GAIN + || write_reg16(sensor->slv_addr, 0x3404, 0x4f0);//AWB B GAIN + break; + case 3://Office + ret = write_reg16(sensor->slv_addr, 0x3400, 0x520) //AWB R GAIN + || write_reg16(sensor->slv_addr, 0x3402, 0x410) //AWB G GAIN + || write_reg16(sensor->slv_addr, 0x3404, 0x660);//AWB B GAIN + break; + case 4://HOME + ret = write_reg16(sensor->slv_addr, 0x3400, 0x420) //AWB R GAIN + || write_reg16(sensor->slv_addr, 0x3402, 0x3f0) //AWB G GAIN + || write_reg16(sensor->slv_addr, 0x3404, 0x710);//AWB B GAIN + break; + default://AUTO + break; + } + + if (ret == 0) { + ESP_LOGD(TAG, "Set wb_mode to: %d", mode); + sensor->status.wb_mode = mode; + } + return ret; +} + +static int set_awb_gain_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + int old_mode = sensor->status.wb_mode; + int mode = enable?old_mode:0; + + ret = set_wb_mode(sensor, mode); + + if (ret == 0) { + sensor->status.wb_mode = old_mode; + ESP_LOGD(TAG, "Set awb_gain to: %d", enable); + sensor->status.awb_gain = enable; + } + return ret; +} + +static int set_special_effect(sensor_t *sensor, int effect) +{ + int ret=0; + if (effect < 0 || effect > 6) { + return -1; + } + + uint8_t * regs = (uint8_t *)sensor_special_effects[effect]; + ret = write_reg(sensor->slv_addr, 0x5580, regs[0]) + || write_reg(sensor->slv_addr, 0x5583, regs[1]) + || write_reg(sensor->slv_addr, 0x5584, regs[2]) + || write_reg(sensor->slv_addr, 0x5003, regs[3]); + + if (ret == 0) { + ESP_LOGD(TAG, "Set special_effect to: %d", effect); + sensor->status.special_effect = effect; + } + return ret; +} + +static int set_brightness(sensor_t *sensor, int level) +{ + int ret = 0; + uint8_t value = 0; + bool negative = false; + + switch (level) { + case 3: + value = 0x30; + break; + case 2: + value = 0x20; + break; + case 1: + value = 0x10; + break; + case -1: + value = 0x10; + negative = true; + break; + case -2: + value = 0x20; + negative = true; + break; + case -3: + value = 0x30; + negative = true; + break; + default: // 0 + break; + } + + ret = write_reg(sensor->slv_addr, 0x5587, value); + if (ret == 0) { + ret = write_reg_bits(sensor->slv_addr, 0x5588, 0x08, negative); + } + + if (ret == 0) { + ESP_LOGD(TAG, "Set brightness to: %d", level); + sensor->status.brightness = level; + } + return ret; +} + +static int set_contrast(sensor_t *sensor, int level) +{ + int ret = 0; + if(level > 3 || level < -3) { + return -1; + } + ret = write_reg(sensor->slv_addr, 0x5586, (level + 4) << 3); + + if (ret == 0) { + ESP_LOGD(TAG, "Set contrast to: %d", level); + sensor->status.contrast = level; + } + return ret; +} + +static int set_saturation(sensor_t *sensor, int level) +{ + int ret = 0; + if(level > 4 || level < -4) { + return -1; + } + + uint8_t * regs = (uint8_t *)sensor_saturation_levels[level+4]; + for(int i=0; i<11; i++) { + ret = write_reg(sensor->slv_addr, 0x5381 + i, regs[i]); + if (ret) { + break; + } + } + + if (ret == 0) { + ESP_LOGD(TAG, "Set saturation to: %d", level); + sensor->status.saturation = level; + } + return ret; +} + +static int set_sharpness(sensor_t *sensor, int level) +{ + int ret = 0; + if(level > 3 || level < -3) { + return -1; + } + + uint8_t mt_offset_2 = (level + 3) * 8; + uint8_t mt_offset_1 = mt_offset_2 + 1; + + ret = write_reg_bits(sensor->slv_addr, 0x5308, 0x40, false)//0x40 means auto + || write_reg(sensor->slv_addr, 0x5300, 0x10) + || write_reg(sensor->slv_addr, 0x5301, 0x10) + || write_reg(sensor->slv_addr, 0x5302, mt_offset_1) + || write_reg(sensor->slv_addr, 0x5303, mt_offset_2) + || write_reg(sensor->slv_addr, 0x5309, 0x10) + || write_reg(sensor->slv_addr, 0x530a, 0x10) + || write_reg(sensor->slv_addr, 0x530b, 0x04) + || write_reg(sensor->slv_addr, 0x530c, 0x06); + + if (ret == 0) { + ESP_LOGD(TAG, "Set sharpness to: %d", level); + sensor->status.sharpness = level; + } + return ret; +} + +static int set_gainceiling(sensor_t *sensor, gainceiling_t level) +{ + int ret = 0, l = (int)level; + + ret = write_reg(sensor->slv_addr, 0x3A18, (l >> 8) & 3) + || write_reg(sensor->slv_addr, 0x3A19, l & 0xFF); + + if (ret == 0) { + ESP_LOGD(TAG, "Set gainceiling to: %d", l); + sensor->status.gainceiling = l; + } + return ret; +} + +static int get_denoise(sensor_t *sensor) +{ + if (!check_reg_mask(sensor->slv_addr, 0x5308, 0x10)) { + return 0; + } + return (read_reg(sensor->slv_addr, 0x5306) / 4) + 1; +} + +static int set_denoise(sensor_t *sensor, int level) +{ + int ret = 0; + if (level < 0 || level > 8) { + return -1; + } + + ret = write_reg_bits(sensor->slv_addr, 0x5308, 0x10, level > 0); + if (ret == 0 && level > 0) { + ret = write_reg(sensor->slv_addr, 0x5306, (level - 1) * 4); + } + + if (ret == 0) { + ESP_LOGD(TAG, "Set denoise to: %d", level); + sensor->status.denoise = level; + } + return ret; +} + +static int get_reg(sensor_t *sensor, int reg, int mask) +{ + int ret = 0, ret2 = 0; + if(mask > 0xFF){ + ret = read_reg16(sensor->slv_addr, reg); + if(ret >= 0 && mask > 0xFFFF){ + ret2 = read_reg(sensor->slv_addr, reg+2); + if(ret2 >= 0){ + ret = (ret << 8) | ret2 ; + } else { + ret = ret2; + } + } + } else { + ret = read_reg(sensor->slv_addr, reg); + } + if(ret > 0){ + ret &= mask; + } + return ret; +} + +static int set_reg(sensor_t *sensor, int reg, int mask, int value) +{ + int ret = 0, ret2 = 0; + if(mask > 0xFF){ + ret = read_reg16(sensor->slv_addr, reg); + if(ret >= 0 && mask > 0xFFFF){ + ret2 = read_reg(sensor->slv_addr, reg+2); + if(ret2 >= 0){ + ret = (ret << 8) | ret2 ; + } else { + ret = ret2; + } + } + } else { + ret = read_reg(sensor->slv_addr, reg); + } + if(ret < 0){ + return ret; + } + value = (ret & ~mask) | (value & mask); + if(mask > 0xFFFF){ + ret = write_reg16(sensor->slv_addr, reg, value >> 8); + if(ret >= 0){ + ret = write_reg(sensor->slv_addr, reg+2, value & 0xFF); + } + } else if(mask > 0xFF){ + ret = write_reg16(sensor->slv_addr, reg, value); + } else { + ret = write_reg(sensor->slv_addr, reg, value); + } + return ret; +} + +static int set_res_raw(sensor_t *sensor, int startX, int startY, int endX, int endY, int offsetX, int offsetY, int totalX, int totalY, int outputX, int outputY, bool scale, bool binning) +{ + int ret = 0; + ret = write_addr_reg(sensor->slv_addr, X_ADDR_ST_H, startX, startY) + || write_addr_reg(sensor->slv_addr, X_ADDR_END_H, endX, endY) + || write_addr_reg(sensor->slv_addr, X_OFFSET_H, offsetX, offsetY) + || write_addr_reg(sensor->slv_addr, X_TOTAL_SIZE_H, totalX, totalY) + || write_addr_reg(sensor->slv_addr, X_OUTPUT_SIZE_H, outputX, outputY) + || write_reg_bits(sensor->slv_addr, ISP_CONTROL_01, 0x20, scale); + if(!ret){ + sensor->status.scale = scale; + sensor->status.binning = binning; + ret = set_image_options(sensor); + } + return ret; +} + +static int _set_pll(sensor_t *sensor, int bypass, int multiplier, int sys_div, int root_2x, int pre_div, int seld5, int pclk_manual, int pclk_div) +{ + int ret = 0; + ret = set_pll(sensor, bypass > 0, multiplier, sys_div, pre_div, root_2x > 0, seld5, pclk_manual > 0, pclk_div); + return ret; +} + +esp_err_t xclk_timer_conf(int ledc_timer, int xclk_freq_hz); +static int set_xclk(sensor_t *sensor, int timer, int xclk) +{ + int ret = 0; + sensor->xclk_freq_hz = xclk * 1000000U; + ret = xclk_timer_conf(timer, sensor->xclk_freq_hz); + return ret; +} + +static int init_status(sensor_t *sensor) +{ + sensor->status.brightness = 0; + sensor->status.contrast = 0; + sensor->status.saturation = 0; + sensor->status.sharpness = (read_reg(sensor->slv_addr, 0x5303) / 8) - 3; + sensor->status.denoise = get_denoise(sensor); + sensor->status.ae_level = 0; + sensor->status.gainceiling = read_reg16(sensor->slv_addr, 0x3A18) & 0x3FF; + sensor->status.awb = check_reg_mask(sensor->slv_addr, ISP_CONTROL_01, 0x01); + sensor->status.dcw = !check_reg_mask(sensor->slv_addr, 0x5183, 0x80); + sensor->status.agc = !check_reg_mask(sensor->slv_addr, AEC_PK_MANUAL, AEC_PK_MANUAL_AGC_MANUALEN); + sensor->status.aec = !check_reg_mask(sensor->slv_addr, AEC_PK_MANUAL, AEC_PK_MANUAL_AEC_MANUALEN); + sensor->status.hmirror = check_reg_mask(sensor->slv_addr, TIMING_TC_REG21, TIMING_TC_REG21_HMIRROR); + sensor->status.vflip = check_reg_mask(sensor->slv_addr, TIMING_TC_REG20, TIMING_TC_REG20_VFLIP); + sensor->status.colorbar = check_reg_mask(sensor->slv_addr, PRE_ISP_TEST_SETTING_1, TEST_COLOR_BAR); + sensor->status.bpc = check_reg_mask(sensor->slv_addr, 0x5000, 0x04); + sensor->status.wpc = check_reg_mask(sensor->slv_addr, 0x5000, 0x02); + sensor->status.raw_gma = check_reg_mask(sensor->slv_addr, 0x5000, 0x20); + sensor->status.lenc = check_reg_mask(sensor->slv_addr, 0x5000, 0x80); + sensor->status.quality = read_reg(sensor->slv_addr, COMPRESSION_CTRL07) & 0x3f; + sensor->status.special_effect = 0; + sensor->status.wb_mode = 0; + sensor->status.awb_gain = check_reg_mask(sensor->slv_addr, 0x3406, 0x01); + sensor->status.agc_gain = get_agc_gain(sensor); + sensor->status.aec_value = get_aec_value(sensor); + sensor->status.aec2 = check_reg_mask(sensor->slv_addr, 0x3a00, 0x04); + return 0; +} + +int ov5640_init(sensor_t *sensor) +{ + sensor->reset = reset; + sensor->set_pixformat = set_pixformat; + sensor->set_framesize = set_framesize; + sensor->set_contrast = set_contrast; + sensor->set_brightness = set_brightness; + sensor->set_saturation = set_saturation; + sensor->set_sharpness = set_sharpness; + sensor->set_gainceiling = set_gainceiling; + sensor->set_quality = set_quality; + sensor->set_colorbar = set_colorbar; + sensor->set_gain_ctrl = set_gain_ctrl; + sensor->set_exposure_ctrl = set_exposure_ctrl; + sensor->set_whitebal = set_whitebal; + sensor->set_hmirror = set_hmirror; + sensor->set_vflip = set_vflip; + sensor->init_status = init_status; + sensor->set_aec2 = set_aec2; + sensor->set_aec_value = set_aec_value; + sensor->set_special_effect = set_special_effect; + sensor->set_wb_mode = set_wb_mode; + sensor->set_ae_level = set_ae_level; + sensor->set_dcw = set_dcw_dsp; + sensor->set_bpc = set_bpc_dsp; + sensor->set_wpc = set_wpc_dsp; + sensor->set_awb_gain = set_awb_gain_dsp; + sensor->set_agc_gain = set_agc_gain; + sensor->set_raw_gma = set_raw_gma_dsp; + sensor->set_lenc = set_lenc_dsp; + sensor->set_denoise = set_denoise; + + sensor->get_reg = get_reg; + sensor->set_reg = set_reg; + sensor->set_res_raw = set_res_raw; + sensor->set_pll = _set_pll; + sensor->set_xclk = set_xclk; + return 0; +} diff --git a/esp32-cam-rtos/ov5640.h b/esp32-cam-rtos/ov5640.h new file mode 100644 index 0000000..7b572ad --- /dev/null +++ b/esp32-cam-rtos/ov5640.h @@ -0,0 +1,9 @@ + +#ifndef __OV5640_H__ +#define __OV5640_H__ + +#include "sensor.h" + +int ov5640_init(sensor_t *sensor); + +#endif // __OV5640_H__ diff --git a/esp32-cam-rtos/ov5640_regs.h b/esp32-cam-rtos/ov5640_regs.h new file mode 100644 index 0000000..c28d80f --- /dev/null +++ b/esp32-cam-rtos/ov5640_regs.h @@ -0,0 +1,213 @@ +/* + * OV5640 register definitions. + */ +#ifndef __OV5640_REG_REGS_H__ +#define __OV5640_REG_REGS_H__ + +/* system control registers */ +#define SYSTEM_CTROL0 0x3008 // Bit[7]: Software reset + // Bit[6]: Software power down + // Bit[5]: Reserved + // Bit[4]: SRB clock SYNC enable + // Bit[3]: Isolation suspend select + // Bit[2:0]: Not used + +#define DRIVE_CAPABILITY 0x302c // Bit[7:6]: + // 00: 1x + // 01: 2x + // 10: 3x + // 11: 4x + +#define SC_PLLS_CTRL0 0x303a // Bit[7]: PLLS bypass +#define SC_PLLS_CTRL1 0x303b // Bit[4:0]: PLLS multiplier +#define SC_PLLS_CTRL2 0x303c // Bit[6:4]: PLLS charge pump control + // Bit[3:0]: PLLS system divider +#define SC_PLLS_CTRL3 0x303d // Bit[5:4]: PLLS pre-divider + // 00: 1 + // 01: 1.5 + // 10: 2 + // 11: 3 + // Bit[2]: PLLS root-divider - 1 + // Bit[1:0]: PLLS seld5 + // 00: 1 + // 01: 1 + // 10: 2 + // 11: 2.5 + +/* AEC/AGC control functions */ +#define AEC_PK_MANUAL 0x3503 // AEC Manual Mode Control + // Bit[7:6]: Reserved + // Bit[5]: Gain delay option + // Valid when 0x3503[4]=1’b0 + // 0: Delay one frame latch + // 1: One frame latch + // Bit[4:2]: Reserved + // Bit[1]: AGC manual + // 0: Auto enable + // 1: Manual enable + // Bit[0]: AEC manual + // 0: Auto enable + // 1: Manual enable + +//gain = {0x350A[1:0], 0x350B[7:0]} / 16 + + +#define X_ADDR_ST_H 0x3800 //Bit[3:0]: X address start[11:8] +#define X_ADDR_ST_L 0x3801 //Bit[7:0]: X address start[7:0] +#define Y_ADDR_ST_H 0x3802 //Bit[2:0]: Y address start[10:8] +#define Y_ADDR_ST_L 0x3803 //Bit[7:0]: Y address start[7:0] +#define X_ADDR_END_H 0x3804 //Bit[3:0]: X address end[11:8] +#define X_ADDR_END_L 0x3805 //Bit[7:0]: +#define Y_ADDR_END_H 0x3806 //Bit[2:0]: Y address end[10:8] +#define Y_ADDR_END_L 0x3807 //Bit[7:0]: +// Size after scaling +#define X_OUTPUT_SIZE_H 0x3808 //Bit[3:0]: DVP output horizontal width[11:8] +#define X_OUTPUT_SIZE_L 0x3809 //Bit[7:0]: +#define Y_OUTPUT_SIZE_H 0x380a //Bit[2:0]: DVP output vertical height[10:8] +#define Y_OUTPUT_SIZE_L 0x380b //Bit[7:0]: +#define X_TOTAL_SIZE_H 0x380c //Bit[3:0]: Total horizontal size[11:8] +#define X_TOTAL_SIZE_L 0x380d //Bit[7:0]: +#define Y_TOTAL_SIZE_H 0x380e //Bit[7:0]: Total vertical size[15:8] +#define Y_TOTAL_SIZE_L 0x380f //Bit[7:0]: +#define X_OFFSET_H 0x3810 //Bit[3:0]: ISP horizontal offset[11:8] +#define X_OFFSET_L 0x3811 //Bit[7:0]: +#define Y_OFFSET_H 0x3812 //Bit[2:0]: ISP vertical offset[10:8] +#define Y_OFFSET_L 0x3813 //Bit[7:0]: +#define X_INCREMENT 0x3814 //Bit[7:4]: Horizontal odd subsample increment + //Bit[3:0]: Horizontal even subsample increment +#define Y_INCREMENT 0x3815 //Bit[7:4]: Vertical odd subsample increment + //Bit[3:0]: Vertical even subsample increment +// Size before scaling +//#define X_INPUT_SIZE (X_ADDR_END - X_ADDR_ST + 1 - (2 * X_OFFSET)) +//#define Y_INPUT_SIZE (Y_ADDR_END - Y_ADDR_ST + 1 - (2 * Y_OFFSET)) + +/* mirror and flip registers */ +#define TIMING_TC_REG20 0x3820 // Timing Control Register + // Bit[2:1]: Vertical flip enable + // 00: Normal + // 11: Vertical flip + // Bit[0]: Vertical binning enable +#define TIMING_TC_REG21 0x3821 // Timing Control Register + // Bit[5]: Compression Enable + // Bit[2:1]: Horizontal mirror enable + // 00: Normal + // 11: Horizontal mirror + // Bit[0]: Horizontal binning enable + +#define PCLK_RATIO 0x3824 // Bit[4:0]: PCLK ratio manual + +/* frame control registers */ +#define FRAME_CTRL01 0x4201 // Control Passed Frame Number When both ON and OFF number set to 0x00,frame control is in bypass mode + // Bit[7:4]: Not used + // Bit[3:0]: Frame ON number +#define FRAME_CTRL02 0x4202 // Control Masked Frame Number When both ON and OFF number set to 0x00,frame control is in bypass mode + // Bit[7:4]: Not used + // BIT[3:0]: Frame OFF number + +/* format control registers */ +#define FORMAT_CTRL00 0x4300 + +#define CLOCK_POL_CONTROL 0x4740// Bit[5]: PCLK polarity 0: active low + // 1: active high + // Bit[3]: Gate PCLK under VSYNC + // Bit[2]: Gate PCLK under HREF + // Bit[1]: HREF polarity + // 0: active low + // 1: active high + // Bit[0] VSYNC polarity + // 0: active low + // 1: active high + +#define ISP_CONTROL_01 0x5001 // Bit[5]: Scale enable + // 0: Disable + // 1: Enable + +/* output format control registers */ +#define FORMAT_CTRL 0x501F // Format select + // Bit[2:0]: + // 000: YUV422 + // 001: RGB + // 010: Dither + // 011: RAW after DPC + // 101: RAW after CIP + +/* ISP top control registers */ +#define PRE_ISP_TEST_SETTING_1 0x503D // Bit[7]: Test enable + // 0: Test disable + // 1: Color bar enable + // Bit[6]: Rolling + // Bit[5]: Transparent + // Bit[4]: Square black and white + // Bit[3:2]: Color bar style + // 00: Standard 8 color bar + // 01: Gradual change at vertical mode 1 + // 10: Gradual change at horizontal + // 11: Gradual change at vertical mode 2 + // Bit[1:0]: Test select + // 00: Color bar + // 01: Random data + // 10: Square data + // 11: Black image + +//exposure = {0x3500[3:0], 0x3501[7:0], 0x3502[7:0]} / 16 × tROW + +#define SCALE_CTRL_1 0x5601 // Bit[6:4]: HDIV RW + // DCW scale times + // 000: DCW 1 time + // 001: DCW 2 times + // 010: DCW 4 times + // 100: DCW 8 times + // 101: DCW 16 times + // Others: DCW 16 times + // Bit[2:0]: VDIV RW + // DCW scale times + // 000: DCW 1 time + // 001: DCW 2 times + // 010: DCW 4 times + // 100: DCW 8 times + // 101: DCW 16 times + // Others: DCW 16 times + +#define SCALE_CTRL_2 0x5602 // X_SCALE High Bits +#define SCALE_CTRL_3 0x5603 // X_SCALE Low Bits +#define SCALE_CTRL_4 0x5604 // Y_SCALE High Bits +#define SCALE_CTRL_5 0x5605 // Y_SCALE Low Bits +#define SCALE_CTRL_6 0x5606 // Bit[3:0]: V Offset + +#define VFIFO_CTRL0C 0x460C // Bit[1]: PCLK manual enable + // 0: Auto + // 1: Manual by PCLK_RATIO + +#define VFIFO_X_SIZE_H 0x4602 +#define VFIFO_X_SIZE_L 0x4603 +#define VFIFO_Y_SIZE_H 0x4604 +#define VFIFO_Y_SIZE_L 0x4605 + +#define COMPRESSION_CTRL00 0x4400 // +#define COMPRESSION_CTRL01 0x4401 // +#define COMPRESSION_CTRL02 0x4402 // +#define COMPRESSION_CTRL03 0x4403 // +#define COMPRESSION_CTRL04 0x4404 // +#define COMPRESSION_CTRL05 0x4405 // +#define COMPRESSION_CTRL06 0x4406 // +#define COMPRESSION_CTRL07 0x4407 // Bit[5:0]: QS +#define COMPRESSION_ISI_CTRL 0x4408 // +#define COMPRESSION_CTRL09 0x4409 // +#define COMPRESSION_CTRL0a 0x440a // +#define COMPRESSION_CTRL0b 0x440b // +#define COMPRESSION_CTRL0c 0x440c // +#define COMPRESSION_CTRL0d 0x440d // +#define COMPRESSION_CTRL0E 0x440e // + +/** + * @brief register value + */ +#define TEST_COLOR_BAR 0xC0 /* Enable Color Bar roling Test */ + +#define AEC_PK_MANUAL_AGC_MANUALEN 0x02 /* Enable AGC Manual enable */ +#define AEC_PK_MANUAL_AEC_MANUALEN 0x01 /* Enable AEC Manual enable */ + +#define TIMING_TC_REG20_VFLIP 0x06 /* Vertical flip enable */ +#define TIMING_TC_REG21_HMIRROR 0x06 /* Horizontal mirror enable */ + +#endif // __OV3660_REG_REGS_H__ diff --git a/esp32-cam-rtos/ov5640_settings.h b/esp32-cam-rtos/ov5640_settings.h new file mode 100644 index 0000000..fec7d67 --- /dev/null +++ b/esp32-cam-rtos/ov5640_settings.h @@ -0,0 +1,334 @@ +#ifndef _OV5640_SETTINGS_H_ +#define _OV5640_SETTINGS_H_ + +#include +#include +#include "esp_attr.h" +#include "ov5640_regs.h" + +static const ratio_settings_t ratio_table[] = { + // mw, mh, sx, sy, ex, ey, ox, oy, tx, ty + { 2560, 1920, 0, 0, 2623, 1951, 32, 16, 2844, 1968 }, //4x3 + { 2560, 1704, 0, 110, 2623, 1843, 32, 16, 2844, 1752 }, //3x2 + { 2560, 1600, 0, 160, 2623, 1791, 32, 16, 2844, 1648 }, //16x10 + { 2560, 1536, 0, 192, 2623, 1759, 32, 16, 2844, 1584 }, //5x3 + { 2560, 1440, 0, 240, 2623, 1711, 32, 16, 2844, 1488 }, //16x9 + { 2560, 1080, 0, 420, 2623, 1531, 32, 16, 2844, 1128 }, //21x9 + { 2400, 1920, 80, 0, 2543, 1951, 32, 16, 2684, 1968 }, //5x4 + { 1920, 1920, 320, 0, 2543, 1951, 32, 16, 2684, 1968 }, //1x1 + { 1088, 1920, 736, 0, 1887, 1951, 32, 16, 1884, 1968 } //9x16 +}; + +#define REG_DLY 0xffff +#define REGLIST_TAIL 0x0000 + +static const DRAM_ATTR uint16_t sensor_default_regs[][2] = { + {SYSTEM_CTROL0, 0x82}, // software reset + {REG_DLY, 10}, // delay 10ms + {SYSTEM_CTROL0, 0x42}, // power down + + //enable pll + {0x3103, 0x13}, + + //io direction + {0x3017, 0xff}, + {0x3018, 0xff}, + + {DRIVE_CAPABILITY, 0xc3}, + {CLOCK_POL_CONTROL, 0x21}, + + {0x4713, 0x02},//jpg mode select + + {ISP_CONTROL_01, 0x83}, // turn color matrix, awb and SDE + + //sys reset + {0x3000, 0x00}, + {0x3002, 0x1c}, + + //clock enable + {0x3004, 0xff}, + {0x3006, 0xc3}, + + //isp control + {0x5000, 0xa7}, + {ISP_CONTROL_01, 0xa3},//+scaling? + {0x5003, 0x08},//special_effect + + //unknown + {0x370c, 0x02},//!!IMPORTANT + {0x3634, 0x40},//!!IMPORTANT + + //AEC/AGC + {0x3a02, 0x03}, + {0x3a03, 0xd8}, + {0x3a08, 0x01}, + {0x3a09, 0x27}, + {0x3a0a, 0x00}, + {0x3a0b, 0xf6}, + {0x3a0d, 0x04}, + {0x3a0e, 0x03}, + {0x3a0f, 0x30},//ae_level + {0x3a10, 0x28},//ae_level + {0x3a11, 0x60},//ae_level + {0x3a13, 0x43}, + {0x3a14, 0x03}, + {0x3a15, 0xd8}, + {0x3a18, 0x00},//gainceiling + {0x3a19, 0xf8},//gainceiling + {0x3a1b, 0x30},//ae_level + {0x3a1e, 0x26},//ae_level + {0x3a1f, 0x14},//ae_level + + //vcm debug + {0x3600, 0x08}, + {0x3601, 0x33}, + + //50/60Hz + {0x3c01, 0xa4}, + {0x3c04, 0x28}, + {0x3c05, 0x98}, + {0x3c06, 0x00}, + {0x3c07, 0x08}, + {0x3c08, 0x00}, + {0x3c09, 0x1c}, + {0x3c0a, 0x9c}, + {0x3c0b, 0x40}, + + {0x460c, 0x22},//disable jpeg footer + + //BLC + {0x4001, 0x02}, + {0x4004, 0x02}, + + //AWB + {0x5180, 0xff}, + {0x5181, 0xf2}, + {0x5182, 0x00}, + {0x5183, 0x14}, + {0x5184, 0x25}, + {0x5185, 0x24}, + {0x5186, 0x09}, + {0x5187, 0x09}, + {0x5188, 0x09}, + {0x5189, 0x75}, + {0x518a, 0x54}, + {0x518b, 0xe0}, + {0x518c, 0xb2}, + {0x518d, 0x42}, + {0x518e, 0x3d}, + {0x518f, 0x56}, + {0x5190, 0x46}, + {0x5191, 0xf8}, + {0x5192, 0x04}, + {0x5193, 0x70}, + {0x5194, 0xf0}, + {0x5195, 0xf0}, + {0x5196, 0x03}, + {0x5197, 0x01}, + {0x5198, 0x04}, + {0x5199, 0x12}, + {0x519a, 0x04}, + {0x519b, 0x00}, + {0x519c, 0x06}, + {0x519d, 0x82}, + {0x519e, 0x38}, + + //color matrix (Saturation) + {0x5381, 0x1e}, + {0x5382, 0x5b}, + {0x5383, 0x08}, + {0x5384, 0x0a}, + {0x5385, 0x7e}, + {0x5386, 0x88}, + {0x5387, 0x7c}, + {0x5388, 0x6c}, + {0x5389, 0x10}, + {0x538a, 0x01}, + {0x538b, 0x98}, + + //CIP control (Sharpness) + {0x5300, 0x10},//sharpness + {0x5301, 0x10},//sharpness + {0x5302, 0x18},//sharpness + {0x5303, 0x19},//sharpness + {0x5304, 0x10}, + {0x5305, 0x10}, + {0x5306, 0x08},//denoise + {0x5307, 0x16}, + {0x5308, 0x40}, + {0x5309, 0x10},//sharpness + {0x530a, 0x10},//sharpness + {0x530b, 0x04},//sharpness + {0x530c, 0x06},//sharpness + + //GAMMA + {0x5480, 0x01}, + {0x5481, 0x00}, + {0x5482, 0x1e}, + {0x5483, 0x3b}, + {0x5484, 0x58}, + {0x5485, 0x66}, + {0x5486, 0x71}, + {0x5487, 0x7d}, + {0x5488, 0x83}, + {0x5489, 0x8f}, + {0x548a, 0x98}, + {0x548b, 0xa6}, + {0x548c, 0xb8}, + {0x548d, 0xca}, + {0x548e, 0xd7}, + {0x548f, 0xe3}, + {0x5490, 0x1d}, + + //Special Digital Effects (SDE) (UV adjust) + {0x5580, 0x06},//enable brightness and contrast + {0x5583, 0x40},//special_effect + {0x5584, 0x10},//special_effect + {0x5586, 0x20},//contrast + {0x5587, 0x00},//brightness + {0x5588, 0x00},//brightness + {0x5589, 0x10}, + {0x558a, 0x00}, + {0x558b, 0xf8}, + {0x501d, 0x40},// enable manual offset of contrast + + //power on + {0x3008, 0x02}, + + //50Hz + {0x3c00, 0x04}, + + {REG_DLY, 300}, + {REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_fmt_jpeg[][2] = { + {FORMAT_CTRL, 0x00}, // YUV422 + {FORMAT_CTRL00, 0x30}, // YUYV + {0x3002, 0x00},//0x1c to 0x00 !!! + {0x3006, 0xff},//0xc3 to 0xff !!! + {0x471c, 0x50},//0xd0 to 0x50 !!! + {REGLIST_TAIL, 0x00}, // tail +}; + +static const DRAM_ATTR uint16_t sensor_fmt_raw[][2] = { + {FORMAT_CTRL, 0x03}, // RAW (DPC) + {FORMAT_CTRL00, 0x00}, // RAW + {REGLIST_TAIL, 0x00} +}; + +static const DRAM_ATTR uint16_t sensor_fmt_grayscale[][2] = { + {FORMAT_CTRL, 0x00}, // YUV422 + {FORMAT_CTRL00, 0x10}, // Y8 + {REGLIST_TAIL, 0x00} +}; + +static const DRAM_ATTR uint16_t sensor_fmt_yuv422[][2] = { + {FORMAT_CTRL, 0x00}, // YUV422 + {FORMAT_CTRL00, 0x30}, // YUYV + {REGLIST_TAIL, 0x00} +}; + +static const DRAM_ATTR uint16_t sensor_fmt_rgb565[][2] = { + {FORMAT_CTRL, 0x01}, // RGB + {FORMAT_CTRL00, 0x61}, // RGB565 (BGR) + {REGLIST_TAIL, 0x00} +}; + +static const DRAM_ATTR uint8_t sensor_saturation_levels[9][11] = { + {0x1d, 0x60, 0x03, 0x07, 0x48, 0x4f, 0x4b, 0x40, 0x0b, 0x01, 0x98},//-4 + {0x1d, 0x60, 0x03, 0x08, 0x54, 0x5c, 0x58, 0x4b, 0x0d, 0x01, 0x98},//-3 + {0x1d, 0x60, 0x03, 0x0a, 0x60, 0x6a, 0x64, 0x56, 0x0e, 0x01, 0x98},//-2 + {0x1d, 0x60, 0x03, 0x0b, 0x6c, 0x77, 0x70, 0x60, 0x10, 0x01, 0x98},//-1 + {0x1d, 0x60, 0x03, 0x0c, 0x78, 0x84, 0x7d, 0x6b, 0x12, 0x01, 0x98},//0 + {0x1d, 0x60, 0x03, 0x0d, 0x84, 0x91, 0x8a, 0x76, 0x14, 0x01, 0x98},//+1 + {0x1d, 0x60, 0x03, 0x0e, 0x90, 0x9e, 0x96, 0x80, 0x16, 0x01, 0x98},//+2 + {0x1d, 0x60, 0x03, 0x10, 0x9c, 0xac, 0xa2, 0x8b, 0x17, 0x01, 0x98},//+3 + {0x1d, 0x60, 0x03, 0x11, 0xa8, 0xb9, 0xaf, 0x96, 0x19, 0x01, 0x98},//+4 +}; + +static const DRAM_ATTR uint8_t sensor_special_effects[7][4] = { + {0x06, 0x40, 0x2c, 0x08},//Normal + {0x46, 0x40, 0x28, 0x08},//Negative + {0x1e, 0x80, 0x80, 0x08},//Grayscale + {0x1e, 0x80, 0xc0, 0x08},//Red Tint + {0x1e, 0x60, 0x60, 0x08},//Green Tint + {0x1e, 0xa0, 0x40, 0x08},//Blue Tint + {0x1e, 0x40, 0xa0, 0x08},//Sepia +}; + +static const DRAM_ATTR uint16_t sensor_regs_gamma0[][2] = { + {0x5480, 0x01}, + {0x5481, 0x08}, + {0x5482, 0x14}, + {0x5483, 0x28}, + {0x5484, 0x51}, + {0x5485, 0x65}, + {0x5486, 0x71}, + {0x5487, 0x7d}, + {0x5488, 0x87}, + {0x5489, 0x91}, + {0x548a, 0x9a}, + {0x548b, 0xaa}, + {0x548c, 0xb8}, + {0x548d, 0xcd}, + {0x548e, 0xdd}, + {0x548f, 0xea}, + {0x5490, 0x1d} +}; + +static const DRAM_ATTR uint16_t sensor_regs_gamma1[][2] = { + {0x5480, 0x1}, + {0x5481, 0x0}, + {0x5482, 0x1e}, + {0x5483, 0x3b}, + {0x5484, 0x58}, + {0x5485, 0x66}, + {0x5486, 0x71}, + {0x5487, 0x7d}, + {0x5488, 0x83}, + {0x5489, 0x8f}, + {0x548a, 0x98}, + {0x548b, 0xa6}, + {0x548c, 0xb8}, + {0x548d, 0xca}, + {0x548e, 0xd7}, + {0x548f, 0xe3}, + {0x5490, 0x1d} +}; + +static const DRAM_ATTR uint16_t sensor_regs_awb0[][2] = { + {0x5180, 0xff}, + {0x5181, 0xf2}, + {0x5182, 0x00}, + {0x5183, 0x14}, + {0x5184, 0x25}, + {0x5185, 0x24}, + {0x5186, 0x09}, + {0x5187, 0x09}, + {0x5188, 0x09}, + {0x5189, 0x75}, + {0x518a, 0x54}, + {0x518b, 0xe0}, + {0x518c, 0xb2}, + {0x518d, 0x42}, + {0x518e, 0x3d}, + {0x518f, 0x56}, + {0x5190, 0x46}, + {0x5191, 0xf8}, + {0x5192, 0x04}, + {0x5193, 0x70}, + {0x5194, 0xf0}, + {0x5195, 0xf0}, + {0x5196, 0x03}, + {0x5197, 0x01}, + {0x5198, 0x04}, + {0x5199, 0x12}, + {0x519a, 0x04}, + {0x519b, 0x00}, + {0x519c, 0x06}, + {0x519d, 0x82}, + {0x519e, 0x38} +}; + +#endif diff --git a/esp32-cam-rtos/ov7725.c b/esp32-cam-rtos/ov7725.c new file mode 100644 index 0000000..bb31573 --- /dev/null +++ b/esp32-cam-rtos/ov7725.c @@ -0,0 +1,557 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * OV7725 driver. + * + */ +#include +#include +#include +#include +#include "sccb.h" +#include "ov7725.h" +#include "ov7725_regs.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#else +#include "esp_log.h" +static const char* TAG = "ov7725"; +#endif + + +static const uint8_t default_regs[][2] = { + {COM3, COM3_SWAP_YUV}, + {COM7, COM7_RES_QVGA | COM7_FMT_YUV}, + + {COM4, 0x01 | 0x00}, /* bypass PLL (0x00:off, 0x40:4x, 0x80:6x, 0xC0:8x) */ + {CLKRC, 0x80 | 0x03}, /* Res/Bypass pre-scalar (0x40:bypass, 0x00-0x3F:prescaler PCLK=XCLK/(prescaler + 1)/2 ) */ + + // QVGA Window Size + {HSTART, 0x3F}, + {HSIZE, 0x50}, + {VSTART, 0x03}, + {VSIZE, 0x78}, + {HREF, 0x00}, + + // Scale down to QVGA Resolution + {HOUTSIZE, 0x50}, + {VOUTSIZE, 0x78}, + {EXHCH, 0x00}, + + {COM12, 0x03}, + {TGT_B, 0x7F}, + {FIXGAIN, 0x09}, + {AWB_CTRL0, 0xE0}, + {DSP_CTRL1, 0xFF}, + + {DSP_CTRL2, DSP_CTRL2_VDCW_EN | DSP_CTRL2_HDCW_EN | DSP_CTRL2_HZOOM_EN | DSP_CTRL2_VZOOM_EN}, + + {DSP_CTRL3, 0x00}, + {DSP_CTRL4, 0x00}, + {DSPAUTO, 0xFF}, + + {COM8, 0xF0}, + {COM6, 0xC5}, + {COM9, 0x11}, + {COM10, COM10_VSYNC_NEG | COM10_PCLK_MASK}, //Invert VSYNC and MASK PCLK + {BDBASE, 0x7F}, + {DBSTEP, 0x03}, + {AEW, 0x96}, + {AEB, 0x64}, + {VPT, 0xA1}, + {EXHCL, 0x00}, + {AWB_CTRL3, 0xAA}, + {COM8, 0xFF}, + + //Gamma + {GAM1, 0x0C}, + {GAM2, 0x16}, + {GAM3, 0x2A}, + {GAM4, 0x4E}, + {GAM5, 0x61}, + {GAM6, 0x6F}, + {GAM7, 0x7B}, + {GAM8, 0x86}, + {GAM9, 0x8E}, + {GAM10, 0x97}, + {GAM11, 0xA4}, + {GAM12, 0xAF}, + {GAM13, 0xC5}, + {GAM14, 0xD7}, + {GAM15, 0xE8}, + + {SLOP, 0x20}, + {EDGE1, 0x05}, + {EDGE2, 0x03}, + {EDGE3, 0x00}, + {DNSOFF, 0x01}, + + {MTX1, 0xB0}, + {MTX2, 0x9D}, + {MTX3, 0x13}, + {MTX4, 0x16}, + {MTX5, 0x7B}, + {MTX6, 0x91}, + {MTX_CTRL, 0x1E}, + + {BRIGHTNESS, 0x08}, + {CONTRAST, 0x30}, + {UVADJ0, 0x81}, + {SDE, (SDE_CONT_BRIGHT_EN | SDE_SATURATION_EN)}, + + // For 30 fps/60Hz + {DM_LNL, 0x00}, + {DM_LNH, 0x00}, + {BDBASE, 0x7F}, + {DBSTEP, 0x03}, + + // Lens Correction, should be tuned with real camera module + {LC_RADI, 0x10}, + {LC_COEF, 0x10}, + {LC_COEFB, 0x14}, + {LC_COEFR, 0x17}, + {LC_CTR, 0x05}, + {COM5, 0xF5}, //0x65 + + {0x00, 0x00}, +}; + +static int get_reg(sensor_t *sensor, int reg, int mask) +{ + int ret = SCCB_Read(sensor->slv_addr, reg & 0xFF); + if(ret > 0){ + ret &= mask; + } + return ret; +} + +static int set_reg(sensor_t *sensor, int reg, int mask, int value) +{ + int ret = 0; + ret = SCCB_Read(sensor->slv_addr, reg & 0xFF); + if(ret < 0){ + return ret; + } + value = (ret & ~mask) | (value & mask); + ret = SCCB_Write(sensor->slv_addr, reg & 0xFF, value); + return ret; +} + +static int set_reg_bits(sensor_t *sensor, uint8_t reg, uint8_t offset, uint8_t length, uint8_t value) +{ + int ret = 0; + ret = SCCB_Read(sensor->slv_addr, reg); + if(ret < 0){ + return ret; + } + uint8_t mask = ((1 << length) - 1) << offset; + value = (ret & ~mask) | ((value << offset) & mask); + ret = SCCB_Write(sensor->slv_addr, reg & 0xFF, value); + return ret; +} + +static int get_reg_bits(sensor_t *sensor, uint8_t reg, uint8_t offset, uint8_t length) +{ + int ret = 0; + ret = SCCB_Read(sensor->slv_addr, reg); + if(ret < 0){ + return ret; + } + uint8_t mask = ((1 << length) - 1) << offset; + return (ret & mask) >> offset; +} + + +static int reset(sensor_t *sensor) +{ + int i=0; + const uint8_t (*regs)[2]; + + // Reset all registers + SCCB_Write(sensor->slv_addr, COM7, COM7_RESET); + + // Delay 10 ms + vTaskDelay(10 / portTICK_PERIOD_MS); + + // Write default regsiters + for (i=0, regs = default_regs; regs[i][0]; i++) { + SCCB_Write(sensor->slv_addr, regs[i][0], regs[i][1]); + } + + // Delay + vTaskDelay(30 / portTICK_PERIOD_MS); + + return 0; +} + + +static int set_pixformat(sensor_t *sensor, pixformat_t pixformat) +{ + int ret=0; + sensor->pixformat = pixformat; + // Read register COM7 + uint8_t reg = SCCB_Read(sensor->slv_addr, COM7); + + switch (pixformat) { + case PIXFORMAT_RGB565: + reg = COM7_SET_RGB(reg, COM7_FMT_RGB565); + break; + case PIXFORMAT_YUV422: + case PIXFORMAT_GRAYSCALE: + reg = COM7_SET_FMT(reg, COM7_FMT_YUV); + break; + default: + return -1; + } + + // Write back register COM7 + ret = SCCB_Write(sensor->slv_addr, COM7, reg); + + // Delay + vTaskDelay(30 / portTICK_PERIOD_MS); + + return ret; +} + +static int set_framesize(sensor_t *sensor, framesize_t framesize) +{ + int ret=0; + if (framesize > FRAMESIZE_VGA) { + return -1; + } + uint16_t w = resolution[framesize].width; + uint16_t h = resolution[framesize].height; + uint8_t reg = SCCB_Read(sensor->slv_addr, COM7); + + sensor->status.framesize = framesize; + + // Write MSBs + ret |= SCCB_Write(sensor->slv_addr, HOUTSIZE, w>>2); + ret |= SCCB_Write(sensor->slv_addr, VOUTSIZE, h>>1); + + ret |= SCCB_Write(sensor->slv_addr, HSIZE, w>>2); + ret |= SCCB_Write(sensor->slv_addr, VSIZE, h>>1); + + // Write LSBs + ret |= SCCB_Write(sensor->slv_addr, HREF, ((w&0x3) | ((h&0x1) << 2))); + + if (framesize < FRAMESIZE_VGA) { + // Enable auto-scaling/zooming factors + ret |= SCCB_Write(sensor->slv_addr, DSPAUTO, 0xFF); + + ret |= SCCB_Write(sensor->slv_addr, HSTART, 0x3F); + ret |= SCCB_Write(sensor->slv_addr, VSTART, 0x03); + + ret |= SCCB_Write(sensor->slv_addr, COM7, reg | COM7_RES_QVGA); + + ret |= SCCB_Write(sensor->slv_addr, CLKRC, 0x80 | 0x01); + + } else { + // Disable auto-scaling/zooming factors + ret |= SCCB_Write(sensor->slv_addr, DSPAUTO, 0xF3); + + // Clear auto-scaling/zooming factors + ret |= SCCB_Write(sensor->slv_addr, SCAL0, 0x00); + ret |= SCCB_Write(sensor->slv_addr, SCAL1, 0x00); + ret |= SCCB_Write(sensor->slv_addr, SCAL2, 0x00); + + ret |= SCCB_Write(sensor->slv_addr, HSTART, 0x23); + ret |= SCCB_Write(sensor->slv_addr, VSTART, 0x07); + + ret |= SCCB_Write(sensor->slv_addr, COM7, reg & ~COM7_RES_QVGA); + + ret |= SCCB_Write(sensor->slv_addr, CLKRC, 0x80 | 0x03); + } + + // Delay + vTaskDelay(30 / portTICK_PERIOD_MS); + + return ret; +} + +static int set_colorbar(sensor_t *sensor, int enable) +{ + int ret=0; + uint8_t reg; + sensor->status.colorbar = enable; + + // Read reg COM3 + reg = SCCB_Read(sensor->slv_addr, COM3); + // Enable colorbar test pattern output + reg = COM3_SET_CBAR(reg, enable); + // Write back COM3 + ret |= SCCB_Write(sensor->slv_addr, COM3, reg); + + // Read reg DSP_CTRL3 + reg = SCCB_Read(sensor->slv_addr, DSP_CTRL3); + // Enable DSP colorbar output + reg = DSP_CTRL3_SET_CBAR(reg, enable); + // Write back DSP_CTRL3 + ret |= SCCB_Write(sensor->slv_addr, DSP_CTRL3, reg); + + return ret; +} + +static int set_whitebal(sensor_t *sensor, int enable) +{ + if(set_reg_bits(sensor, COM8, 1, 1, enable) >= 0){ + sensor->status.awb = !!enable; + } + return sensor->status.awb; +} + +static int set_gain_ctrl(sensor_t *sensor, int enable) +{ + if(set_reg_bits(sensor, COM8, 2, 1, enable) >= 0){ + sensor->status.agc = !!enable; + } + return sensor->status.agc; +} + +static int set_exposure_ctrl(sensor_t *sensor, int enable) +{ + if(set_reg_bits(sensor, COM8, 0, 1, enable) >= 0){ + sensor->status.aec = !!enable; + } + return sensor->status.aec; +} + +static int set_hmirror(sensor_t *sensor, int enable) +{ + if(set_reg_bits(sensor, COM3, 6, 1, enable) >= 0){ + sensor->status.hmirror = !!enable; + } + return sensor->status.hmirror; +} + +static int set_vflip(sensor_t *sensor, int enable) +{ + if(set_reg_bits(sensor, COM3, 7, 1, enable) >= 0){ + sensor->status.vflip = !!enable; + } + return sensor->status.vflip; +} + +static int set_dcw_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = set_reg_bits(sensor, 0x65, 2, 1, !enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set dcw to: %d", enable); + sensor->status.dcw = enable; + } + return ret; +} + +static int set_aec2(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = set_reg_bits(sensor, COM8, 7, 1, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set aec2 to: %d", enable); + sensor->status.aec2 = enable; + } + return ret; +} + +static int set_bpc_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = set_reg_bits(sensor, 0x64, 1, 1, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set bpc to: %d", enable); + sensor->status.bpc = enable; + } + return ret; +} + +static int set_wpc_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = set_reg_bits(sensor, 0x64, 0, 1, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set wpc to: %d", enable); + sensor->status.wpc = enable; + } + return ret; +} + +static int set_raw_gma_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = set_reg_bits(sensor, 0x64, 2, 1, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set raw_gma to: %d", enable); + sensor->status.raw_gma = enable; + } + return ret; +} + +static int set_lenc_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = set_reg_bits(sensor, LC_CTR, 0, 1, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set lenc to: %d", enable); + sensor->status.lenc = enable; + } + return ret; +} + +//real gain +static int set_agc_gain(sensor_t *sensor, int gain) +{ + int ret = 0; + ret = set_reg_bits(sensor, COM9, 4, 3, gain % 5); + if (ret == 0) { + ESP_LOGD(TAG, "Set gain to: %d", gain); + sensor->status.agc_gain = gain; + } + return ret; +} + +static int set_aec_value(sensor_t *sensor, int value) +{ + int ret = 0; + ret = SCCB_Write(sensor->slv_addr, AEC, value & 0xff) | SCCB_Write(sensor->slv_addr, AECH, value >> 8); + if (ret == 0) { + ESP_LOGD(TAG, "Set aec_value to: %d", value); + sensor->status.aec_value = value; + } + return ret; +} + +static int set_awb_gain_dsp(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = set_reg_bits(sensor, 0x63, 7, 1, enable); + if (ret == 0) { + ESP_LOGD(TAG, "Set awb_gain to: %d", enable); + sensor->status.awb_gain = enable; + } + return ret; +} + +static int set_brightness(sensor_t *sensor, int level) +{ + int ret = 0; + ret = SCCB_Write(sensor->slv_addr, 0x9B, level); + if (ret == 0) { + ESP_LOGD(TAG, "Set brightness to: %d", level); + sensor->status.brightness = level; + } + return ret; +} + +static int set_contrast(sensor_t *sensor, int level) +{ + int ret = 0; + ret = SCCB_Write(sensor->slv_addr, 0x9C, level); + if (ret == 0) { + ESP_LOGD(TAG, "Set contrast to: %d", level); + sensor->status.contrast = level; + } + return ret; +} + +static int init_status(sensor_t *sensor) +{ + sensor->status.brightness = SCCB_Read(sensor->slv_addr, 0x9B); + sensor->status.contrast = SCCB_Read(sensor->slv_addr, 0x9C); + sensor->status.saturation = 0; + sensor->status.ae_level = 0; + sensor->status.special_effect = get_reg_bits(sensor, 0x64, 5, 1); + sensor->status.wb_mode = get_reg_bits(sensor, 0x6B, 7, 1); + sensor->status.agc_gain = get_reg_bits(sensor, COM9, 4, 3); + sensor->status.aec_value = SCCB_Read(sensor->slv_addr, AEC) | (SCCB_Read(sensor->slv_addr, AECH) << 8); + sensor->status.gainceiling = SCCB_Read(sensor->slv_addr, 0x00); + sensor->status.awb = get_reg_bits(sensor, COM8, 1, 1); + sensor->status.awb_gain = get_reg_bits(sensor, 0x63, 7, 1); + sensor->status.aec = get_reg_bits(sensor, COM8, 0, 1); + sensor->status.aec2 = get_reg_bits(sensor, COM8, 7, 1); + sensor->status.agc = get_reg_bits(sensor, COM8, 2, 1); + sensor->status.bpc = get_reg_bits(sensor, 0x64, 1, 1); + sensor->status.wpc = get_reg_bits(sensor, 0x64, 0, 1); + sensor->status.raw_gma = get_reg_bits(sensor, 0x64, 2, 1); + sensor->status.lenc = get_reg_bits(sensor, LC_CTR, 0, 1); + sensor->status.hmirror = get_reg_bits(sensor, COM3, 6, 1); + sensor->status.vflip = get_reg_bits(sensor, COM3, 7, 1); + sensor->status.dcw = get_reg_bits(sensor, 0x65, 2, 1); + sensor->status.colorbar = get_reg_bits(sensor, COM3, 0, 1); + sensor->status.sharpness = get_reg_bits(sensor, EDGE0, 0, 5); + sensor->status.denoise = SCCB_Read(sensor->slv_addr, 0x8E); + return 0; +} + +static int set_dummy(sensor_t *sensor, int val){ return -1; } +static int set_gainceiling_dummy(sensor_t *sensor, gainceiling_t val){ return -1; } +static int set_res_raw(sensor_t *sensor, int startX, int startY, int endX, int endY, int offsetX, int offsetY, int totalX, int totalY, int outputX, int outputY, bool scale, bool binning){return -1;} +static int _set_pll(sensor_t *sensor, int bypass, int multiplier, int sys_div, int root_2x, int pre_div, int seld5, int pclk_manual, int pclk_div){return -1;} + +esp_err_t xclk_timer_conf(int ledc_timer, int xclk_freq_hz); +static int set_xclk(sensor_t *sensor, int timer, int xclk) +{ + int ret = 0; + sensor->xclk_freq_hz = xclk * 1000000U; + ret = xclk_timer_conf(timer, sensor->xclk_freq_hz); + return ret; +} + +int ov7725_init(sensor_t *sensor) +{ + // Set function pointers + sensor->reset = reset; + sensor->init_status = init_status; + sensor->set_pixformat = set_pixformat; + sensor->set_framesize = set_framesize; + sensor->set_colorbar = set_colorbar; + sensor->set_whitebal = set_whitebal; + sensor->set_gain_ctrl = set_gain_ctrl; + sensor->set_exposure_ctrl = set_exposure_ctrl; + sensor->set_hmirror = set_hmirror; + sensor->set_vflip = set_vflip; + + sensor->set_brightness = set_brightness; + sensor->set_contrast = set_contrast; + sensor->set_aec2 = set_aec2; + sensor->set_aec_value = set_aec_value; + sensor->set_awb_gain = set_awb_gain_dsp; + sensor->set_agc_gain = set_agc_gain; + sensor->set_dcw = set_dcw_dsp; + sensor->set_bpc = set_bpc_dsp; + sensor->set_wpc = set_wpc_dsp; + sensor->set_raw_gma = set_raw_gma_dsp; + sensor->set_lenc = set_lenc_dsp; + + //not supported + sensor->set_saturation= set_dummy; + sensor->set_sharpness = set_dummy; + sensor->set_denoise = set_dummy; + sensor->set_quality = set_dummy; + sensor->set_special_effect = set_dummy; + sensor->set_wb_mode = set_dummy; + sensor->set_ae_level = set_dummy; + sensor->set_gainceiling = set_gainceiling_dummy; + + + sensor->get_reg = get_reg; + sensor->set_reg = set_reg; + sensor->set_res_raw = set_res_raw; + sensor->set_pll = _set_pll; + sensor->set_xclk = set_xclk; + + // Retrieve sensor's signature + sensor->id.MIDH = SCCB_Read(sensor->slv_addr, REG_MIDH); + sensor->id.MIDL = SCCB_Read(sensor->slv_addr, REG_MIDL); + sensor->id.PID = SCCB_Read(sensor->slv_addr, REG_PID); + sensor->id.VER = SCCB_Read(sensor->slv_addr, REG_VER); + + ESP_LOGD(TAG, "OV7725 Attached"); + + return 0; +} diff --git a/esp32-cam-rtos/ov7725.h b/esp32-cam-rtos/ov7725.h new file mode 100644 index 0000000..f8c3516 --- /dev/null +++ b/esp32-cam-rtos/ov7725.h @@ -0,0 +1,14 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * OV7725 driver. + * + */ +#ifndef __OV7725_H__ +#define __OV7725_H__ +#include "sensor.h" + +int ov7725_init(sensor_t *sensor); +#endif // __OV7725_H__ diff --git a/esp32-cam-rtos/ov7725_regs.h b/esp32-cam-rtos/ov7725_regs.h new file mode 100644 index 0000000..5cb233d --- /dev/null +++ b/esp32-cam-rtos/ov7725_regs.h @@ -0,0 +1,335 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * OV2640 register definitions. + */ +#ifndef __REG_REGS_H__ +#define __REG_REGS_H__ +#define GAIN 0x00 /* AGC – Gain control gain setting */ +#define BLUE 0x01 /* AWB – Blue channel gain setting */ +#define RED 0x02 /* AWB – Red channel gain setting */ +#define GREEN 0x03 /* AWB – Green channel gain setting */ +#define BAVG 0x05 /* U/B Average Level */ +#define GAVG 0x06 /* Y/Gb Average Level */ +#define RAVG 0x07 /* V/R Average Level */ +#define AECH 0x08 /* Exposure Value – AEC MSBs */ + +#define COM2 0x09 /* Common Control 2 */ +#define COM2_SOFT_SLEEP 0x10 /* Soft sleep mode */ +#define COM2_OUT_DRIVE_1x 0x00 /* Output drive capability 1x */ +#define COM2_OUT_DRIVE_2x 0x01 /* Output drive capability 2x */ +#define COM2_OUT_DRIVE_3x 0x02 /* Output drive capability 3x */ +#define COM2_OUT_DRIVE_4x 0x03 /* Output drive capability 4x */ + +#define REG_PID 0x0A /* Product ID Number MSB */ +#define REG_VER 0x0B /* Product ID Number LSB */ + +#define COM3 0x0C /* Common Control 3 */ +#define COM3_VFLIP 0x80 /* Vertical flip image ON/OFF selection */ +#define COM3_MIRROR 0x40 /* Horizontal mirror image ON/OFF selection */ +#define COM3_SWAP_BR 0x20 /* Swap B/R output sequence in RGB output mode */ +#define COM3_SWAP_YUV 0x10 /* Swap Y/UV output sequence in YUV output mode */ +#define COM3_SWAP_MSB 0x08 /* Swap output MSB/LSB */ +#define COM3_TRI_CLOCK 0x04 /* Tri-state option for output clock at power-down period */ +#define COM3_TRI_DATA 0x02 /* Tri-state option for output data at power-down period */ +#define COM3_COLOR_BAR 0x01 /* Sensor color bar test pattern output enable */ +#define COM3_SET_CBAR(r, x) ((r&0xFE)|((x&1)<<0)) +#define COM3_SET_MIRROR(r, x) ((r&0xBF)|((x&1)<<6)) +#define COM3_SET_FLIP(r, x) ((r&0x7F)|((x&1)<<7)) + +#define COM4 0x0D /* Common Control 4 */ +#define COM4_PLL_BYPASS 0x00 /* Bypass PLL */ +#define COM4_PLL_4x 0x40 /* PLL frequency 4x */ +#define COM4_PLL_6x 0x80 /* PLL frequency 6x */ +#define COM4_PLL_8x 0xc0 /* PLL frequency 8x */ +#define COM4_AEC_FULL 0x00 /* AEC evaluate full window */ +#define COM4_AEC_1_2 0x10 /* AEC evaluate 1/2 window */ +#define COM4_AEC_1_4 0x20 /* AEC evaluate 1/4 window */ +#define COM4_AEC_2_3 0x30 /* AEC evaluate 2/3 window */ + +#define COM5 0x0E /* Common Control 5 */ +#define COM5_AFR 0x80 /* Auto frame rate control ON/OFF selection (night mode) */ +#define COM5_AFR_SPEED 0x40 /* Auto frame rate control speed selection */ +#define COM5_AFR_0 0x00 /* No reduction of frame rate */ +#define COM5_AFR_1_2 0x10 /* Max reduction to 1/2 frame rate */ +#define COM5_AFR_1_4 0x20 /* Max reduction to 1/4 frame rate */ +#define COM5_AFR_1_8 0x30 /* Max reduction to 1/8 frame rate */ +#define COM5_AFR_4x 0x04 /* Add frame when AGC reaches 4x gain */ +#define COM5_AFR_8x 0x08 /* Add frame when AGC reaches 8x gain */ +#define COM5_AFR_16x 0x0c /* Add frame when AGC reaches 16x gain */ +#define COM5_AEC_NO_LIMIT 0x01 /* No limit to AEC increase step */ + +#define COM6 0x0F /* Common Control 6 */ +#define COM6_AUTO_WINDOW 0x01 /* Auto window setting ON/OFF selection when format changes */ + +#define AEC 0x10 /* AEC[7:0] (see register AECH for AEC[15:8]) */ +#define CLKRC 0x11 /* Internal Clock */ + +#define COM7 0x12 /* Common Control 7 */ +#define COM7_RESET 0x80 /* SCCB Register Reset */ +#define COM7_RES_VGA 0x00 /* Resolution VGA */ +#define COM7_RES_QVGA 0x40 /* Resolution QVGA */ +#define COM7_BT656 0x20 /* BT.656 protocol ON/OFF */ +#define COM7_SENSOR_RAW 0x10 /* Sensor RAW */ +#define COM7_FMT_GBR422 0x00 /* RGB output format GBR422 */ +#define COM7_FMT_RGB565 0x04 /* RGB output format RGB565 */ +#define COM7_FMT_RGB555 0x08 /* RGB output format RGB555 */ +#define COM7_FMT_RGB444 0x0C /* RGB output format RGB444 */ +#define COM7_FMT_YUV 0x00 /* Output format YUV */ +#define COM7_FMT_P_BAYER 0x01 /* Output format Processed Bayer RAW */ +#define COM7_FMT_RGB 0x02 /* Output format RGB */ +#define COM7_FMT_R_BAYER 0x03 /* Output format Bayer RAW */ +#define COM7_SET_FMT(r, x) ((r&0xFC)|((x&0x3)<<0)) +#define COM7_SET_RGB(r, x) ((r&0xF0)|(x&0x0C)|COM7_FMT_RGB) + +#define COM8 0x13 /* Common Control 8 */ +#define COM8_FAST_AUTO 0x80 /* Enable fast AGC/AEC algorithm */ +#define COM8_STEP_VSYNC 0x00 /* AEC - Step size limited to vertical blank */ +#define COM8_STEP_UNLIMIT 0x40 /* AEC - Step size unlimited step size */ +#define COM8_BANDF_EN 0x20 /* Banding filter ON/OFF */ +#define COM8_AEC_BANDF 0x10 /* Enable AEC below banding value */ +#define COM8_AEC_FINE_EN 0x08 /* Fine AEC ON/OFF control */ +#define COM8_AGC_EN 0x04 /* AGC Enable */ +#define COM8_AWB_EN 0x02 /* AWB Enable */ +#define COM8_AEC_EN 0x01 /* AEC Enable */ +#define COM8_SET_AGC(r, x) ((r&0xFB)|((x&0x1)<<2)) +#define COM8_SET_AWB(r, x) ((r&0xFD)|((x&0x1)<<1)) +#define COM8_SET_AEC(r, x) ((r&0xFE)|((x&0x1)<<0)) + +#define COM9 0x14 /* Common Control 9 */ +#define COM9_HISTO_AVG 0x80 /* Histogram or average based AEC/AGC selection */ +#define COM9_AGC_GAIN_2x 0x00 /* Automatic Gain Ceiling 2x */ +#define COM9_AGC_GAIN_4x 0x10 /* Automatic Gain Ceiling 4x */ +#define COM9_AGC_GAIN_8x 0x20 /* Automatic Gain Ceiling 8x */ +#define COM9_AGC_GAIN_16x 0x30 /* Automatic Gain Ceiling 16x */ +#define COM9_AGC_GAIN_32x 0x40 /* Automatic Gain Ceiling 32x */ +#define COM9_DROP_VSYNC 0x04 /* Drop VSYNC output of corrupt frame */ +#define COM9_DROP_HREF 0x02 /* Drop HREF output of corrupt frame */ +#define COM9_SET_AGC(r, x) ((r&0x8F)|((x&0x07)<<4)) + +#define COM10 0x15 /* Common Control 10 */ +#define COM10_NEGATIVE 0x80 /* Output negative data */ +#define COM10_HSYNC_EN 0x40 /* HREF changes to HSYNC */ +#define COM10_PCLK_FREE 0x00 /* PCLK output option: free running PCLK */ +#define COM10_PCLK_MASK 0x20 /* PCLK output option: masked during horizontal blank */ +#define COM10_PCLK_REV 0x10 /* PCLK reverse */ +#define COM10_HREF_REV 0x08 /* HREF reverse */ +#define COM10_VSYNC_FALLING 0x00 /* VSYNC changes on falling edge of PCLK */ +#define COM10_VSYNC_RISING 0x04 /* VSYNC changes on rising edge of PCLK */ +#define COM10_VSYNC_NEG 0x02 /* VSYNC negative */ +#define COM10_OUT_RANGE_8 0x01 /* Output data range: Full range */ +#define COM10_OUT_RANGE_10 0x00 /* Output data range: Data from [10] to [F0] (8 MSBs) */ + +#define REG16 0x16 /* Register 16 */ +#define REG16_BIT_SHIFT 0x80 /* Bit shift test pattern options */ +#define HSTART 0x17 /* Horizontal Frame (HREF column) Start 8 MSBs (2 LSBs are at HREF[5:4]) */ +#define HSIZE 0x18 /* Horizontal Sensor Size (2 LSBs are at HREF[1:0]) */ +#define VSTART 0x19 /* Vertical Frame (row) Start 8 MSBs (1 LSB is at HREF[6]) */ +#define VSIZE 0x1A /* Vertical Sensor Size (1 LSB is at HREF[2]) */ +#define PSHFT 0x1B /* Data Format - Pixel Delay Select */ +#define REG_MIDH 0x1C /* Manufacturer ID Byte – High */ +#define REG_MIDL 0x1D /* Manufacturer ID Byte – Low */ +#define LAEC 0x1F /* Fine AEC Value - defines exposure value less than one row period */ + +#define COM11 0x20 /* Common Control 11 */ +#define COM11_SNGL_FRAME_EN 0x02 /* Single frame ON/OFF selection */ +#define COM11_SNGL_XFR_TRIG 0x01 /* Single frame transfer trigger */ + +#define BDBASE 0x22 /* Banding Filter Minimum AEC Value */ +#define DBSTEP 0x23 /* Banding Filter Maximum Step */ +#define AEW 0x24 /* AGC/AEC - Stable Operating Region (Upper Limit) */ +#define AEB 0x25 /* AGC/AEC - Stable Operating Region (Lower Limit) */ +#define VPT 0x26 /* AGC/AEC Fast Mode Operating Region */ +#define REG28 0x28 /* Selection on the number of dummy rows, N */ +#define HOUTSIZE 0x29 /* Horizontal Data Output Size MSBs (2 LSBs at register EXHCH[1:0]) */ +#define EXHCH 0x2A /* Dummy Pixel Insert MSB */ +#define EXHCL 0x2B /* Dummy Pixel Insert LSB */ +#define VOUTSIZE 0x2C /* Vertical Data Output Size MSBs (LSB at register EXHCH[2]) */ +#define ADVFL 0x2D /* LSB of Insert Dummy Rows in Vertical Sync (1 bit equals 1 row) */ +#define ADVFH 0x2E /* MSB of Insert Dummy Rows in Vertical Sync */ +#define YAVE 0x2F /* Y/G Channel Average Value */ +#define LUMHTH 0x30 /* Histogram AEC/AGC Luminance High Level Threshold */ +#define LUMLTH 0x31 /* Histogram AEC/AGC Luminance Low Level Threshold */ +#define HREF 0x32 /* Image Start and Size Control */ +#define DM_LNL 0x33 /* Dummy Row Low 8 Bits */ +#define DM_LNH 0x34 /* Dummy Row High 8 Bits */ +#define ADOFF_B 0x35 /* AD Offset Compensation Value for B Channel */ +#define ADOFF_R 0x36 /* AD Offset Compensation Value for R Channel */ +#define ADOFF_GB 0x37 /* AD Offset Compensation Value for GB Channel */ +#define ADOFF_GR 0x38 /* AD Offset Compensation Value for GR Channel */ +#define OFF_B 0x39 /* AD Offset Compensation Value for B Channel */ +#define OFF_R 0x3A /* AD Offset Compensation Value for R Channel */ +#define OFF_GB 0x3B /* AD Offset Compensation Value for GB Channel */ +#define OFF_GR 0x3C /* AD Offset Compensation Value for GR Channel */ +#define COM12 0x3D /* DC offset compensation for analog process */ + +#define COM13 0x3E /* Common Control 13 */ +#define COM13_BLC_EN 0x80 /* BLC enable */ +#define COM13_ADC_EN 0x40 /* ADC channel BLC ON/OFF control */ +#define COM13_ANALOG_BLC 0x20 /* Analog processing channel BLC ON/OFF control */ +#define COM13_ABLC_GAIN_EN 0x04 /* ABLC gain trigger enable */ + +#define COM14 0x3F /* Common Control 14 */ +#define COM15 0x40 /* Common Control 15 */ +#define COM16 0x41 /* Common Control 16 */ +#define TGT_B 0x42 /* BLC Blue Channel Target Value */ +#define TGT_R 0x43 /* BLC Red Channel Target Value */ +#define TGT_GB 0x44 /* BLC Gb Channel Target Value */ +#define TGT_GR 0x45 /* BLC Gr Channel Target Value */ + +#define LC_CTR 0x46 /* Lens Correction Control */ +#define LC_CTR_RGB_COMP_1 0x00 /* R, G, and B channel compensation coefficient is set by LC_COEF (0x49) */ +#define LC_CTR_RGB_COMP_3 0x04 /* R, G, and B channel compensation coefficient is set by registers + LC_COEFB (0x4B), LC_COEF (0x49), and LC_COEFR (0x4C), respectively */ +#define LC_CTR_EN 0x01 /* Lens correction enable */ +#define LC_XC 0x47 /* X Coordinate of Lens Correction Center Relative to Array Center */ +#define LC_YC 0x48 /* Y Coordinate of Lens Correction Center Relative to Array Center */ +#define LC_COEF 0x49 /* Lens Correction Coefficient */ +#define LC_RADI 0x4A /* Lens Correction Radius */ +#define LC_COEFB 0x4B /* Lens Correction B Channel Compensation Coefficient */ +#define LC_COEFR 0x4C /* Lens Correction R Channel Compensation Coefficient */ + +#define FIXGAIN 0x4D /* Analog Fix Gain Amplifier */ +#define AREF0 0x4E /* Sensor Reference Control */ +#define AREF1 0x4F /* Sensor Reference Current Control */ +#define AREF2 0x50 /* Analog Reference Control */ +#define AREF3 0x51 /* ADC Reference Control */ +#define AREF4 0x52 /* ADC Reference Control */ +#define AREF5 0x53 /* ADC Reference Control */ +#define AREF6 0x54 /* Analog Reference Control */ +#define AREF7 0x55 /* Analog Reference Control */ +#define UFIX 0x60 /* U Channel Fixed Value Output */ +#define VFIX 0x61 /* V Channel Fixed Value Output */ +#define AWBB_BLK 0x62 /* AWB Option for Advanced AWB */ + +#define AWB_CTRL0 0x63 /* AWB Control Byte 0 */ +#define AWB_CTRL0_GAIN_EN 0x80 /* AWB gain enable */ +#define AWB_CTRL0_CALC_EN 0x40 /* AWB calculate enable */ +#define AWB_CTRL0_WBC_MASK 0x0F /* WBC threshold 2 */ + +#define DSP_CTRL1 0x64 /* DSP Control Byte 1 */ +#define DSP_CTRL1_FIFO_EN 0x80 /* FIFO enable/disable selection */ +#define DSP_CTRL1_UV_EN 0x40 /* UV adjust function ON/OFF selection */ +#define DSP_CTRL1_SDE_EN 0x20 /* SDE enable */ +#define DSP_CTRL1_MTRX_EN 0x10 /* Color matrix ON/OFF selection */ +#define DSP_CTRL1_INTRP_EN 0x08 /* Interpolation ON/OFF selection */ +#define DSP_CTRL1_GAMMA_EN 0x04 /* Gamma function ON/OFF selection */ +#define DSP_CTRL1_BLACK_EN 0x02 /* Black defect auto correction ON/OFF */ +#define DSP_CTRL1_WHITE_EN 0x01 /* White defect auto correction ON/OFF */ + +#define DSP_CTRL2 0x65 /* DSP Control Byte 2 */ +#define DSP_CTRL2_VDCW_EN 0x08 /* Vertical DCW enable */ +#define DSP_CTRL2_HDCW_EN 0x04 /* Horizontal DCW enable */ +#define DSP_CTRL2_VZOOM_EN 0x02 /* Vertical zoom out enable */ +#define DSP_CTRL2_HZOOM_EN 0x01 /* Horizontal zoom out enable */ + +#define DSP_CTRL3 0x66 /* DSP Control Byte 3 */ +#define DSP_CTRL3_UV_EN 0x80 /* UV output sequence option */ +#define DSP_CTRL3_CBAR_EN 0x20 /* DSP color bar ON/OFF selection */ +#define DSP_CTRL3_FIFO_EN 0x08 /* FIFO power down ON/OFF selection */ +#define DSP_CTRL3_SCAL1_PWDN 0x04 /* Scaling module power down control 1 */ +#define DSP_CTRL3_SCAL2_PWDN 0x02 /* Scaling module power down control 2 */ +#define DSP_CTRL3_INTRP_PWDN 0x01 /* Interpolation module power down control */ +#define DSP_CTRL3_SET_CBAR(r, x) ((r&0xDF)|((x&1)<<5)) + + +#define DSP_CTRL4 0x67 /* DSP Control Byte 4 */ +#define DSP_CTRL4_YUV_RGB 0x00 /* Output selection YUV or RGB */ +#define DSP_CTRL4_RAW8 0x02 /* Output selection RAW8 */ +#define DSP_CTRL4_RAW10 0x03 /* Output selection RAW10 */ + + +#define AWB_BIAS 0x68 /* AWB BLC Level Clip */ +#define AWB_CTRL1 0x69 /* AWB Control 1 */ +#define AWB_CTRL2 0x6A /* AWB Control 2 */ + +#define AWB_CTRL3 0x6B /* AWB Control 3 */ +#define AWB_CTRL3_ADVANCED 0x80 /* AWB mode select - Advanced AWB */ +#define AWB_CTRL3_SIMPLE 0x00 /* AWB mode select - Simple AWB */ + +#define AWB_CTRL4 0x6C /* AWB Control 4 */ +#define AWB_CTRL5 0x6D /* AWB Control 5 */ +#define AWB_CTRL6 0x6E /* AWB Control 6 */ +#define AWB_CTRL7 0x6F /* AWB Control 7 */ +#define AWB_CTRL8 0x70 /* AWB Control 8 */ +#define AWB_CTRL9 0x71 /* AWB Control 9 */ +#define AWB_CTRL10 0x72 /* AWB Control 10 */ +#define AWB_CTRL11 0x73 /* AWB Control 11 */ +#define AWB_CTRL12 0x74 /* AWB Control 12 */ +#define AWB_CTRL13 0x75 /* AWB Control 13 */ +#define AWB_CTRL14 0x76 /* AWB Control 14 */ +#define AWB_CTRL15 0x77 /* AWB Control 15 */ +#define AWB_CTRL16 0x78 /* AWB Control 16 */ +#define AWB_CTRL17 0x79 /* AWB Control 17 */ +#define AWB_CTRL18 0x7A /* AWB Control 18 */ +#define AWB_CTRL19 0x7B /* AWB Control 19 */ +#define AWB_CTRL20 0x7C /* AWB Control 20 */ +#define AWB_CTRL21 0x7D /* AWB Control 21 */ +#define GAM1 0x7E /* Gamma Curve 1st Segment Input End Point 0x04 Output Value */ +#define GAM2 0x7F /* Gamma Curve 2nd Segment Input End Point 0x08 Output Value */ +#define GAM3 0x80 /* Gamma Curve 3rd Segment Input End Point 0x10 Output Value */ +#define GAM4 0x81 /* Gamma Curve 4th Segment Input End Point 0x20 Output Value */ +#define GAM5 0x82 /* Gamma Curve 5th Segment Input End Point 0x28 Output Value */ +#define GAM6 0x83 /* Gamma Curve 6th Segment Input End Point 0x30 Output Value */ +#define GAM7 0x84 /* Gamma Curve 7th Segment Input End Point 0x38 Output Value */ +#define GAM8 0x85 /* Gamma Curve 8th Segment Input End Point 0x40 Output Value */ +#define GAM9 0x86 /* Gamma Curve 9th Segment Input End Point 0x48 Output Value */ +#define GAM10 0x87 /* Gamma Curve 10th Segment Input End Point 0x50 Output Value */ +#define GAM11 0x88 /* Gamma Curve 11th Segment Input End Point 0x60 Output Value */ +#define GAM12 0x89 /* Gamma Curve 12th Segment Input End Point 0x70 Output Value */ +#define GAM13 0x8A /* Gamma Curve 13th Segment Input End Point 0x90 Output Value */ +#define GAM14 0x8B /* Gamma Curve 14th Segment Input End Point 0xB0 Output Value */ +#define GAM15 0x8C /* Gamma Curve 15th Segment Input End Point 0xD0 Output Value */ +#define SLOP 0x8D /* Gamma Curve Highest Segment Slope */ +#define DNSTH 0x8E /* De-noise Threshold */ +#define EDGE0 0x8F /* Edge Enhancement Strength Control */ +#define EDGE1 0x90 /* Edge Enhancement Threshold Control */ +#define DNSOFF 0x91 /* Auto De-noise Threshold Control */ +#define EDGE2 0x92 /* Edge Enhancement Strength Upper Limit */ +#define EDGE3 0x93 /* Edge Enhancement Strength Upper Limit */ +#define MTX1 0x94 /* Matrix Coefficient 1 */ +#define MTX2 0x95 /* Matrix Coefficient 2 */ +#define MTX3 0x96 /* Matrix Coefficient 3 */ +#define MTX4 0x97 /* Matrix Coefficient 4 */ +#define MTX5 0x98 /* Matrix Coefficient 5 */ +#define MTX6 0x99 /* Matrix Coefficient 6 */ + +#define MTX_CTRL 0x9A /* Matrix Control */ +#define MTX_CTRL_DBL_EN 0x80 /* Matrix double ON/OFF selection */ + +#define BRIGHTNESS 0x9B /* Brightness Control */ +#define CONTRAST 0x9C /* Contrast Gain */ +#define UVADJ0 0x9E /* Auto UV Adjust Control 0 */ +#define UVADJ1 0x9F /* Auto UV Adjust Control 1 */ +#define SCAL0 0xA0 /* DCW Ratio Control */ +#define SCAL1 0xA1 /* Horizontal Zoom Out Control */ +#define SCAL2 0xA2 /* Vertical Zoom Out Control */ +#define FIFODLYM 0xA3 /* FIFO Manual Mode Delay Control */ +#define FIFODLYA 0xA4 /* FIFO Auto Mode Delay Control */ + +#define SDE 0xA6 /* Special Digital Effect Control */ +#define SDE_NEGATIVE_EN 0x40 /* Negative image enable */ +#define SDE_GRAYSCALE_EN 0x20 /* Gray scale image enable */ +#define SDE_V_FIXED_EN 0x10 /* V fixed value enable */ +#define SDE_U_FIXED_EN 0x08 /* U fixed value enable */ +#define SDE_CONT_BRIGHT_EN 0x04 /* Contrast/Brightness enable */ +#define SDE_SATURATION_EN 0x02 /* Saturation enable */ +#define SDE_HUE_EN 0x01 /* Hue enable */ + +#define USAT 0xA7 /* U Component Saturation Gain */ +#define VSAT 0xA8 /* V Component Saturation Gain */ +#define HUECOS 0xA9 /* Cosine value × 0x80 */ +#define HUESIN 0xAA /* Sine value × 0x80 */ +#define SIGN_BIT 0xAB /* Sign Bit for Hue and Brightness */ + +#define DSPAUTO 0xAC /* DSP Auto Function ON/OFF Control */ +#define DSPAUTO_AWB_EN 0x80 /* AWB auto threshold control */ +#define DSPAUTO_DENOISE_EN 0x40 /* De-noise auto threshold control */ +#define DSPAUTO_EDGE_EN 0x20 /* Sharpness (edge enhancement) auto strength control */ +#define DSPAUTO_UV_EN 0x10 /* UV adjust auto slope control */ +#define DSPAUTO_SCAL0_EN 0x08 /* Auto scaling factor control (register SCAL0 (0xA0)) */ +#define DSPAUTO_SCAL1_EN 0x04 /* Auto scaling factor control (registers SCAL1 (0xA1 and SCAL2 (0xA2))*/ +#define SET_REG(reg, x) (##reg_DEFAULT|x) +#endif //__REG_REGS_H__ diff --git a/esp32-cam-rtos/sccb.c b/esp32-cam-rtos/sccb.c new file mode 100644 index 0000000..d2f5fb9 --- /dev/null +++ b/esp32-cam-rtos/sccb.c @@ -0,0 +1,260 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * SCCB (I2C like) driver. + * + */ +#include +#include +#include +#include "sccb.h" +#include +#include "sdkconfig.h" +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#else +#include "esp_log.h" +static const char* TAG = "sccb"; +#endif + +//#undef CONFIG_SCCB_HARDWARE_I2C + +#define LITTLETOBIG(x) ((x<<8)|(x>>8)) + +#ifdef CONFIG_SCCB_HARDWARE_I2C +#include "driver/i2c.h" + +#define SCCB_FREQ 100000 /*!< I2C master frequency*/ +#define WRITE_BIT I2C_MASTER_WRITE /*!< I2C master write */ +#define READ_BIT I2C_MASTER_READ /*!< I2C master read */ +#define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/ +#define ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */ +#define ACK_VAL 0x0 /*!< I2C ack value */ +#define NACK_VAL 0x1 /*!< I2C nack value */ +#if CONFIG_SCCB_HARDWARE_I2C_PORT1 +const int SCCB_I2C_PORT = 1; +#else +const int SCCB_I2C_PORT = 0; +#endif +static uint8_t ESP_SLAVE_ADDR = 0x3c; +#else +#include "twi.h" +#endif + +int SCCB_Init(int pin_sda, int pin_scl) +{ + ESP_LOGI(TAG, "pin_sda %d pin_scl %d\n", pin_sda, pin_scl); +#ifdef CONFIG_SCCB_HARDWARE_I2C + //log_i("SCCB_Init start"); + i2c_config_t conf; + conf.mode = I2C_MODE_MASTER; + conf.sda_io_num = pin_sda; + conf.sda_pullup_en = GPIO_PULLUP_ENABLE; + conf.scl_io_num = pin_scl; + conf.scl_pullup_en = GPIO_PULLUP_ENABLE; + conf.master.clk_speed = SCCB_FREQ; + + i2c_param_config(SCCB_I2C_PORT, &conf); + i2c_driver_install(SCCB_I2C_PORT, conf.mode, 0, 0, 0); +#else + twi_init(pin_sda, pin_scl); +#endif + return 0; +} + +uint8_t SCCB_Probe() +{ +#ifdef CONFIG_SCCB_HARDWARE_I2C + uint8_t slave_addr = 0x0; + while(slave_addr < 0x7f) { + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( slave_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); + i2c_master_stop(cmd); + esp_err_t ret = i2c_master_cmd_begin(SCCB_I2C_PORT, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + if( ret == ESP_OK) { + ESP_SLAVE_ADDR = slave_addr; + return ESP_SLAVE_ADDR; + } + slave_addr++; + } + return ESP_SLAVE_ADDR; +#else + uint8_t reg = 0x00; + uint8_t slv_addr = 0x00; + + ESP_LOGI(TAG, "SCCB_Probe start"); + for (uint8_t i = 0; i < 127; i++) { + if (twi_writeTo(i, ®, 1, true) == 0) { + slv_addr = i; + break; + } + + if (i!=126) { + vTaskDelay(10 / portTICK_PERIOD_MS); // Necessary for OV7725 camera (not for OV2640). + } + } + return slv_addr; +#endif +} + +uint8_t SCCB_Read(uint8_t slv_addr, uint8_t reg) +{ +#ifdef CONFIG_SCCB_HARDWARE_I2C + uint8_t data=0; + esp_err_t ret = ESP_FAIL; + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); + i2c_master_write_byte(cmd, reg, ACK_CHECK_EN); + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin(SCCB_I2C_PORT, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + if(ret != ESP_OK) return -1; + cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( slv_addr << 1 ) | READ_BIT, ACK_CHECK_EN); + i2c_master_read_byte(cmd, &data, NACK_VAL); + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin(SCCB_I2C_PORT, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + if(ret != ESP_OK) { + ESP_LOGE(TAG, "SCCB_Read Failed addr:0x%02x, reg:0x%02x, data:0x%02x, ret:%d", slv_addr, reg, data, ret); + } + return data; +#else + uint8_t data=0; + + int rc = twi_writeTo(slv_addr, ®, 1, true); + if (rc != 0) { + data = 0xff; + } else { + rc = twi_readFrom(slv_addr, &data, 1, true); + if (rc != 0) { + data=0xFF; + } + } + if (rc != 0) { + ESP_LOGE(TAG, "SCCB_Read [%02x] failed rc=%d\n", reg, rc); + } + return data; +#endif +} + +uint8_t SCCB_Write(uint8_t slv_addr, uint8_t reg, uint8_t data) +{ +#ifdef CONFIG_SCCB_HARDWARE_I2C + esp_err_t ret = ESP_FAIL; + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); + i2c_master_write_byte(cmd, reg, ACK_CHECK_EN); + i2c_master_write_byte(cmd, data, ACK_CHECK_EN); + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin(SCCB_I2C_PORT, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + if(ret != ESP_OK) { + ESP_LOGE(TAG, "SCCB_Write Failed addr:0x%02x, reg:0x%02x, data:0x%02x, ret:%d", slv_addr, reg, data, ret); + } + return ret == ESP_OK ? 0 : -1; +#else + uint8_t ret=0; + uint8_t buf[] = {reg, data}; + + if(twi_writeTo(slv_addr, buf, 2, true) != 0) { + ret=0xFF; + } + if (ret != 0) { + ESP_LOGE(TAG, "SCCB_Write [%02x]=%02x failed\n", reg, data); + } + return ret; +#endif +} + +uint8_t SCCB_Read16(uint8_t slv_addr, uint16_t reg) +{ +#ifdef CONFIG_SCCB_HARDWARE_I2C + uint8_t data=0; + esp_err_t ret = ESP_FAIL; + uint16_t reg_htons = LITTLETOBIG(reg); + uint8_t *reg_u8 = (uint8_t *)®_htons; + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); + i2c_master_write_byte(cmd, reg_u8[0], ACK_CHECK_EN); + i2c_master_write_byte(cmd, reg_u8[1], ACK_CHECK_EN); + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin(SCCB_I2C_PORT, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + if(ret != ESP_OK) return -1; + cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( slv_addr << 1 ) | READ_BIT, ACK_CHECK_EN); + i2c_master_read_byte(cmd, &data, NACK_VAL); + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin(SCCB_I2C_PORT, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + if(ret != ESP_OK) { + ESP_LOGE(TAG, "W [%04x]=%02x fail\n", reg, data); + } + return data; +#else + uint8_t data=0; + uint16_t reg_htons = LITTLETOBIG(reg); + uint8_t *reg_u8 = (uint8_t *)®_htons; + uint8_t buf[] = {reg_u8[0], reg_u8[1]}; + + int rc = twi_writeTo(slv_addr, buf, 2, true); + if (rc != 0) { + data = 0xff; + } else { + rc = twi_readFrom(slv_addr, &data, 1, true); + if (rc != 0) { + data=0xFF; + } + } + if (rc != 0) { + ESP_LOGE(TAG, "R [%04x] fail rc=%d\n", reg, rc); + } + return data; +#endif +} + +uint8_t SCCB_Write16(uint8_t slv_addr, uint16_t reg, uint8_t data) +{ + static uint16_t i = 0; +#ifdef CONFIG_SCCB_HARDWARE_I2C + esp_err_t ret = ESP_FAIL; + uint16_t reg_htons = LITTLETOBIG(reg); + uint8_t *reg_u8 = (uint8_t *)®_htons; + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); + i2c_master_write_byte(cmd, reg_u8[0], ACK_CHECK_EN); + i2c_master_write_byte(cmd, reg_u8[1], ACK_CHECK_EN); + i2c_master_write_byte(cmd, data, ACK_CHECK_EN); + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin(SCCB_I2C_PORT, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + if(ret != ESP_OK) { + ESP_LOGE(TAG, "W [%04x]=%02x %d fail\n", reg, data, i++); + } + return ret == ESP_OK ? 0 : -1; +#else + uint8_t ret=0; + uint16_t reg_htons = LITTLETOBIG(reg); + uint8_t *reg_u8 = (uint8_t *)®_htons; + uint8_t buf[] = {reg_u8[0], reg_u8[1], data}; + + if(twi_writeTo(slv_addr, buf, 3, true) != 0) { + ret = 0xFF; + } + if (ret != 0) { + ESP_LOGE(TAG, "W [%04x]=%02x %d fail\n", reg, data, i++); + } + return ret; +#endif +} diff --git a/esp32-cam-rtos/sccb.h b/esp32-cam-rtos/sccb.h new file mode 100644 index 0000000..4d5b5b4 --- /dev/null +++ b/esp32-cam-rtos/sccb.h @@ -0,0 +1,18 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * SCCB (I2C like) driver. + * + */ +#ifndef __SCCB_H__ +#define __SCCB_H__ +#include +int SCCB_Init(int pin_sda, int pin_scl); +uint8_t SCCB_Probe(); +uint8_t SCCB_Read(uint8_t slv_addr, uint8_t reg); +uint8_t SCCB_Write(uint8_t slv_addr, uint8_t reg, uint8_t data); +uint8_t SCCB_Read16(uint8_t slv_addr, uint16_t reg); +uint8_t SCCB_Write16(uint8_t slv_addr, uint16_t reg, uint8_t data); +#endif // __SCCB_H__ diff --git a/esp32-cam-rtos/sensor.c b/esp32-cam-rtos/sensor.c new file mode 100644 index 0000000..2e6d111 --- /dev/null +++ b/esp32-cam-rtos/sensor.c @@ -0,0 +1,28 @@ +#include "sensor.h" + +const resolution_info_t resolution[FRAMESIZE_INVALID] = { + { 96, 96, ASPECT_RATIO_1X1 }, /* 96x96 */ + { 160, 120, ASPECT_RATIO_4X3 }, /* QQVGA */ + { 176, 144, ASPECT_RATIO_5X4 }, /* QCIF */ + { 240, 176, ASPECT_RATIO_4X3 }, /* HQVGA */ + { 240, 240, ASPECT_RATIO_1X1 }, /* 240x240 */ + { 320, 240, ASPECT_RATIO_4X3 }, /* QVGA */ + { 400, 296, ASPECT_RATIO_4X3 }, /* CIF */ + { 480, 320, ASPECT_RATIO_3X2 }, /* HVGA */ + { 640, 480, ASPECT_RATIO_4X3 }, /* VGA */ + { 800, 600, ASPECT_RATIO_4X3 }, /* SVGA */ + { 1024, 768, ASPECT_RATIO_4X3 }, /* XGA */ + { 1280, 720, ASPECT_RATIO_16X9 }, /* HD */ + { 1280, 1024, ASPECT_RATIO_5X4 }, /* SXGA */ + { 1600, 1200, ASPECT_RATIO_4X3 }, /* UXGA */ + // 3MP Sensors + { 1920, 1080, ASPECT_RATIO_16X9 }, /* FHD */ + { 720, 1280, ASPECT_RATIO_9X16 }, /* Portrait HD */ + { 864, 1536, ASPECT_RATIO_9X16 }, /* Portrait 3MP */ + { 2048, 1536, ASPECT_RATIO_4X3 }, /* QXGA */ + // 5MP Sensors + { 2560, 1440, ASPECT_RATIO_16X9 }, /* QHD */ + { 2560, 1600, ASPECT_RATIO_16X10 }, /* WQXGA */ + { 1088, 1920, ASPECT_RATIO_9X16 }, /* Portrait FHD */ + { 2560, 1920, ASPECT_RATIO_4X3 }, /* QSXGA */ +}; diff --git a/esp32-cam-rtos/sensor.h b/esp32-cam-rtos/sensor.h new file mode 100644 index 0000000..3ea7e2c --- /dev/null +++ b/esp32-cam-rtos/sensor.h @@ -0,0 +1,191 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Sensor abstraction layer. + * + */ +#ifndef __SENSOR_H__ +#define __SENSOR_H__ +#include +#include + +#define OV9650_PID (0x96) +#define OV7725_PID (0x77) +#define OV2640_PID (0x26) +#define OV3660_PID (0x36) +#define OV5640_PID (0x56) + +typedef enum { + PIXFORMAT_RGB565, // 2BPP/RGB565 + PIXFORMAT_YUV422, // 2BPP/YUV422 + PIXFORMAT_GRAYSCALE, // 1BPP/GRAYSCALE + PIXFORMAT_JPEG, // JPEG/COMPRESSED + PIXFORMAT_RGB888, // 3BPP/RGB888 + PIXFORMAT_RAW, // RAW + PIXFORMAT_RGB444, // 3BP2P/RGB444 + PIXFORMAT_RGB555, // 3BP2P/RGB555 +} pixformat_t; + +typedef enum { + FRAMESIZE_96X96, // 96x96 + FRAMESIZE_QQVGA, // 160x120 + FRAMESIZE_QCIF, // 176x144 + FRAMESIZE_HQVGA, // 240x176 + FRAMESIZE_240X240, // 240x240 + FRAMESIZE_QVGA, // 320x240 + FRAMESIZE_CIF, // 400x296 + FRAMESIZE_HVGA, // 480x320 + FRAMESIZE_VGA, // 640x480 + FRAMESIZE_SVGA, // 800x600 + FRAMESIZE_XGA, // 1024x768 + FRAMESIZE_HD, // 1280x720 + FRAMESIZE_SXGA, // 1280x1024 + FRAMESIZE_UXGA, // 1600x1200 + // 3MP Sensors + FRAMESIZE_FHD, // 1920x1080 + FRAMESIZE_P_HD, // 720x1280 + FRAMESIZE_P_3MP, // 864x1536 + FRAMESIZE_QXGA, // 2048x1536 + // 5MP Sensors + FRAMESIZE_QHD, // 2560x1440 + FRAMESIZE_WQXGA, // 2560x1600 + FRAMESIZE_P_FHD, // 1080x1920 + FRAMESIZE_QSXGA, // 2560x1920 + FRAMESIZE_INVALID +} framesize_t; + +typedef enum { + ASPECT_RATIO_4X3, + ASPECT_RATIO_3X2, + ASPECT_RATIO_16X10, + ASPECT_RATIO_5X3, + ASPECT_RATIO_16X9, + ASPECT_RATIO_21X9, + ASPECT_RATIO_5X4, + ASPECT_RATIO_1X1, + ASPECT_RATIO_9X16 +} aspect_ratio_t; + +typedef enum { + GAINCEILING_2X, + GAINCEILING_4X, + GAINCEILING_8X, + GAINCEILING_16X, + GAINCEILING_32X, + GAINCEILING_64X, + GAINCEILING_128X, +} gainceiling_t; + +typedef struct { + uint16_t max_width; + uint16_t max_height; + uint16_t start_x; + uint16_t start_y; + uint16_t end_x; + uint16_t end_y; + uint16_t offset_x; + uint16_t offset_y; + uint16_t total_x; + uint16_t total_y; +} ratio_settings_t; + +typedef struct { + const uint16_t width; + const uint16_t height; + const aspect_ratio_t aspect_ratio; +} resolution_info_t; + +// Resolution table (in sensor.c) +extern const resolution_info_t resolution[]; + +typedef struct { + uint8_t MIDH; + uint8_t MIDL; + uint8_t PID; + uint8_t VER; +} sensor_id_t; + +typedef struct { + framesize_t framesize;//0 - 10 + bool scale; + bool binning; + uint8_t quality;//0 - 63 + int8_t brightness;//-2 - 2 + int8_t contrast;//-2 - 2 + int8_t saturation;//-2 - 2 + int8_t sharpness;//-2 - 2 + uint8_t denoise; + uint8_t special_effect;//0 - 6 + uint8_t wb_mode;//0 - 4 + uint8_t awb; + uint8_t awb_gain; + uint8_t aec; + uint8_t aec2; + int8_t ae_level;//-2 - 2 + uint16_t aec_value;//0 - 1200 + uint8_t agc; + uint8_t agc_gain;//0 - 30 + uint8_t gainceiling;//0 - 6 + uint8_t bpc; + uint8_t wpc; + uint8_t raw_gma; + uint8_t lenc; + uint8_t hmirror; + uint8_t vflip; + uint8_t dcw; + uint8_t colorbar; +} camera_status_t; + +typedef struct _sensor sensor_t; +typedef struct _sensor { + sensor_id_t id; // Sensor ID. + uint8_t slv_addr; // Sensor I2C slave address. + pixformat_t pixformat; + camera_status_t status; + int xclk_freq_hz; + + // Sensor function pointers + int (*init_status) (sensor_t *sensor); + int (*reset) (sensor_t *sensor); + int (*set_pixformat) (sensor_t *sensor, pixformat_t pixformat); + int (*set_framesize) (sensor_t *sensor, framesize_t framesize); + int (*set_contrast) (sensor_t *sensor, int level); + int (*set_brightness) (sensor_t *sensor, int level); + int (*set_saturation) (sensor_t *sensor, int level); + int (*set_sharpness) (sensor_t *sensor, int level); + int (*set_denoise) (sensor_t *sensor, int level); + int (*set_gainceiling) (sensor_t *sensor, gainceiling_t gainceiling); + int (*set_quality) (sensor_t *sensor, int quality); + int (*set_colorbar) (sensor_t *sensor, int enable); + int (*set_whitebal) (sensor_t *sensor, int enable); + int (*set_gain_ctrl) (sensor_t *sensor, int enable); + int (*set_exposure_ctrl) (sensor_t *sensor, int enable); + int (*set_hmirror) (sensor_t *sensor, int enable); + int (*set_vflip) (sensor_t *sensor, int enable); + + int (*set_aec2) (sensor_t *sensor, int enable); + int (*set_awb_gain) (sensor_t *sensor, int enable); + int (*set_agc_gain) (sensor_t *sensor, int gain); + int (*set_aec_value) (sensor_t *sensor, int gain); + + int (*set_special_effect) (sensor_t *sensor, int effect); + int (*set_wb_mode) (sensor_t *sensor, int mode); + int (*set_ae_level) (sensor_t *sensor, int level); + + int (*set_dcw) (sensor_t *sensor, int enable); + int (*set_bpc) (sensor_t *sensor, int enable); + int (*set_wpc) (sensor_t *sensor, int enable); + + int (*set_raw_gma) (sensor_t *sensor, int enable); + int (*set_lenc) (sensor_t *sensor, int enable); + + int (*get_reg) (sensor_t *sensor, int reg, int mask); + int (*set_reg) (sensor_t *sensor, int reg, int mask, int value); + int (*set_res_raw) (sensor_t *sensor, int startX, int startY, int endX, int endY, int offsetX, int offsetY, int totalX, int totalY, int outputX, int outputY, bool scale, bool binning); + int (*set_pll) (sensor_t *sensor, int bypass, int mul, int sys, int root, int pre, int seld5, int pclken, int pclk); + int (*set_xclk) (sensor_t *sensor, int timer, int xclk); +} sensor_t; + +#endif /* __SENSOR_H__ */ diff --git a/esp32-cam-rtos/to_bmp.c b/esp32-cam-rtos/to_bmp.c new file mode 100644 index 0000000..59455de --- /dev/null +++ b/esp32-cam-rtos/to_bmp.c @@ -0,0 +1,315 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include +#include +#include "img_converters.h" +#include "soc/efuse_reg.h" +#include "esp_heap_caps.h" +#include "yuv.h" +#include "sdkconfig.h" +#include "esp_jpg_decode.h" + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define TAG "" +#else +#include "esp_log.h" +static const char* TAG = "to_bmp"; +#endif + +static const int BMP_HEADER_LEN = 54; + +typedef struct { + uint32_t filesize; + uint32_t reserved; + uint32_t fileoffset_to_pixelarray; + uint32_t dibheadersize; + int32_t width; + int32_t height; + uint16_t planes; + uint16_t bitsperpixel; + uint32_t compression; + uint32_t imagesize; + uint32_t ypixelpermeter; + uint32_t xpixelpermeter; + uint32_t numcolorspallette; + uint32_t mostimpcolor; +} bmp_header_t; + +typedef struct { + uint16_t width; + uint16_t height; + uint16_t data_offset; + const uint8_t *input; + uint8_t *output; +} rgb_jpg_decoder; + +static void *_malloc(size_t size) +{ + return heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); +} + +//output buffer and image width +static bool _rgb_write(void * arg, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t *data) +{ + rgb_jpg_decoder * jpeg = (rgb_jpg_decoder *)arg; + if(!data){ + if(x == 0 && y == 0){ + //write start + jpeg->width = w; + jpeg->height = h; + //if output is null, this is BMP + if(!jpeg->output){ + jpeg->output = (uint8_t *)_malloc((w*h*3)+jpeg->data_offset); + if(!jpeg->output){ + return false; + } + } + } else { + //write end + } + return true; + } + + size_t jw = jpeg->width*3; + size_t t = y * jw; + size_t b = t + (h * jw); + size_t l = x * 3; + uint8_t *out = jpeg->output+jpeg->data_offset; + uint8_t *o = out; + size_t iy, ix; + + w = w * 3; + + for(iy=t; iyinput + index, len); + } + return len; +} + +static bool jpg2rgb888(const uint8_t *src, size_t src_len, uint8_t * out, jpg_scale_t scale) +{ + rgb_jpg_decoder jpeg; + jpeg.width = 0; + jpeg.height = 0; + jpeg.input = src; + jpeg.output = out; + jpeg.data_offset = 0; + + if(esp_jpg_decode(src_len, scale, _jpg_read, _rgb_write, (void*)&jpeg) != ESP_OK){ + return false; + } + return true; +} + +bool jpg2bmp(const uint8_t *src, size_t src_len, uint8_t ** out, size_t * out_len) +{ + + rgb_jpg_decoder jpeg; + jpeg.width = 0; + jpeg.height = 0; + jpeg.input = src; + jpeg.output = NULL; + jpeg.data_offset = BMP_HEADER_LEN; + + if(esp_jpg_decode(src_len, JPG_SCALE_NONE, _jpg_read, _rgb_write, (void*)&jpeg) != ESP_OK){ + return false; + } + + size_t output_size = jpeg.width*jpeg.height*3; + + jpeg.output[0] = 'B'; + jpeg.output[1] = 'M'; + bmp_header_t * bitmap = (bmp_header_t*)&jpeg.output[2]; + bitmap->reserved = 0; + bitmap->filesize = output_size+BMP_HEADER_LEN; + bitmap->fileoffset_to_pixelarray = BMP_HEADER_LEN; + bitmap->dibheadersize = 40; + bitmap->width = jpeg.width; + bitmap->height = -jpeg.height;//set negative for top to bottom + bitmap->planes = 1; + bitmap->bitsperpixel = 24; + bitmap->compression = 0; + bitmap->imagesize = output_size; + bitmap->ypixelpermeter = 0x0B13 ; //2835 , 72 DPI + bitmap->xpixelpermeter = 0x0B13 ; //2835 , 72 DPI + bitmap->numcolorspallette = 0; + bitmap->mostimpcolor = 0; + + *out = jpeg.output; + *out_len = output_size+BMP_HEADER_LEN; + + return true; +} + +bool fmt2rgb888(const uint8_t *src_buf, size_t src_len, pixformat_t format, uint8_t * rgb_buf) +{ + int pix_count = 0; + if(format == PIXFORMAT_JPEG) { + return jpg2rgb888(src_buf, src_len, rgb_buf, JPG_SCALE_NONE); + } else if(format == PIXFORMAT_RGB888) { + memcpy(rgb_buf, src_buf, src_len); + } else if(format == PIXFORMAT_RGB565) { + int i; + uint8_t hb, lb; + pix_count = src_len / 2; + for(i=0; i> 3; + *rgb_buf++ = hb & 0xF8; + } + } else if(format == PIXFORMAT_GRAYSCALE) { + int i; + uint8_t b; + pix_count = src_len; + for(i=0; ireserved = 0; + bitmap->filesize = out_size; + bitmap->fileoffset_to_pixelarray = BMP_HEADER_LEN; + bitmap->dibheadersize = 40; + bitmap->width = width; + bitmap->height = -height;//set negative for top to bottom + bitmap->planes = 1; + bitmap->bitsperpixel = 24; + bitmap->compression = 0; + bitmap->imagesize = pix_count * 3; + bitmap->ypixelpermeter = 0x0B13 ; //2835 , 72 DPI + bitmap->xpixelpermeter = 0x0B13 ; //2835 , 72 DPI + bitmap->numcolorspallette = 0; + bitmap->mostimpcolor = 0; + + uint8_t * rgb_buf = out_buf + BMP_HEADER_LEN; + uint8_t * src_buf = src; + + + //convert data to RGB888 + if(format == PIXFORMAT_RGB888) { + memcpy(rgb_buf, src_buf, pix_count*3); + } else if(format == PIXFORMAT_RGB565) { + int i; + uint8_t hb, lb; + for(i=0; i> 3; + *rgb_buf++ = hb & 0xF8; + } + } else if(format == PIXFORMAT_GRAYSCALE) { + int i; + uint8_t b; + for(i=0; ibuf, fb->len, fb->width, fb->height, fb->format, out, out_len); +} diff --git a/esp32-cam-rtos/to_jpg.cpp b/esp32-cam-rtos/to_jpg.cpp new file mode 100644 index 0000000..f8987a8 --- /dev/null +++ b/esp32-cam-rtos/to_jpg.cpp @@ -0,0 +1,241 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include +#include +#include "esp_attr.h" +#include "soc/efuse_reg.h" +#include "esp_heap_caps.h" +#include "esp_camera.h" +#include "img_converters.h" +#include "jpge.h" +#include "yuv.h" + +#include "esp_system.h" +#if ESP_IDF_VERSION_MAJOR >= 4 // IDF 4+ +#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 +#include "esp32/spiram.h" +#else +#error Target CONFIG_IDF_TARGET is not supported +#endif +#else // ESP32 Before IDF 4.0 +#include "esp_spiram.h" +#endif + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define TAG "" +#else +#include "esp_log.h" +static const char* TAG = "to_jpg"; +#endif + +static void *_malloc(size_t size) +{ + void * res = malloc(size); + if(res) { + return res; + } + return heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); +} + +static IRAM_ATTR void convert_line_format(uint8_t * src, pixformat_t format, uint8_t * dst, size_t width, size_t in_channels, size_t line) +{ + int i=0, o=0, l=0; + if(format == PIXFORMAT_GRAYSCALE) { + memcpy(dst, src + line * width, width); + } else if(format == PIXFORMAT_RGB888) { + l = width * 3; + src += l * line; + for(i=0; i> 3; + dst[o++] = (src[i+1] & 0x1F) << 3; + } + } else if(format == PIXFORMAT_YUV422) { + uint8_t y0, y1, u, v; + uint8_t r, g, b; + l = width * 2; + src += l * line; + for(i=0; i 100) { + quality = 100; + } + + jpge::params comp_params = jpge::params(); + comp_params.m_subsampling = subsampling; + comp_params.m_quality = quality; + + jpge::jpeg_encoder dst_image; + + if (!dst_image.init(dst_stream, width, height, num_channels, comp_params)) { + ESP_LOGE(TAG, "JPG encoder init failed"); + return false; + } + + uint8_t* line = (uint8_t*)_malloc(width * num_channels); + if(!line) { + ESP_LOGE(TAG, "Scan line malloc failed"); + return false; + } + + for (int i = 0; i < height; i++) { + convert_line_format(src, format, line, width, num_channels, i); + if (!dst_image.process_scanline(line)) { + ESP_LOGE(TAG, "JPG process line %u failed", i); + free(line); + return false; + } + } + free(line); + + if (!dst_image.process_scanline(NULL)) { + ESP_LOGE(TAG, "JPG image finish failed"); + return false; + } + dst_image.deinit(); + return true; +} + +class callback_stream : public jpge::output_stream { +protected: + jpg_out_cb ocb; + void * oarg; + size_t index; + +public: + callback_stream(jpg_out_cb cb, void * arg) : ocb(cb), oarg(arg), index(0) { } + virtual ~callback_stream() { } + virtual bool put_buf(const void* data, int len) + { + index += ocb(oarg, index, data, len); + return true; + } + virtual size_t get_size() const + { + return index; + } +}; + +bool fmt2jpg_cb(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, jpg_out_cb cb, void * arg) +{ + callback_stream dst_stream(cb, arg); + return convert_image(src, width, height, format, quality, &dst_stream); +} + +bool frame2jpg_cb(camera_fb_t * fb, uint8_t quality, jpg_out_cb cb, void * arg) +{ + return fmt2jpg_cb(fb->buf, fb->len, fb->width, fb->height, fb->format, quality, cb, arg); +} + + + +class memory_stream : public jpge::output_stream { +protected: + uint8_t *out_buf; + size_t max_len, index; + +public: + memory_stream(void *pBuf, uint buf_size) : out_buf(static_cast(pBuf)), max_len(buf_size), index(0) { } + + virtual ~memory_stream() { } + + virtual bool put_buf(const void* pBuf, int len) + { + if (!pBuf) { + //end of image + return true; + } + if ((size_t)len > (max_len - index)) { + ESP_LOGW(TAG, "JPG output overflow: %d bytes", len - (max_len - index)); + len = max_len - index; + } + if (len) { + memcpy(out_buf + index, pBuf, len); + index += len; + } + return true; + } + + virtual size_t get_size() const + { + return index; + } +}; + +bool fmt2jpg(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, uint8_t ** out, size_t * out_len) +{ + //todo: allocate proper buffer for holding JPEG data + //this should be enough for CIF frame size + int jpg_buf_len = 64*1024; + + + uint8_t * jpg_buf = (uint8_t *)_malloc(jpg_buf_len); + if(jpg_buf == NULL) { + ESP_LOGE(TAG, "JPG buffer malloc failed"); + return false; + } + memory_stream dst_stream(jpg_buf, jpg_buf_len); + + if(!convert_image(src, width, height, format, quality, &dst_stream)) { + free(jpg_buf); + return false; + } + + *out = jpg_buf; + *out_len = dst_stream.get_size(); + return true; +} + +bool frame2jpg(camera_fb_t * fb, uint8_t quality, uint8_t ** out, size_t * out_len) +{ + return fmt2jpg(fb->buf, fb->len, fb->width, fb->height, fb->format, quality, out, out_len); +} diff --git a/esp32-cam-rtos/twi.c b/esp32-cam-rtos/twi.c new file mode 100644 index 0000000..25d71fc --- /dev/null +++ b/esp32-cam-rtos/twi.c @@ -0,0 +1,432 @@ +/* + si2c.c - Software I2C library for ESP31B + + Copyright (c) 2015 Hristo Gochkov. All rights reserved. + This file is part of the ESP31B core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include +#include +#include "twi.h" +#include "soc/gpio_reg.h" +#include "soc/gpio_struct.h" +#include "soc/io_mux_reg.h" +#include "driver/rtc_io.h" +#include + + +#define LOW 0x0 +#define HIGH 0x1 + +//GPIO FUNCTIONS +#define INPUT 0x01 +#define OUTPUT 0x02 +#define PULLUP 0x04 +#define INPUT_PULLUP 0x05 +#define PULLDOWN 0x08 +#define INPUT_PULLDOWN 0x09 +#define OPEN_DRAIN 0x10 +#define OUTPUT_OPEN_DRAIN 0x12 +#define SPECIAL 0xF0 +#define FUNCTION_1 0x00 +#define FUNCTION_2 0x20 +#define FUNCTION_3 0x40 +#define FUNCTION_4 0x60 +#define FUNCTION_5 0x80 +#define FUNCTION_6 0xA0 + +#define ESP_REG(addr) *((volatile uint32_t *)(addr)) + +const uint8_t pin_to_mux[40] = { 0x44, 0x88, 0x40, 0x84, 0x48, 0x6c, 0x60, 0x64, 0x68, 0x54, 0x58, 0x5c, 0x34, 0x38, 0x30, 0x3c, 0x4c, 0x50, 0x70, 0x74, 0x78, 0x7c, 0x80, 0x8c, 0, 0x24, 0x28, 0x2c, 0, 0, 0, 0, 0x1c, 0x20, 0x14, 0x18, 0x04, 0x08, 0x0c, 0x10}; + +static void pinMode(uint8_t pin, uint8_t mode) +{ + if(pin >= 40) { + return; + } + + uint32_t rtc_reg = rtc_gpio_desc[pin].reg; + + //RTC pins PULL settings + if(rtc_reg) { + //lock rtc + ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].mux); + if(mode & PULLUP) { + ESP_REG(rtc_reg) = (ESP_REG(rtc_reg) | rtc_gpio_desc[pin].pullup) & ~(rtc_gpio_desc[pin].pulldown); + } else if(mode & PULLDOWN) { + ESP_REG(rtc_reg) = (ESP_REG(rtc_reg) | rtc_gpio_desc[pin].pulldown) & ~(rtc_gpio_desc[pin].pullup); + } else { + ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].pullup | rtc_gpio_desc[pin].pulldown); + } + //unlock rtc + } + + uint32_t pinFunction = 0, pinControl = 0; + + //lock gpio + if(mode & INPUT) { + if(pin < 32) { + GPIO.enable_w1tc = BIT(pin); + } else { + GPIO.enable1_w1tc.val = BIT(pin - 32); + } + } else if(mode & OUTPUT) { + if(pin > 33) { + //unlock gpio + return;//pins above 33 can be only inputs + } else if(pin < 32) { + GPIO.enable_w1ts = BIT(pin); + } else { + GPIO.enable1_w1ts.val = BIT(pin - 32); + } + } + + if(mode & PULLUP) { + pinFunction |= FUN_PU; + } else if(mode & PULLDOWN) { + pinFunction |= FUN_PD; + } + + pinFunction |= ((uint32_t)2 << FUN_DRV_S);//what are the drivers? + pinFunction |= FUN_IE;//input enable but required for output as well? + + if(mode & (INPUT | OUTPUT)) { + pinFunction |= ((uint32_t)2 << MCU_SEL_S); + } else if(mode == SPECIAL) { + pinFunction |= ((uint32_t)(((pin)==1||(pin)==3)?0:1) << MCU_SEL_S); + } else { + pinFunction |= ((uint32_t)(mode >> 5) << MCU_SEL_S); + } + + ESP_REG(DR_REG_IO_MUX_BASE + pin_to_mux[pin]) = pinFunction; + + if(mode & OPEN_DRAIN) { + pinControl = (1 << GPIO_PIN0_PAD_DRIVER_S); + } + + GPIO.pin[pin].val = pinControl; + //unlock gpio +} + +static void digitalWrite(uint8_t pin, uint8_t val) +{ + if(val) { + if(pin < 32) { + GPIO.out_w1ts = BIT(pin); + } else if(pin < 34) { + GPIO.out1_w1ts.val = BIT(pin - 32); + } + } else { + if(pin < 32) { + GPIO.out_w1tc = BIT(pin); + } else if(pin < 34) { + GPIO.out1_w1tc.val = BIT(pin - 32); + } + } +} + + +unsigned char twi_dcount = 18; +static unsigned char twi_sda, twi_scl; + + +static inline void SDA_LOW() +{ + //Enable SDA (becomes output and since GPO is 0 for the pin, + // it will pull the line low) + if (twi_sda < 32) { + GPIO.enable_w1ts = BIT(twi_sda); + } else { + GPIO.enable1_w1ts.val = BIT(twi_sda - 32); + } +} + +static inline void SDA_HIGH() +{ + //Disable SDA (becomes input and since it has pullup it will go high) + if (twi_sda < 32) { + GPIO.enable_w1tc = BIT(twi_sda); + } else { + GPIO.enable1_w1tc.val = BIT(twi_sda - 32); + } +} + +static inline uint32_t SDA_READ() +{ + if (twi_sda < 32) { + return (GPIO.in & BIT(twi_sda)) != 0; + } else { + return (GPIO.in1.val & BIT(twi_sda - 32)) != 0; + } +} + +static void SCL_LOW() +{ + if (twi_scl < 32) { + GPIO.enable_w1ts = BIT(twi_scl); + } else { + GPIO.enable1_w1ts.val = BIT(twi_scl - 32); + } +} + +static void SCL_HIGH() +{ + if (twi_scl < 32) { + GPIO.enable_w1tc = BIT(twi_scl); + } else { + GPIO.enable1_w1tc.val = BIT(twi_scl - 32); + } +} + +static uint32_t SCL_READ() +{ + if (twi_scl < 32) { + return (GPIO.in & BIT(twi_scl)) != 0; + } else { + return (GPIO.in1.val & BIT(twi_scl - 32)) != 0; + } +} + + +#ifndef FCPU80 +#define FCPU80 80000000L +#endif + +#if F_CPU == FCPU80 +#define TWI_CLOCK_STRETCH 800 +#else +#define TWI_CLOCK_STRETCH 1600 +#endif + +void twi_setClock(unsigned int freq) +{ +#if F_CPU == FCPU80 + if(freq <= 100000) { + twi_dcount = 19; //about 100KHz + } else if(freq <= 200000) { + twi_dcount = 8; //about 200KHz + } else if(freq <= 300000) { + twi_dcount = 3; //about 300KHz + } else if(freq <= 400000) { + twi_dcount = 1; //about 400KHz + } else { + twi_dcount = 1; //about 400KHz + } +#else + if(freq <= 100000) { + twi_dcount = 32; //about 100KHz + } else if(freq <= 200000) { + twi_dcount = 14; //about 200KHz + } else if(freq <= 300000) { + twi_dcount = 8; //about 300KHz + } else if(freq <= 400000) { + twi_dcount = 5; //about 400KHz + } else if(freq <= 500000) { + twi_dcount = 3; //about 500KHz + } else if(freq <= 600000) { + twi_dcount = 2; //about 600KHz + } else { + twi_dcount = 1; //about 700KHz + } +#endif +} + +void twi_init(unsigned char sda, unsigned char scl) +{ + twi_sda = sda; + twi_scl = scl; + pinMode(twi_sda, OUTPUT); + pinMode(twi_scl, OUTPUT); + + digitalWrite(twi_sda, 0); + digitalWrite(twi_scl, 0); + + pinMode(twi_sda, INPUT_PULLUP); + pinMode(twi_scl, INPUT_PULLUP); + twi_setClock(100000); +} + +void twi_stop(void) +{ + pinMode(twi_sda, INPUT); + pinMode(twi_scl, INPUT); +} + +static void twi_delay(unsigned char v) +{ + unsigned int i; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" + unsigned int reg; + for(i=0; i= 4 + timer_conf.clk_cfg = LEDC_AUTO_CLK; +#endif + timer_conf.timer_num = (ledc_timer_t)ledc_timer; + esp_err_t err = ledc_timer_config(&timer_conf); + if (err != ESP_OK) { + ESP_LOGE(TAG, "ledc_timer_config failed for freq %d, rc=%x", xclk_freq_hz, err); + } + return err; +} + +esp_err_t camera_enable_out_clock(camera_config_t* config) +{ + periph_module_enable(PERIPH_LEDC_MODULE); + + esp_err_t err = xclk_timer_conf(config->ledc_timer, config->xclk_freq_hz); + if (err != ESP_OK) { + ESP_LOGE(TAG, "ledc_timer_config failed, rc=%x", err); + return err; + } + + ledc_channel_config_t ch_conf; + ch_conf.gpio_num = config->pin_xclk; + ch_conf.speed_mode = LEDC_HIGH_SPEED_MODE; + ch_conf.channel = config->ledc_channel; + ch_conf.intr_type = LEDC_INTR_DISABLE; + ch_conf.timer_sel = config->ledc_timer; + ch_conf.duty = 2; + ch_conf.hpoint = 0; + err = ledc_channel_config(&ch_conf); + if (err != ESP_OK) { + ESP_LOGE(TAG, "ledc_channel_config failed, rc=%x", err); + return err; + } + return ESP_OK; +} + +void camera_disable_out_clock() +{ + periph_module_disable(PERIPH_LEDC_MODULE); +} diff --git a/esp32-cam-rtos/xclk.h b/esp32-cam-rtos/xclk.h new file mode 100644 index 0000000..15ed736 --- /dev/null +++ b/esp32-cam-rtos/xclk.h @@ -0,0 +1,7 @@ +#pragma once + +#include "camera_common.h" + +esp_err_t camera_enable_out_clock(); + +void camera_disable_out_clock(); diff --git a/esp32-cam-rtos/yuv.c b/esp32-cam-rtos/yuv.c new file mode 100644 index 0000000..46034cc --- /dev/null +++ b/esp32-cam-rtos/yuv.c @@ -0,0 +1,298 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "yuv.h" +#include "esp_attr.h" + +typedef struct { + int16_t vY; + int16_t vVr; + int16_t vVg; + int16_t vUg; + int16_t vUb; +} yuv_table_row; + +static const yuv_table_row yuv_table[256] = { + // Y Vr Vg Ug Ub // # + { -18, -204, 50, 104, -258 }, // 0 + { -17, -202, 49, 103, -256 }, // 1 + { -16, -201, 49, 102, -254 }, // 2 + { -15, -199, 48, 101, -252 }, // 3 + { -13, -197, 48, 100, -250 }, // 4 + { -12, -196, 48, 99, -248 }, // 5 + { -11, -194, 47, 99, -246 }, // 6 + { -10, -193, 47, 98, -244 }, // 7 + { -9, -191, 46, 97, -242 }, // 8 + { -8, -189, 46, 96, -240 }, // 9 + { -6, -188, 46, 95, -238 }, // 10 + { -5, -186, 45, 95, -236 }, // 11 + { -4, -185, 45, 94, -234 }, // 12 + { -3, -183, 44, 93, -232 }, // 13 + { -2, -181, 44, 92, -230 }, // 14 + { -1, -180, 44, 91, -228 }, // 15 + { 0, -178, 43, 91, -226 }, // 16 + { 1, -177, 43, 90, -223 }, // 17 + { 2, -175, 43, 89, -221 }, // 18 + { 3, -173, 42, 88, -219 }, // 19 + { 4, -172, 42, 87, -217 }, // 20 + { 5, -170, 41, 86, -215 }, // 21 + { 6, -169, 41, 86, -213 }, // 22 + { 8, -167, 41, 85, -211 }, // 23 + { 9, -165, 40, 84, -209 }, // 24 + { 10, -164, 40, 83, -207 }, // 25 + { 11, -162, 39, 82, -205 }, // 26 + { 12, -161, 39, 82, -203 }, // 27 + { 13, -159, 39, 81, -201 }, // 28 + { 15, -158, 38, 80, -199 }, // 29 + { 16, -156, 38, 79, -197 }, // 30 + { 17, -154, 37, 78, -195 }, // 31 + { 18, -153, 37, 78, -193 }, // 32 + { 19, -151, 37, 77, -191 }, // 33 + { 20, -150, 36, 76, -189 }, // 34 + { 22, -148, 36, 75, -187 }, // 35 + { 23, -146, 35, 74, -185 }, // 36 + { 24, -145, 35, 73, -183 }, // 37 + { 25, -143, 35, 73, -181 }, // 38 + { 26, -142, 34, 72, -179 }, // 39 + { 27, -140, 34, 71, -177 }, // 40 + { 29, -138, 34, 70, -175 }, // 41 + { 30, -137, 33, 69, -173 }, // 42 + { 31, -135, 33, 69, -171 }, // 43 + { 32, -134, 32, 68, -169 }, // 44 + { 33, -132, 32, 67, -167 }, // 45 + { 34, -130, 32, 66, -165 }, // 46 + { 36, -129, 31, 65, -163 }, // 47 + { 37, -127, 31, 65, -161 }, // 48 + { 38, -126, 30, 64, -159 }, // 49 + { 39, -124, 30, 63, -157 }, // 50 + { 40, -122, 30, 62, -155 }, // 51 + { 41, -121, 29, 61, -153 }, // 52 + { 43, -119, 29, 60, -151 }, // 53 + { 44, -118, 28, 60, -149 }, // 54 + { 45, -116, 28, 59, -147 }, // 55 + { 46, -114, 28, 58, -145 }, // 56 + { 47, -113, 27, 57, -143 }, // 57 + { 48, -111, 27, 56, -141 }, // 58 + { 50, -110, 26, 56, -139 }, // 59 + { 51, -108, 26, 55, -137 }, // 60 + { 52, -106, 26, 54, -135 }, // 61 + { 53, -105, 25, 53, -133 }, // 62 + { 54, -103, 25, 52, -131 }, // 63 + { 55, -102, 25, 52, -129 }, // 64 + { 57, -100, 24, 51, -127 }, // 65 + { 58, -98, 24, 50, -125 }, // 66 + { 59, -97, 23, 49, -123 }, // 67 + { 60, -95, 23, 48, -121 }, // 68 + { 61, -94, 23, 47, -119 }, // 69 + { 62, -92, 22, 47, -117 }, // 70 + { 64, -90, 22, 46, -115 }, // 71 + { 65, -89, 21, 45, -113 }, // 72 + { 66, -87, 21, 44, -110 }, // 73 + { 67, -86, 21, 43, -108 }, // 74 + { 68, -84, 20, 43, -106 }, // 75 + { 69, -82, 20, 42, -104 }, // 76 + { 71, -81, 19, 41, -102 }, // 77 + { 72, -79, 19, 40, -100 }, // 78 + { 73, -78, 19, 39, -98 }, // 79 + { 74, -76, 18, 39, -96 }, // 80 + { 75, -75, 18, 38, -94 }, // 81 + { 76, -73, 17, 37, -92 }, // 82 + { 77, -71, 17, 36, -90 }, // 83 + { 79, -70, 17, 35, -88 }, // 84 + { 80, -68, 16, 34, -86 }, // 85 + { 81, -67, 16, 34, -84 }, // 86 + { 82, -65, 16, 33, -82 }, // 87 + { 83, -63, 15, 32, -80 }, // 88 + { 84, -62, 15, 31, -78 }, // 89 + { 86, -60, 14, 30, -76 }, // 90 + { 87, -59, 14, 30, -74 }, // 91 + { 88, -57, 14, 29, -72 }, // 92 + { 89, -55, 13, 28, -70 }, // 93 + { 90, -54, 13, 27, -68 }, // 94 + { 91, -52, 12, 26, -66 }, // 95 + { 93, -51, 12, 26, -64 }, // 96 + { 94, -49, 12, 25, -62 }, // 97 + { 95, -47, 11, 24, -60 }, // 98 + { 96, -46, 11, 23, -58 }, // 99 + { 97, -44, 10, 22, -56 }, // 100 + { 98, -43, 10, 21, -54 }, // 101 + { 100, -41, 10, 21, -52 }, // 102 + { 101, -39, 9, 20, -50 }, // 103 + { 102, -38, 9, 19, -48 }, // 104 + { 103, -36, 8, 18, -46 }, // 105 + { 104, -35, 8, 17, -44 }, // 106 + { 105, -33, 8, 17, -42 }, // 107 + { 107, -31, 7, 16, -40 }, // 108 + { 108, -30, 7, 15, -38 }, // 109 + { 109, -28, 7, 14, -36 }, // 110 + { 110, -27, 6, 13, -34 }, // 111 + { 111, -25, 6, 13, -32 }, // 112 + { 112, -23, 5, 12, -30 }, // 113 + { 114, -22, 5, 11, -28 }, // 114 + { 115, -20, 5, 10, -26 }, // 115 + { 116, -19, 4, 9, -24 }, // 116 + { 117, -17, 4, 8, -22 }, // 117 + { 118, -15, 3, 8, -20 }, // 118 + { 119, -14, 3, 7, -18 }, // 119 + { 121, -12, 3, 6, -16 }, // 120 + { 122, -11, 2, 5, -14 }, // 121 + { 123, -9, 2, 4, -12 }, // 122 + { 124, -7, 1, 4, -10 }, // 123 + { 125, -6, 1, 3, -8 }, // 124 + { 126, -4, 1, 2, -6 }, // 125 + { 128, -3, 0, 1, -4 }, // 126 + { 129, -1, 0, 0, -2 }, // 127 + { 130, 0, 0, 0, 0 }, // 128 + { 131, 1, 0, 0, 2 }, // 129 + { 132, 3, 0, -1, 4 }, // 130 + { 133, 4, -1, -2, 6 }, // 131 + { 135, 6, -1, -3, 8 }, // 132 + { 136, 7, -1, -4, 10 }, // 133 + { 137, 9, -2, -4, 12 }, // 134 + { 138, 11, -2, -5, 14 }, // 135 + { 139, 12, -3, -6, 16 }, // 136 + { 140, 14, -3, -7, 18 }, // 137 + { 142, 15, -3, -8, 20 }, // 138 + { 143, 17, -4, -8, 22 }, // 139 + { 144, 19, -4, -9, 24 }, // 140 + { 145, 20, -5, -10, 26 }, // 141 + { 146, 22, -5, -11, 28 }, // 142 + { 147, 23, -5, -12, 30 }, // 143 + { 148, 25, -6, -13, 32 }, // 144 + { 150, 27, -6, -13, 34 }, // 145 + { 151, 28, -7, -14, 36 }, // 146 + { 152, 30, -7, -15, 38 }, // 147 + { 153, 31, -7, -16, 40 }, // 148 + { 154, 33, -8, -17, 42 }, // 149 + { 155, 35, -8, -17, 44 }, // 150 + { 157, 36, -8, -18, 46 }, // 151 + { 158, 38, -9, -19, 48 }, // 152 + { 159, 39, -9, -20, 50 }, // 153 + { 160, 41, -10, -21, 52 }, // 154 + { 161, 43, -10, -21, 54 }, // 155 + { 162, 44, -10, -22, 56 }, // 156 + { 164, 46, -11, -23, 58 }, // 157 + { 165, 47, -11, -24, 60 }, // 158 + { 166, 49, -12, -25, 62 }, // 159 + { 167, 51, -12, -26, 64 }, // 160 + { 168, 52, -12, -26, 66 }, // 161 + { 169, 54, -13, -27, 68 }, // 162 + { 171, 55, -13, -28, 70 }, // 163 + { 172, 57, -14, -29, 72 }, // 164 + { 173, 59, -14, -30, 74 }, // 165 + { 174, 60, -14, -30, 76 }, // 166 + { 175, 62, -15, -31, 78 }, // 167 + { 176, 63, -15, -32, 80 }, // 168 + { 178, 65, -16, -33, 82 }, // 169 + { 179, 67, -16, -34, 84 }, // 170 + { 180, 68, -16, -34, 86 }, // 171 + { 181, 70, -17, -35, 88 }, // 172 + { 182, 71, -17, -36, 90 }, // 173 + { 183, 73, -17, -37, 92 }, // 174 + { 185, 75, -18, -38, 94 }, // 175 + { 186, 76, -18, -39, 96 }, // 176 + { 187, 78, -19, -39, 98 }, // 177 + { 188, 79, -19, -40, 100 }, // 178 + { 189, 81, -19, -41, 102 }, // 179 + { 190, 82, -20, -42, 104 }, // 180 + { 192, 84, -20, -43, 106 }, // 181 + { 193, 86, -21, -43, 108 }, // 182 + { 194, 87, -21, -44, 110 }, // 183 + { 195, 89, -21, -45, 113 }, // 184 + { 196, 90, -22, -46, 115 }, // 185 + { 197, 92, -22, -47, 117 }, // 186 + { 199, 94, -23, -47, 119 }, // 187 + { 200, 95, -23, -48, 121 }, // 188 + { 201, 97, -23, -49, 123 }, // 189 + { 202, 98, -24, -50, 125 }, // 190 + { 203, 100, -24, -51, 127 }, // 191 + { 204, 102, -25, -52, 129 }, // 192 + { 206, 103, -25, -52, 131 }, // 193 + { 207, 105, -25, -53, 133 }, // 194 + { 208, 106, -26, -54, 135 }, // 195 + { 209, 108, -26, -55, 137 }, // 196 + { 210, 110, -26, -56, 139 }, // 197 + { 211, 111, -27, -56, 141 }, // 198 + { 213, 113, -27, -57, 143 }, // 199 + { 214, 114, -28, -58, 145 }, // 200 + { 215, 116, -28, -59, 147 }, // 201 + { 216, 118, -28, -60, 149 }, // 202 + { 217, 119, -29, -60, 151 }, // 203 + { 218, 121, -29, -61, 153 }, // 204 + { 219, 122, -30, -62, 155 }, // 205 + { 221, 124, -30, -63, 157 }, // 206 + { 222, 126, -30, -64, 159 }, // 207 + { 223, 127, -31, -65, 161 }, // 208 + { 224, 129, -31, -65, 163 }, // 209 + { 225, 130, -32, -66, 165 }, // 210 + { 226, 132, -32, -67, 167 }, // 211 + { 228, 134, -32, -68, 169 }, // 212 + { 229, 135, -33, -69, 171 }, // 213 + { 230, 137, -33, -69, 173 }, // 214 + { 231, 138, -34, -70, 175 }, // 215 + { 232, 140, -34, -71, 177 }, // 216 + { 233, 142, -34, -72, 179 }, // 217 + { 235, 143, -35, -73, 181 }, // 218 + { 236, 145, -35, -73, 183 }, // 219 + { 237, 146, -35, -74, 185 }, // 220 + { 238, 148, -36, -75, 187 }, // 221 + { 239, 150, -36, -76, 189 }, // 222 + { 240, 151, -37, -77, 191 }, // 223 + { 242, 153, -37, -78, 193 }, // 224 + { 243, 154, -37, -78, 195 }, // 225 + { 244, 156, -38, -79, 197 }, // 226 + { 245, 158, -38, -80, 199 }, // 227 + { 246, 159, -39, -81, 201 }, // 228 + { 247, 161, -39, -82, 203 }, // 229 + { 249, 162, -39, -82, 205 }, // 230 + { 250, 164, -40, -83, 207 }, // 231 + { 251, 165, -40, -84, 209 }, // 232 + { 252, 167, -41, -85, 211 }, // 233 + { 253, 169, -41, -86, 213 }, // 234 + { 254, 170, -41, -86, 215 }, // 235 + { 256, 172, -42, -87, 217 }, // 236 + { 257, 173, -42, -88, 219 }, // 237 + { 258, 175, -43, -89, 221 }, // 238 + { 259, 177, -43, -90, 223 }, // 239 + { 260, 178, -43, -91, 226 }, // 240 + { 261, 180, -44, -91, 228 }, // 241 + { 263, 181, -44, -92, 230 }, // 242 + { 264, 183, -44, -93, 232 }, // 243 + { 265, 185, -45, -94, 234 }, // 244 + { 266, 186, -45, -95, 236 }, // 245 + { 267, 188, -46, -95, 238 }, // 246 + { 268, 189, -46, -96, 240 }, // 247 + { 270, 191, -46, -97, 242 }, // 248 + { 271, 193, -47, -98, 244 }, // 249 + { 272, 194, -47, -99, 246 }, // 250 + { 273, 196, -48, -99, 248 }, // 251 + { 274, 197, -48, -100, 250 }, // 252 + { 275, 199, -48, -101, 252 }, // 253 + { 277, 201, -49, -102, 254 }, // 254 + { 278, 202, -49, -103, 256 } // 255 +}; + +#define YUYV_CONSTRAIN(v) ((v)<0)?0:(((v)>255)?255:(v)) + +void IRAM_ATTR yuv2rgb(uint8_t y, uint8_t u, uint8_t v, uint8_t *r, uint8_t *g, uint8_t *b) +{ + int16_t ri, gi, bi; + + ri = yuv_table[y].vY + yuv_table[v].vVr; + gi = yuv_table[y].vY + yuv_table[u].vUg + yuv_table[v].vVg; + bi = yuv_table[y].vY + yuv_table[u].vUb; + + *r = YUYV_CONSTRAIN(ri); + *g = YUYV_CONSTRAIN(gi); + *b = YUYV_CONSTRAIN(bi); +} diff --git a/esp32-cam-rtos/yuv.h b/esp32-cam-rtos/yuv.h new file mode 100644 index 0000000..c5a0577 --- /dev/null +++ b/esp32-cam-rtos/yuv.h @@ -0,0 +1,29 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef _CONVERSIONS_YUV_H_ +#define _CONVERSIONS_YUV_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +void yuv2rgb(uint8_t y, uint8_t u, uint8_t v, uint8_t *r, uint8_t *g, uint8_t *b); + +#ifdef __cplusplus +} +#endif + +#endif /* _CONVERSIONS_YUV_H_ */ diff --git a/esp32-cam/camera.c b/esp32-cam/camera.c index d2ac7a3..1e78c99 100644 --- a/esp32-cam/camera.c +++ b/esp32-cam/camera.c @@ -247,13 +247,13 @@ static esp_err_t camera_fb_init(size_t count) } 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) { +// _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); - } +// } 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); diff --git a/esp32-cam/esp32-cam.ino b/esp32-cam/esp32-cam.ino index 10ef464..d8d1c3e 100644 --- a/esp32-cam/esp32-cam.ino +++ b/esp32-cam/esp32-cam.ino @@ -75,7 +75,7 @@ SemaphoreHandle_t frameSync = NULL; QueueHandle_t streamingClients; // We will try to achieve 25 FPS frame rate -const int FPS = 25; +const int FPS = 10; // We will handle web client requests every 50 ms (20 Hz) const int WSINTERVAL = 50; @@ -113,7 +113,8 @@ void mjpegCB(void* pvParameters) { NULL, //(void*) handler, 2, &tStream, - APP_CPU); +// APP_CPU); + PRO_CPU); // Registering webserver handling routines server.on("/mjpeg/1", HTTP_GET, handleJPGSstream); @@ -451,8 +452,8 @@ void setup() // .frame_size = FRAMESIZE_QVGA, // .frame_size = FRAMESIZE_UXGA, // .frame_size = FRAMESIZE_SVGA, - // .frame_size = FRAMESIZE_QVGA, - .frame_size = FRAMESIZE_VGA, + // .frame_size = FRAMESIZE_VGA, + .frame_size = FRAMESIZE_SVGA, .jpeg_quality = 12, .fb_count = 2 }; @@ -500,10 +501,5 @@ void setup() } void loop() { - // loop() runs in the RTOS Idle Task. - // If loop has a chance to run, there is nothing else for the CPU to do - // so we can nap for 1 ms - - // esp_sleep_enable_timer_wakeup((uint64_t) 1000); - // esp_light_sleep_start(); + vTaskDelay(100); }