# ESP32 Parallel IO Camera Driver
[](https://components.espressif.com/components/haqqihaziq/esp_cam_io_parl)
<!--[](https://github.com/HaqqiHaziq/esp_cam_io_parl/releases)-->
## General Information
This repository provides **ESP Parallel IO Camera** component (`esp_cam_io_parl`) that utilizes Parallel IO (PARLIO) peripheral to receive image data over DVP (Digital Video Port) using ESP32 SoCs that are capable of driving image sensors.
### Supported Targets
- ESP32-C5
- ESP32-C6
- ESP32-H2
- ESP32-H4
- ESP32-P4
### Supported Sensors
| Model | Max resolution | Color type | Output format | Lens Size | Supported |
| ------- | -------------- | ---------- | ----------------------------------------------------------------- | --------- | --------- |
| OV2640 | 1600 x 1200 | color | YUV(422/420)/YCbCr422<br>RGB565/555<br>8-bit compressed data<br>8/10-bit Raw RGB data | 1/4" | ⚠️ (Only on ESP32-C6 & ESP32-P4) |
| OV3660 | 2048 x 1536 | color | raw RGB data<br/>RGB565/555/444<br/>CCIR656<br/>YCbCr422<br/>compression | 1/5" | ✅ |
| OV5640 | 2592 x 1944 | color | RAW RGB<br/>RGB565/555/444<br/>CCIR656<br/>YUV422/420<br/>YCbCr422<br/>compression | 1/4" | ✅ |
### DVP Data Width
| Target | Max data width | Max PCLK frequency (ideal conditions) | Sample method format |
| --------- | ---------------------------- | ------------------------------------- | ----------------------|
| ESP32-C6 | 16 (8 with valid signals) | 80MHz | Gated PCLK with software delimiter for 16 data lines, HREF (level delimiter) or HSYNC (pulse delimiter) signals for 8 data lines. |
| ESP32-H2 | 8 (No valid signals) | 48MHz | Gated PCLK with software delimiter for 8 data lines. This target does not accept valid signals with 8 data lines. |
| ESP32-P4 | 16 (8 with valid signals) | 80MHz | Gated PCLK with software delimiter for 16 data lines, HREF (level delimiter) or HSYNC (pulse delimiter) signals for 8 data lines. |
| ESP32-C5 | 8 (No valid signals) | 80MHz | Gated PCLK with software delimiter for 8 data lines. This target does not accept valid signals with 8 data lines. |
| ESP32-H4 | ? | ? | ? |
## Important to Remember
- It is recommended to have PSRAM installed and enabled for higher resolutions. Therefore, the ESP32-H2 is not recommended for image streaming due to its lack of PSRAM support and limited internal RAM. The ESP32-C6 can handle resolutions up to SVGA (tested with Wi-Fi enabled and a streaming web server, XGA can be reached with some tweaks).
- This component currently only accepts JPEG image inputs from DVP sensors due to the limitations of the Parallel IO driver for some targets (e.g. ESP32-H2 and ESP32-C5). As a result, it uses software delimiter with PCLK gating to allow streaming JPEG image from the following image sensors (except OV2640).
- Currently only OV3660 and OV5640 sensors are implemented to have gated PCLK signals. OV2640 requires the target to have valid signals (e.g. ESP32-C6 and ESP32-P4) so it can properly interface with the following sensor. It is highly recommended to use OV5640 or OV3660 for targets with limited data width.
- This component does not utilize VSYNC signals for controlling frames at the moment, support for receiving raw data will be possible if it gets implemented.
## Additional Notes
- You can also use this component to receive JPEG images from another target capable of sending parallel data.
## Installation Instructions
### Using with ESP-IDF
#### Through `idf.py add-dependency`
- You can add the component through ESP Component Registry:
```c
idf.py add-dependency haqqihaziq/esp_cam_io_parl
```
- Additionally, to add the component as a dependency directly from GitHub `master` branch, run:
```c
idf.py add-dependency --git "https://github.com/HaqqiHaziq/esp_cam_io_parl.git" esp_cam_io_parl
```
#### Download and apply it locally
- Download and extract the component file.
- Insert the component under the `components` folder in your project file.
- If possible, enable PSRAM in `menuconfig` (also set Flash and PSRAM frequiencies to 80MHz for optimal performance)
- Include the component in your main code:
```c
#include "esp_camera_sensor.h"
#include "esp_cam_io_parl.h"
```
### Using with Arduino
#### Arduino IDE
This component will be released as a library.
---
# Examples
## Simple Camera Capture
```c
#include "esp_err.h"
#include "esp_heap_caps.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "esp_cam_io_parl.h"
#include "esp_camera_sensor.h"
static const char *TAG = "simple_camera_capture";
// Camera Sensor Configuration. Set the pins according to your connection to the DVP camera.
#define CAM_PWDN_PIN -1 // Power down pin, set to -1 if not used
#define CAM_RESET_PIN 9 // Software reset will be performed if set to -1
#define CAM_XCLK_PIN -1 // Emulated by PWM (LEDC), set to -1 for sensors with built-in crystal oscillator
#define CAM_SDA_PIN 13
#define CAM_SCL_PIN 14
#define CAM_D0_PIN 0
#define CAM_D1_PIN 1
#define CAM_D2_PIN 2
#define CAM_D3_PIN 3
#define CAM_D4_PIN 4
#define CAM_D5_PIN 5
#define CAM_D6_PIN 10
#define CAM_D7_PIN 11
#define CAM_VSYNC_PIN -1 // Not implemented at the moment
#define CAM_HREF_PIN -1 // Can not use any additional signals on ESP32-C5/ESP32-H2
#define CAM_HSYNC_PIN -1 // Can not use any additional signals on ESP32-C5/ESP32-H2
#define CAM_PCLK_PIN 12
// Save the frame buffer in PSRAM, or set it to MALLOC_CAP_INTERNAL for targets without PSRAM support
#define FRAME_BUFFER_CAPS MALLOC_CAP_INTERNAL
static esp_cam_io_parl_handle_t esp_cam_io_parl_handle;
typedef struct {
size_t size; //number of values used for filtering
size_t index; //current value index
size_t count; //value count
int sum;
int *values; //array to be filled with values
} ra_filter_t;
static ra_filter_t ra_filter;
static ra_filter_t *ra_filter_init(ra_filter_t *filter, size_t sample_size) {
memset(filter, 0, sizeof(ra_filter_t));
filter->values = (int *)malloc(sample_size * sizeof(int));
if (!filter->values) {
return NULL;
}
memset(filter->values, 0, sample_size * sizeof(int));
filter->size = sample_size;
return filter;
}
static int ra_filter_run(ra_filter_t *filter, int value) {
if (!filter->values) {
return value;
}
filter->sum -= filter->values[filter->index];
filter->values[filter->index] = value;
filter->sum += filter->values[filter->index];
filter->index++;
filter->index = filter->index % filter->size;
if (filter->count < filter->size) {
filter->count++;
}
return filter->sum / filter->count;
}
void camera_task(void *args) {
while(true) {
int64_t last_frame = esp_timer_get_time();
esp_cam_io_parl_trans_t image;
if (esp_cam_io_parl_receive(esp_cam_io_parl_handle, &image, 5000) != ESP_OK) {
ESP_LOGE(TAG, "Camera capture failed");
} else {
int64_t frame_time = esp_timer_get_time() - last_frame;
frame_time /= 1000;
uint32_t avg_frame_time = ra_filter_run(&ra_filter, frame_time);
ESP_LOGI(TAG, "JPEG: %uB %ums (%.1ffps), AVG: %ums (%.1ffps)", (uint32_t)(image.length), (uint32_t)frame_time, 1000.0 / (uint32_t)frame_time, avg_frame_time, 1000.0 / avg_frame_time);
esp_cam_io_parl_free_buffer(image);
}
}
}
void app_main(void) {
// Camera sensor configuration
static esp_camera_sensor_config_t camera_sensor_config = {
.pwdn_io = CAM_PWDN_PIN,
.reset_io = CAM_RESET_PIN,
.xclk_io = CAM_XCLK_PIN,
.xclk_hz = 20000000,
.sda_io = CAM_SDA_PIN,
.scl_io = CAM_SCL_PIN,
.ledc_timer = LEDC_TIMER_0,
.ledc_channel = LEDC_CHANNEL_0,
.pixel_format = PIXFORMAT_JPEG, // esp_cam_io_parl only supports JPEG images at the moment
.frame_size = FRAMESIZE_HVGA,
.jpeg_quality = 8,
};
// DVP port configuration
static esp_cam_io_parl_config_t esp_cam_io_parl_config = {
.data_width = 8,
.queue_frames = 1,
.pclk_io = CAM_PCLK_PIN,
.de_io = CAM_HREF_PIN,
.hsync_io = CAM_HSYNC_PIN,
.vsync_io = CAM_VSYNC_PIN, // Not implemented
.data_io = {
CAM_D0_PIN,
CAM_D1_PIN,
CAM_D2_PIN,
CAM_D3_PIN,
CAM_D4_PIN,
CAM_D5_PIN,
CAM_D6_PIN,
CAM_D7_PIN,
},
.flags = {
.free_clk = true,
.allow_pd = true,
},
};
esp_err_t err = esp_camera_sensor_init(&camera_sensor_config);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Camera init failed with error 0x%x", err);
return;
}
camera_sensor_t *sensor = esp_camera_sensor_get();
ESP_LOGI(TAG, "Camera detected! Current quality = %u", sensor->status.quality);
sensor->set_vflip(sensor, true); // Adjust if the image is flipped vertically
sensor->set_hmirror(sensor, false); // Adjust if the image is flipped horizontally
ESP_ERROR_CHECK(esp_cam_new_io_parl(&esp_cam_io_parl_config, &esp_cam_io_parl_handle));
ESP_ERROR_CHECK(esp_cam_io_parl_enable(esp_cam_io_parl_handle, true));
image_info_t *image = esp_camera_sensor_get_image(); // Prepare the frame allocation
ESP_ERROR_CHECK(esp_cam_io_parl_set_alloc_size(esp_cam_io_parl_handle, image->width * image->height / 4 + 2048, FRAME_BUFFER_CAPS));
ra_filter_init(&ra_filter, 20);
xTaskCreate(camera_task, "camera_task", 2048, NULL, 2, NULL);
}
```
## Capture & Stream Image (AP)
```c
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include "esp_err.h"
#include "esp_event.h"
#include "esp_heap_caps.h"
#include "esp_http_server.h"
#include "esp_log.h"
#include "esp_mac.h"
#include "esp_timer.h"
#include "esp_wifi.h"
#include "nvs_flash.h"
#include "sdkconfig.h"
#include "esp_cam_io_parl.h"
#include "esp_camera_sensor.h"
static const char *TAG = "camera_stream_to_http";
// Camera Sensor Configuration. Set the pins according to your connection to the DVP camera.
#define CAM_PWDN_PIN -1 // Power down pin, set to -1 if not used
#define CAM_RESET_PIN 5 // Software reset will be performed if set to -1
#define CAM_XCLK_PIN -1 // Emulated by PWM (LEDC), set to -1 for sensors with built-in crystal oscillator
#define CAM_SDA_PIN 26
#define CAM_SCL_PIN 25
#define CAM_D0_PIN 10
#define CAM_D1_PIN 9
#define CAM_D2_PIN 8
#define CAM_D3_PIN 7
#define CAM_D4_PIN 6
#define CAM_D5_PIN 1
#define CAM_D6_PIN 0
#define CAM_D7_PIN 3
#define CAM_VSYNC_PIN -1 // Not implemented at the moment
#define CAM_HREF_PIN -1 // Can not use any additional signals on ESP32-C5/ESP32-H2
#define CAM_HSYNC_PIN -1 // Can not use any additional signals on ESP32-C5/ESP32-H2
#define CAM_PCLK_PIN 2
// Save the frame buffer in PSRAM, or set it to MALLOC_CAP_INTERNAL for targets without PSRAM support
#define FRAME_BUFFER_CAPS MALLOC_CAP_SPIRAM
// Wi-Fi details
#define ESP_WIFI_SSID "camera@2.4GHz"
#define ESP_WIFI_PASS "my_camera"
#define ESP_WIFI_CHANNEL 2 // Set channel over 36 for 5GHz (ESP32-C5)
#define MAX_STA_CONN 4
#define PART_BOUNDARY "123456789000000000000987654321"
static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
static const char* _STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n";
httpd_handle_t capture_httpd = NULL;
httpd_handle_t stream_httpd = NULL;
static esp_cam_io_parl_handle_t esp_cam_io_parl_handle;
typedef struct {
size_t size; //number of values used for filtering
size_t index; //current value index
size_t count; //value count
int sum;
int *values; //array to be filled with values
} ra_filter_t;
static ra_filter_t ra_filter;
static ra_filter_t *ra_filter_init(ra_filter_t *filter, size_t sample_size) {
memset(filter, 0, sizeof(ra_filter_t));
filter->values = (int *)malloc(sample_size * sizeof(int));
if (!filter->values) {
return NULL;
}
memset(filter->values, 0, sample_size * sizeof(int));
filter->size = sample_size;
return filter;
}
static int ra_filter_run(ra_filter_t *filter, int value) {
if (!filter->values) {
return value;
}
filter->sum -= filter->values[filter->index];
filter->values[filter->index] = value;
filter->sum += filter->values[filter->index];
filter->index++;
filter->index = filter->index % filter->size;
if (filter->count < filter->size) {
filter->count++;
}
return filter->sum / filter->count;
}
static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
if (event_id == WIFI_EVENT_AP_STACONNECTED) {
wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data;
ESP_LOGI(TAG, "station "MACSTR" join, AID=%d", MAC2STR(event->mac), event->aid);
} else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) {
wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data;
ESP_LOGI(TAG, "station "MACSTR" leave, AID=%d, reason=%d", MAC2STR(event->mac), event->aid, event->reason);
}
}
static void wifi_init_softap(void) {
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_t *netif_interface = esp_netif_create_default_wifi_ap();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, NULL));
wifi_config_t wifi_config = {
.ap = {
.ssid = ESP_WIFI_SSID,
.ssid_len = strlen(ESP_WIFI_SSID),
.channel = ESP_WIFI_CHANNEL,
.password = ESP_WIFI_PASS,
.max_connection = MAX_STA_CONN,
#ifdef CONFIG_ESP_WIFI_SOFTAP_SAE_SUPPORT
.authmode = WIFI_AUTH_WPA3_PSK,
.sae_pwe_h2e = WPA3_SAE_PWE_BOTH,
#else /* CONFIG_ESP_WIFI_SOFTAP_SAE_SUPPORT */
.authmode = WIFI_AUTH_WPA2_PSK,
#endif
.pmf_cfg = {
.required = true,
},
#ifdef CONFIG_ESP_WIFI_BSS_MAX_IDLE_SUPPORT
.bss_max_idle_cfg = {
.period = WIFI_AP_DEFAULT_MAX_IDLE_PERIOD,
.protected_keep_alive = 1,
},
#endif
},
};
if (strlen(ESP_WIFI_PASS) == 0) {
wifi_config.ap.authmode = WIFI_AUTH_OPEN;
}
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
/*
// Uncomment if target is ESP32-C5 for 5GHz connectivity
ESP_ERROR_CHECK(esp_wifi_set_band_mode(WIFI_BAND_MODE_5G_ONLY));
*/
ESP_LOGI(TAG, "wifi_init_softap finished. SSID:%s password:%s channel:%d", ESP_WIFI_SSID, ESP_WIFI_PASS, ESP_WIFI_CHANNEL);
esp_netif_ip_info_t ip_info;
esp_netif_get_ip_info(netif_interface, &ip_info);
ESP_LOGI(TAG, "IP Address:" IPSTR, IP2STR(&ip_info.ip));
}
static esp_err_t capture_handler(httpd_req_t *req) {
esp_err_t res = ESP_OK;
int64_t fr_start = esp_timer_get_time();
esp_cam_io_parl_trans_t frame;
if (esp_cam_io_parl_receive(esp_cam_io_parl_handle, &frame, 5000) != ESP_OK) {
ESP_LOGE(TAG, "Camera capture failed");
httpd_resp_send_500(req);
return ESP_FAIL;
}
size_t frame_length = frame.length;
httpd_resp_set_type(req, "image/jpeg");
httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg");
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
res = httpd_resp_send(req, (const char *)frame.buffer, frame.length);
esp_cam_io_parl_free_buffer(frame);
int64_t fr_end = esp_timer_get_time();
ESP_LOGI(TAG, "JPG: %uB %ums", frame_length, (uint32_t)((fr_end - fr_start) / 1000));
return res;
}
static esp_err_t stream_handler(httpd_req_t *req) {
esp_err_t res = ESP_OK;
size_t _jpg_buf_len = 0;
uint8_t *_jpg_buf = NULL;
char *part_buf[128];
res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE);
if (res != ESP_OK) {
return res;
}
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
httpd_resp_set_hdr(req, "X-Framerate", "60");
while (true) {
int64_t last_frame = esp_timer_get_time();
esp_cam_io_parl_trans_t image;
if (esp_cam_io_parl_receive(esp_cam_io_parl_handle, &image, 5000) != ESP_OK) {
ESP_LOGE(TAG, "Camera capture failed");
res = ESP_FAIL;
} else {
_jpg_buf_len = image.length;
_jpg_buf = image.buffer;
}
if (res == ESP_OK) {
res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
}
if (res == ESP_OK) {
size_t hlen = snprintf((char *)part_buf, 128, _STREAM_PART, _jpg_buf_len);
res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen);
}
if (res == ESP_OK) {
res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len);
}
if (image.buffer) {
esp_cam_io_parl_free_buffer(image);
_jpg_buf = NULL;
} else if (_jpg_buf) {
free(_jpg_buf);
_jpg_buf = NULL;
}
if (res != ESP_OK) {
ESP_LOGE(TAG, "Send frame failed");
break;
}
int64_t frame_time = esp_timer_get_time() - last_frame;
frame_time /= 1000;
uint32_t avg_frame_time = ra_filter_run(&ra_filter, frame_time);
ESP_LOGI(TAG, "MJPG: %uB %ums (%.1ffps), AVG: %ums (%.1ffps)", (uint32_t)(_jpg_buf_len), (uint32_t)frame_time, 1000.0 / (uint32_t)frame_time, avg_frame_time, 1000.0 / avg_frame_time);
}
return res;
}
static void start_camera_server(void) {
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
httpd_uri_t capture_uri = {
.uri = "/capture",
.method = HTTP_GET,
.handler = capture_handler,
.user_ctx = NULL,
#ifdef CONFIG_HTTPD_WS_SUPPORT
.is_websocket = true,
.handle_ws_control_frames = false,
.supported_subprotocol = NULL,
#endif
};
httpd_uri_t stream_uri = {
.uri = "/stream",
.method = HTTP_GET,
.handler = stream_handler,
.user_ctx = NULL,
#ifdef CONFIG_HTTPD_WS_SUPPORT
.is_websocket = true,
.handle_ws_control_frames = false,
.supported_subprotocol = NULL,
#endif
};
ra_filter_init(&ra_filter, 20);
ESP_LOGI(TAG, "Starting capture server on port: '%d'", config.server_port);
if (httpd_start(&capture_httpd, &config) == ESP_OK) {
httpd_register_uri_handler(capture_httpd, &capture_uri);
}
config.server_port += 1;
config.ctrl_port += 1;
ESP_LOGI(TAG, "Starting stream server on port: '%d'", config.server_port);
if (httpd_start(&stream_httpd, &config) == ESP_OK) {
httpd_register_uri_handler(stream_httpd, &stream_uri);
}
}
void app_main(void) {
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
// Camera sensor configuration
static esp_camera_sensor_config_t camera_sensor_config = {
.pwdn_io = CAM_PWDN_PIN,
.reset_io = CAM_RESET_PIN,
.xclk_io = CAM_XCLK_PIN,
.xclk_hz = 20000000,
.sda_io = CAM_SDA_PIN,
.scl_io = CAM_SCL_PIN,
.ledc_timer = LEDC_TIMER_0,
.ledc_channel = LEDC_CHANNEL_0,
.pixel_format = PIXFORMAT_JPEG, // esp_cam_io_parl only supports JPEG images at the moment
.frame_size = FRAMESIZE_QVGA,
.jpeg_quality = 8,
};
// DVP port configuration
static esp_cam_io_parl_config_t esp_cam_io_parl_config = {
.data_width = 8,
.queue_frames = 1,
.pclk_io = CAM_PCLK_PIN,
.de_io = CAM_HREF_PIN,
.hsync_io = CAM_HSYNC_PIN,
.vsync_io = CAM_VSYNC_PIN, // Not implemented
.data_io = {
CAM_D0_PIN,
CAM_D1_PIN,
CAM_D2_PIN,
CAM_D3_PIN,
CAM_D4_PIN,
CAM_D5_PIN,
CAM_D6_PIN,
CAM_D7_PIN,
},
.flags = {
.free_clk = true,
.allow_pd = true,
},
};
esp_err_t err = esp_camera_sensor_init(&camera_sensor_config);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Camera init failed with error 0x%x", err);
return;
}
camera_sensor_t *sensor = esp_camera_sensor_get();
ESP_LOGI(TAG, "Camera detected! Current quality = %u", sensor->status.quality);
sensor->set_vflip(sensor, true); // Adjust if the image is flipped vertically
sensor->set_hmirror(sensor, false); // Adjust if the image is flipped horizontally
ESP_ERROR_CHECK(esp_cam_new_io_parl(&esp_cam_io_parl_config, &esp_cam_io_parl_handle));
ESP_ERROR_CHECK(esp_cam_io_parl_enable(esp_cam_io_parl_handle, true));
image_info_t *image = esp_camera_sensor_get_image(); // Prepare the frame allocation
ESP_ERROR_CHECK(esp_cam_io_parl_set_alloc_size(esp_cam_io_parl_handle, image->width * image->height / 4 + 2048, FRAME_BUFFER_CAPS));
wifi_init_softap();
start_camera_server();
}
```
---
# Kconfig configurations
You can go to `menuconfig` -> `Component config` -> `Parallel IO Camera configuration` to view the available configurations. Note that some targets may have different configurations.<br>
You can also enable the use of LP I2C for ESP32-C6, ESP32-P4 and ESP32-C5 for SCCB interface. Please check the TRM on each target for the pins of LP I2C.<br>
These are the list of default configurations for this component:
```c
CONFIG_ESP_CAM_IO_PARL_OV2640 y // Probes OV2640. ESP32-C5 and ESP32-H2 have this configuration disabled since this sensor does not support it
CONFIG_ESP_CAM_IO_PARL_OV3660 y // Probes OV3660
CONFIG_ESP_CAM_IO_PARL_OV5640 y // Probes OV5640
CONFIG_ESP_CAM_IO_PARL_OV5640_AF n // Allows OV5640 with Autofocus function
CONFIG_CAMERA_PAYLOAD_BUFFER_SIZE 0x8000 // Payload size: 32768
CONFIG_ESP_CAM_IO_PARL_SCCB_I2C_PORT0 y // Use the I2C0 port by default
CONFIG_ESP_CAM_IO_PARL_SCCB_I2C_PORT1 n // I2C1 only available on ESP32-P4 and ESP32-H2
CONFIG_ESP_CAM_IO_PARL_SCCB_LP_I2C_PORT0 n // Only available on ESP32-C6, ESP32-P4 and ESP32-C5
CONFIG_ESP_CAM_IO_PARL_SCCB_CLK_FREQ 100000 // Higher values allows for faster initialization for SCCB
```
# API Reference
## `esp_camera_sensor`
ESP Camera Sensor component included in this package to interface with OV2640, OV3660 and OV5640 camera sensors. It is a modified version of `esp_camera.h` component by Espressif.
```c
#include "esp_camera_sensor.h"
```
### Data Types
#### `esp_camera_sensor_config_t`
Configuration structure for camera sensor initialization.
| Field | Type | Description |
| --------------- | -------------------- | ------------------------------------------- |
| `pwdn_io` | `gpio_num_t` | GPIO pin for camera power-down line |
| `reset_io` | `gpio_num_t` | GPIO pin for camera reset line |
| `xclk_io` | `gpio_num_t` | GPIO pin for camera XCLK line |
| `sda_io` | `gpio_num_t` | GPIO pin for camera SDA line |
| `scl_io` | `gpio_num_t` | GPIO pin for camera SCL line |
| `xclk_hz` | `uint32_t` | XCLK frequency in Hz |
| `pixel_format` | `camera_pixformat_t` | Pixel format (`PIXFORMAT_*`) |
| `frame_size` | `camera_framesize_t` | Frame size (`FRAMESIZE_*`) |
| `ledc_timer` | `ledc_timer_t` | LEDC timer for XCLK generation |
| `ledc_channel` | `ledc_channel_t` | LEDC channel for XCLK generation |
| `jpeg_quality` | `int` | JPEG quality (0–63, lower = higher quality) |
#### `image_info_t`
Data structure containing image properties.
| Field | Type | Description |
| -------- | -------------------- | ---------------------- |
| `width` | `size_t` | Image width in pixels |
| `height` | `size_t` | Image height in pixels |
| `format` | `camera_pixformat_t` | Pixel format |
### Functions
#### `esp_camera_sensor_init`
```c
esp_err_t esp_camera_sensor_init(const esp_camera_sensor_config_t *config);
```
Initialize the camera driver and configure the sensor via SCCB/I2C.
**Parameters:**
* `config` — Pointer to camera configuration parameters.
**Returns:**
* `ESP_OK` — Success
* `ESP_ERR_INVALID_ARG` — Invalid parameters
* `ESP_ERR_CAMERA_NOT_DETECTED` — Sensor not detected
#### `esp_camera_sensor_deinit`
```c
esp_err_t esp_camera_sensor_deinit(void);
```
Deinitialize the camera driver.
**Returns:**
* `ESP_OK` — Success
* `ESP_ERR_INVALID_STATE` — Driver not initialized
#### `esp_camera_sensor_get_image`
```c
image_info_t *esp_camera_sensor_get_image(void);
```
Get image resolution information for frame allocation.
**Returns:**
Pointer to `image_info_t` structure.
#### `esp_camera_sensor_get`
```c
camera_sensor_t *esp_camera_sensor_get(void);
```
Get a pointer to the sensor control structure.
**Returns:**
Pointer to `camera_sensor_t` structure.
#### `esp_camera_sensor_erase_nvs`
```c
esp_err_t esp_camera_sensor_erase_nvs(const char *key);
```
Remove camera settings from NVS.
**Parameters:**
* `key` — Unique key for camera settings.
#### `esp_camera_sensor_save_to_nvs`
```c
esp_err_t esp_camera_sensor_save_to_nvs(const char *key);
```
Save camera settings to NVS.
**Parameters:**
* `key` — Unique key for camera settings.
#### `esp_camera_sensor_load_from_nvs`
```c
esp_err_t esp_camera_sensor_load_from_nvs(const char *key);
```
Load camera settings from NVS.
**Parameters:**
* `key` — Unique key for camera settings.
---
## `esp_cam_io_parl`
ESP Parallel IO Camera component to interface with the DVP port of the following camera sensor.
```c
#include "esp_cam_io_parl.h"
```
### Data Types
#### `esp_cam_io_parl_pclk_edge_t`
> **PCLK edge configuration for sampling incoming data.**
| Enumerator | Description |
| -------------------------- | ------------------------------------- |
| `ESP_CAM_IO_PARL_PCLK_NEG` | Sample PCLK data on the negative edge |
| `ESP_CAM_IO_PARL_PCLK_POS` | Sample PCLK data on the positive edge |
#### `esp_cam_io_parl_input_format_t`
> **Input color format. Only takes effect if `jpeg_en` is `0` (Not implemented)**
| Enumerator | Description |
| -------------------------- | ------------------------------------- |
| `ESP_CAM_IO_PARL_RGB565` | RGB565 (2 bytes per pixel) |
| `ESP_CAM_IO_PARL_RGB888` | RGB888 (3 bytes per pixel) |
| `ESP_CAM_IO_PARL_YUV422` | YUV422 (2 bytes per pixel) |
#### `esp_cam_io_parl_config_t`
> **Configuration structure for `esp_cam_io_parl`.**
| Field | Type | Description |
| -------------------- | -------------- | ----------------------------------------------------------- |
| `data_width` | `size_t` | DVP data width (8 or 16 bits depending on SoC capabilities) |
| `queue_frames` | `size_t` | Number of frames to be queued |
| `pclk_io` | `gpio_num_t` | PCLK GPIO pin |
| `pclk_sample_edge` | `esp_cam_io_parl_pclk_edge_t` | Sampling edge (`ESP_CAM_IO_PARL_PCLK_NEG` or `ESP_CAM_IO_PARL_PCLK_POS`) |
| `vsync_io` | `gpio_num_t` | VSYNC GPIO pin *(Not implemented)* |
| `de_io` | `gpio_num_t` | DE (HREF) GPIO pin, set to -1 if unused |
| `hsync_io` | `gpio_num_t` | HSYNC GPIO pin, set to -1 if unused |
| `data_io[]` | `gpio_num_t` | Data line GPIOs |
| `input_type` | `esp_cam_io_parl_input_format_t` | Input color format (Not implemented) |
| `flags.invert_vsync` | `bool` | Invert VSYNC *(Not implemented)* |
| `flags.invert_de` | `bool` | Invert DE pin (active low) |
| `flags.invert_hsync` | `bool` | Invert HSYNC pin (active on rising edge) |
| `flags.jpeg_en` | `bool` | JPEG input expected *(default)* |
| `flags.free_clk` | `bool` | PCLK is free-running |
| `flags.allow_pd` | `bool` | Allow power down |
#### `esp_cam_io_parl_trans_t`
> **Transaction buffer structure for received frames.**
| Field | Type | Description |
| -------- | ---------- | ----------------------- |
| `buffer` | `uint8_t*` | Pointer to frame buffer |
| `length` | `uint32_t` | Length of frame buffer |
#### `esp_cam_io_parl_handle_t`
> **Handle pointer to `esp_cam_io_parl`.**
### Functions
#### `esp_cam_new_io_parl`
```c
esp_err_t esp_cam_new_io_parl(const esp_cam_io_parl_config_t *config, esp_cam_io_parl_handle_t *ret_handle)
```
Creates and initializes a new `esp_cam_io_parl` handle.
**Parameters:**
* `config` — Pointer to configuration structure.
* `ret_handle` — Output handle.
**Returns:**
* `ESP_ERR_INVALID_ARG` — Invalid parameters.
* `ESP_ERR_NO_MEM` — Out of memory.
* `ESP_OK` — Success.
#### `esp_cam_del_io_parl`
```c
esp_err_t esp_cam_del_io_parl(esp_cam_io_parl_handle_t esp_cam_io_parl);
```
Deletes a `esp_cam_io_parl` handle and deinitializes resources.
**Parameters:**
* `esp_cam_io_parl` — Handle that was created with `esp_cam_new_io_parl` to delete.
**Returns:**
* `ESP_ERR_INVALID_ARG` — Handle is NULL.
* `ESP_OK` — Success.
#### `esp_cam_io_parl_set_alloc_size`
```c
esp_err_t esp_cam_io_parl_set_alloc_size(esp_cam_io_parl_handle_t esp_cam_io_parl, uint32_t alloc_size, uint32_t heap_caps);
```
Sets the allocation size for the frame buffer.
**Parameters:**
* `esp_cam_io_parl` — Handle that was created with `esp_cam_new_io_parl`
* `alloc_size` — Frame allocation size (JPEG recommended: `width * height / 4 + 2048`).
* `heap_caps` — Memory type (`MALLOC_CAP_INTERNAL` or `MALLOC_CAP_SPIRAM`).
**Returns:**
* `ESP_ERR_INVALID_ARG` — Invalid arguments.
* `ESP_OK` — Success.
#### `esp_cam_io_parl_enable`
```c
esp_err_t esp_cam_io_parl_enable(esp_cam_io_parl_handle_t esp_cam_io_parl, bool reset_queue);
```
Enables frame reception.
**Parameters:**
* `esp_cam_io_parl` — Handle that was created with `esp_cam_new_io_parl`
* `reset_queue` — Reset frame queue.
**Returns:**
* `ESP_ERR_INVALID_ARG` — Invalid handle.
* `ESP_ERR_INVALID_STATE` — Already enabled.
* `ESP_OK` — Success.
#### `esp_cam_io_parl_disable`
```c
esp_err_t esp_cam_io_parl_disable(esp_cam_io_parl_handle_t esp_cam_io_parl)
```
Disables frame reception.
**Parameters:**
* `esp_cam_io_parl` — Handle that was created with `esp_cam_new_io_parl`
**Returns:**
* `ESP_ERR_INVALID_ARG` — Invalid handle.
* `ESP_ERR_INVALID_STATE` — Already disabled.
* `ESP_OK` — Success.
#### `esp_cam_io_parl_receive`
```c
esp_err_t esp_cam_io_parl_receive(esp_cam_io_parl_handle_t esp_cam_io_parl, esp_cam_io_parl_trans_t *frame, int32_t timeout_ms);
```
Receives a frame from the queue.
**Parameters:**
* `esp_cam_io_parl` — Handle that was created with `esp_cam_new_io_parl`
* `frame` — the frame buffer object.
* `timeout_ms` — Timeout in milliseconds (-1 for no timeout).
**Returns:**
* `ESP_ERR_INVALID_ARG` — Invalid handle.
* `ESP_ERR_TIMEOUT` — Timeout expired.
* `ESP_OK` — Success.
#### `esp_cam_io_parl_receive_from_isr`
```c
esp_err_t esp_cam_io_parl_receive_from_isr(esp_cam_io_parl_handle_t esp_cam_io_parl, esp_cam_io_parl_trans_t *frame, bool *hp_task_woken);
```
Receives a frame from ISR context.
**Notes:**
* Should only be called in ISR.
* Callback must be non-blocking.
**Parameters:**
* `esp_cam_io_parl` — Handle that was created with `esp_cam_new_io_parl`
* `frame` — the frame buffer object.
* `hp_task_woken` — Whether the high priority task is woken.
**Returns:**
* `ESP_ERR_INVALID_ARG` — Invalid handle.
* `ESP_ERR_INVALID_STATE` — Called outside ISR.
* `ESP_FAIL` — Failed to receive.
* `ESP_OK` — Success.
#### `esp_cam_io_parl_free_buffer`
```c
esp_err_t esp_cam_io_parl_free_buffer(esp_cam_io_parl_trans_t frame);
```
Frees a previously received frame buffer.
**Parameters:**
* `frame` — The frame buffer object.
**Returns:**
* `ESP_ERR_INVALID_ARG` — Invalid buffer.
* `ESP_OK` — Success.
idf.py add-dependency "haqqihaziq/esp_cam_io_parl^0.1.0-beta.5"