mirror of
https://github.com/espressif/esp32-camera.git
synced 2025-08-23 07:05:35 +08:00

* ov7670: fix ov7670_frame_control() Last value of array (VREF) was overwritten by end marker * OV7670: Adapt QQVGA VSTART value to remove bar on top Remove empty bar at the top of the frame. This is more visible on OV7675, but also present on OV7670
458 lines
13 KiB
C
458 lines
13 KiB
C
/*
|
|
* This file is part of the OpenMV project.
|
|
* author: Juan Schiavoni <juanjoseschiavoni@hotmail.com>
|
|
* This work is licensed under the MIT license, see the file LICENSE for details.
|
|
*
|
|
* OV7725 driver.
|
|
*
|
|
*/
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "sccb.h"
|
|
#include "ov7670.h"
|
|
#include "ov7670_regs.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include <stdio.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 = "ov7760";
|
|
#endif
|
|
|
|
static int ov7670_clkrc = 0x01;
|
|
|
|
/*
|
|
* The default register settings, as obtained from OmniVision. There
|
|
* is really no making sense of most of these - lots of "reserved" values
|
|
* and such.
|
|
*
|
|
* These settings give VGA YUYV.
|
|
*/
|
|
struct regval_list {
|
|
uint8_t reg_num;
|
|
uint8_t value;
|
|
};
|
|
|
|
static struct regval_list ov7670_default_regs[] = {
|
|
/* Sensor automatically sets output window when resolution changes. */
|
|
{TSLB, 0x04},
|
|
|
|
/* Frame rate 30 fps at 12 Mhz clock */
|
|
{CLKRC, 0x00},
|
|
{DBLV, 0x4A},
|
|
|
|
{COM10, COM10_VSYNC_NEG | COM10_PCLK_FREE},
|
|
|
|
/* Improve white balance */
|
|
{COM4, 0x40},
|
|
|
|
/* Improve color */
|
|
{RSVD_B0, 0x84},
|
|
|
|
/* Enable 50/60 Hz auto detection */
|
|
{COM11, COM11_EXP|COM11_HZAUTO},
|
|
|
|
/* Disable some delays */
|
|
{HSYST, 0},
|
|
{HSYEN, 0},
|
|
|
|
{MVFP, MVFP_SUN},
|
|
|
|
/* More reserved magic, some of which tweaks white balance */
|
|
{AWBC1, 0x0a},
|
|
{AWBC2, 0xf0},
|
|
{AWBC3, 0x34},
|
|
{AWBC4, 0x58},
|
|
{AWBC5, 0x28},
|
|
{AWBC6, 0x3a},
|
|
|
|
{AWBCTR3, 0x0a},
|
|
{AWBCTR2, 0x55},
|
|
{AWBCTR1, 0x11},
|
|
{AWBCTR0, 0x9e},
|
|
|
|
{COM8, COM8_FAST_AUTO|COM8_STEP_UNLIMIT|COM8_AGC_EN|COM8_AEC_EN|COM8_AWB_EN},
|
|
|
|
/* End marker is FF because in ov7670 the address of GAIN 0 and default value too. */
|
|
{0xFF, 0xFF},
|
|
};
|
|
|
|
static struct regval_list ov7670_fmt_yuv422[] = {
|
|
{ COM7, 0x0 }, /* Selects YUV mode */
|
|
{ RGB444, 0 }, /* No RGB444 please */
|
|
{ COM1, 0 }, /* CCIR601 */
|
|
{ COM15, COM15_R00FF },
|
|
{ MVFP, MVFP_SUN },
|
|
{ COM9, 0x6A }, /* 128x gain ceiling; 0x8 is reserved bit */
|
|
{ MTX1, 0x80 }, /* "matrix coefficient 1" */
|
|
{ MTX2, 0x80 }, /* "matrix coefficient 2" */
|
|
{ MTX3, 0 }, /* vb */
|
|
{ MTX4, 0x22 }, /* "matrix coefficient 4" */
|
|
{ MTX5, 0x5e }, /* "matrix coefficient 5" */
|
|
{ MTX6, 0x80 }, /* "matrix coefficient 6" */
|
|
{ COM13, COM13_UVSAT },
|
|
{ 0xff, 0xff }, /* END MARKER */
|
|
};
|
|
|
|
static struct regval_list ov7670_fmt_rgb565[] = {
|
|
{ COM7, COM7_FMT_RGB565 }, /* Selects RGB mode */
|
|
{ RGB444, 0 }, /* No RGB444 please */
|
|
{ COM1, 0x0 }, /* CCIR601 */
|
|
{ COM15, COM15_RGB565 |COM15_R00FF },
|
|
{ MVFP, MVFP_SUN },
|
|
{ COM9, 0x6A }, /* 128x gain ceiling; 0x8 is reserved bit */
|
|
{ MTX1, 0xb3 }, /* "matrix coefficient 1" */
|
|
{ MTX2, 0xb3 }, /* "matrix coefficient 2" */
|
|
{ MTX3, 0 }, /* vb */
|
|
{ MTX4, 0x3d }, /* "matrix coefficient 4" */
|
|
{ MTX5, 0xa7 }, /* "matrix coefficient 5" */
|
|
{ MTX6, 0xe4 }, /* "matrix coefficient 6" */
|
|
{ COM13, COM13_UVSAT },
|
|
{ 0xff, 0xff }, /* END MARKER */
|
|
};
|
|
|
|
|
|
static struct regval_list ov7670_vga[] = {
|
|
{ COM3, 0x00 },
|
|
{ COM14, 0x00 },
|
|
{ SCALING_XSC, 0x3A },
|
|
{ SCALING_YSC, 0x35 },
|
|
{ SCALING_DCWCTR, 0x11 },
|
|
{ SCALING_PCLK_DIV, 0xF0 },
|
|
{ SCALING_PCLK_DELAY, 0x02 },
|
|
{ 0xff, 0xff },
|
|
};
|
|
|
|
static struct regval_list ov7670_qvga[] = {
|
|
{ COM3, 0x04 },
|
|
{ COM14, 0x19 },
|
|
{ SCALING_XSC, 0x3A },
|
|
{ SCALING_YSC, 0x35 },
|
|
{ SCALING_DCWCTR, 0x11 },
|
|
{ SCALING_PCLK_DIV, 0xF1 },
|
|
{ SCALING_PCLK_DELAY, 0x02 },
|
|
{ 0xff, 0xff },
|
|
};
|
|
|
|
static struct regval_list ov7670_qqvga[] = {
|
|
{ COM3, 0x04 }, //DCW enable
|
|
{ COM14, 0x1a }, //pixel clock divided by 4, manual scaling enable, DCW and PCLK controlled by register
|
|
{ SCALING_XSC, 0x3a },
|
|
{ SCALING_YSC, 0x35 },
|
|
{ SCALING_DCWCTR, 0x22 }, //downsample by 4
|
|
{ SCALING_PCLK_DIV, 0xf2 }, //pixel clock divided by 4
|
|
{ SCALING_PCLK_DELAY, 0x02 },
|
|
{ 0xff, 0xff },
|
|
};
|
|
|
|
/*
|
|
* Write a list of register settings; ff/ff stops the process.
|
|
*/
|
|
static int ov7670_write_array(sensor_t *sensor, struct regval_list *vals)
|
|
{
|
|
int ret = 0;
|
|
|
|
while ( (vals->reg_num != 0xff || vals->value != 0xff) && (ret == 0) ) {
|
|
ret = SCCB_Write(sensor->slv_addr, vals->reg_num, vals->value);
|
|
|
|
ESP_LOGD(TAG, "reset reg %02X, W(%02X) R(%02X)", vals->reg_num,
|
|
vals->value, SCCB_Read(sensor->slv_addr, vals->reg_num) );
|
|
|
|
vals++;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Calculate the frame control registers.
|
|
*/
|
|
static int ov7670_frame_control(sensor_t *sensor, int hstart, int hstop, int vstart, int vstop)
|
|
{
|
|
struct regval_list frame[7];
|
|
|
|
frame[0].reg_num = HSTART;
|
|
frame[0].value = (hstart >> 3);
|
|
|
|
frame[1].reg_num = HSTOP;
|
|
frame[1].value = (hstop >> 3);
|
|
|
|
frame[2].reg_num = HREF;
|
|
frame[2].value = (((hstop & 0x07) << 3) | (hstart & 0x07));
|
|
|
|
frame[3].reg_num = VSTART;
|
|
frame[3].value = (vstart >> 2);
|
|
|
|
frame[4].reg_num = VSTOP;
|
|
frame[4].value = (vstop >> 2);
|
|
|
|
frame[5].reg_num = VREF;
|
|
frame[5].value = (((vstop & 0x02) << 2) | (vstart & 0x02));
|
|
|
|
/* End mark */
|
|
frame[6].reg_num = 0xFF;
|
|
frame[6].value = 0xFF;
|
|
|
|
return ov7670_write_array(sensor, frame);
|
|
}
|
|
|
|
static int reset(sensor_t *sensor)
|
|
{
|
|
int ret;
|
|
|
|
// Reset all registers
|
|
SCCB_Write(sensor->slv_addr, COM7, COM7_RESET);
|
|
|
|
// Delay 10 ms
|
|
vTaskDelay(10 / portTICK_PERIOD_MS);
|
|
|
|
ret = ov7670_write_array(sensor, ov7670_default_regs);
|
|
|
|
// Delay
|
|
vTaskDelay(30 / portTICK_PERIOD_MS);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int set_pixformat(sensor_t *sensor, pixformat_t pixformat)
|
|
{
|
|
int ret;
|
|
|
|
switch (pixformat) {
|
|
case PIXFORMAT_RGB565:
|
|
case PIXFORMAT_RGB888:
|
|
ret = ov7670_write_array(sensor, ov7670_fmt_rgb565);
|
|
break;
|
|
|
|
case PIXFORMAT_YUV422:
|
|
case PIXFORMAT_GRAYSCALE:
|
|
default:
|
|
ret = ov7670_write_array(sensor, ov7670_fmt_yuv422);
|
|
break;
|
|
}
|
|
|
|
vTaskDelay(30 / portTICK_PERIOD_MS);
|
|
|
|
/*
|
|
* If we're running RGB565, we must rewrite clkrc after setting
|
|
* the other parameters or the image looks poor. If we're *not*
|
|
* doing RGB565, we must not rewrite clkrc or the image looks
|
|
* *really* poor.
|
|
*
|
|
* (Update) Now that we retain clkrc state, we should be able
|
|
* to write it unconditionally, and that will make the frame
|
|
* rate persistent too.
|
|
*/
|
|
if (pixformat == PIXFORMAT_RGB565) {
|
|
ret = SCCB_Write(sensor->slv_addr, CLKRC, ov7670_clkrc);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int set_framesize(sensor_t *sensor, framesize_t framesize)
|
|
{
|
|
int ret;
|
|
|
|
// store clkrc before changing window settings...
|
|
ov7670_clkrc = SCCB_Read(sensor->slv_addr, CLKRC);
|
|
|
|
switch (framesize){
|
|
case FRAMESIZE_VGA:
|
|
if( (ret = ov7670_write_array(sensor, ov7670_vga)) == 0 ) {
|
|
/* These values from Omnivision */
|
|
ret = ov7670_frame_control(sensor, 158, 14, 10, 490);
|
|
}
|
|
break;
|
|
case FRAMESIZE_QVGA:
|
|
if( (ret = ov7670_write_array(sensor, ov7670_qvga)) == 0 ) {
|
|
/* These values from Omnivision */
|
|
ret = ov7670_frame_control(sensor, 158, 14, 10, 490);
|
|
}
|
|
break;
|
|
case FRAMESIZE_QQVGA:
|
|
if( (ret = ov7670_write_array(sensor, ov7670_qqvga)) == 0 ) {
|
|
/* These values from Omnivision */
|
|
ret = ov7670_frame_control(sensor, 158, 14, 12, 490);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
ret = -1;
|
|
}
|
|
|
|
vTaskDelay(30 / portTICK_PERIOD_MS);
|
|
|
|
if (ret == 0) {
|
|
sensor->status.framesize = framesize;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int set_colorbar(sensor_t *sensor, int enable)
|
|
{
|
|
uint8_t ret = 0;
|
|
// Read register scaling_xsc
|
|
uint8_t reg = SCCB_Read(sensor->slv_addr, SCALING_XSC);
|
|
|
|
// Pattern to set color bar bit[0]=0 in every case
|
|
reg = SCALING_XSC_CBAR(reg);
|
|
|
|
// Write pattern to SCALING_XSC
|
|
ret = SCCB_Write(sensor->slv_addr, SCALING_XSC, reg);
|
|
|
|
// Read register scaling_ysc
|
|
reg = SCCB_Read(sensor->slv_addr, SCALING_YSC);
|
|
|
|
// Pattern to set color bar bit[0]=0 in every case
|
|
reg = SCALING_YSC_CBAR(reg, enable);
|
|
|
|
// Write pattern to SCALING_YSC
|
|
ret = ret | SCCB_Write(sensor->slv_addr, SCALING_YSC, reg);
|
|
|
|
// return 0 or 0xFF
|
|
return ret;
|
|
}
|
|
|
|
static int set_whitebal(sensor_t *sensor, int enable)
|
|
{
|
|
// Read register COM8
|
|
uint8_t reg = SCCB_Read(sensor->slv_addr, COM8);
|
|
|
|
// Set white bal on/off
|
|
reg = COM8_SET_AWB(reg, enable);
|
|
|
|
// Write back register COM8
|
|
return SCCB_Write(sensor->slv_addr, COM8, reg);
|
|
}
|
|
|
|
static int set_gain_ctrl(sensor_t *sensor, int enable)
|
|
{
|
|
// Read register COM8
|
|
uint8_t reg = SCCB_Read(sensor->slv_addr, COM8);
|
|
|
|
// Set white bal on/off
|
|
reg = COM8_SET_AGC(reg, enable);
|
|
|
|
// Write back register COM8
|
|
return SCCB_Write(sensor->slv_addr, COM8, reg);
|
|
}
|
|
|
|
static int set_exposure_ctrl(sensor_t *sensor, int enable)
|
|
{
|
|
// Read register COM8
|
|
uint8_t reg = SCCB_Read(sensor->slv_addr, COM8);
|
|
|
|
// Set white bal on/off
|
|
reg = COM8_SET_AEC(reg, enable);
|
|
|
|
// Write back register COM8
|
|
return SCCB_Write(sensor->slv_addr, COM8, reg);
|
|
}
|
|
|
|
static int set_hmirror(sensor_t *sensor, int enable)
|
|
{
|
|
// Read register MVFP
|
|
uint8_t reg = SCCB_Read(sensor->slv_addr, MVFP);
|
|
|
|
// Set mirror on/off
|
|
reg = MVFP_SET_MIRROR(reg, enable);
|
|
|
|
// Write back register MVFP
|
|
return SCCB_Write(sensor->slv_addr, MVFP, reg);
|
|
}
|
|
|
|
static int set_vflip(sensor_t *sensor, int enable)
|
|
{
|
|
// Read register MVFP
|
|
uint8_t reg = SCCB_Read(sensor->slv_addr, MVFP);
|
|
|
|
// Set mirror on/off
|
|
reg = MVFP_SET_FLIP(reg, enable);
|
|
|
|
// Write back register MVFP
|
|
return SCCB_Write(sensor->slv_addr, MVFP, reg);
|
|
}
|
|
|
|
static int init_status(sensor_t *sensor)
|
|
{
|
|
sensor->status.awb = 0;
|
|
sensor->status.aec = 0;
|
|
sensor->status.agc = 0;
|
|
sensor->status.hmirror = 0;
|
|
sensor->status.vflip = 0;
|
|
sensor->status.colorbar = 0;
|
|
return 0;
|
|
}
|
|
|
|
static int set_dummy(sensor_t *sensor, int val){ return -1; }
|
|
static int set_gainceiling_dummy(sensor_t *sensor, gainceiling_t val){ return -1; }
|
|
|
|
int ov7670_detect(int slv_addr, sensor_id_t *id)
|
|
{
|
|
if (OV7670_SCCB_ADDR == slv_addr) {
|
|
SCCB_Write(slv_addr, 0xFF, 0x01);//bank sensor
|
|
uint16_t PID = SCCB_Read(slv_addr, 0x0A);
|
|
if (OV7670_PID == PID) {
|
|
id->PID = PID;
|
|
id->VER = SCCB_Read(slv_addr, REG_VER);
|
|
id->MIDL = SCCB_Read(slv_addr, REG_MIDL);
|
|
id->MIDH = SCCB_Read(slv_addr, REG_MIDH);
|
|
return PID;
|
|
} else {
|
|
ESP_LOGI(TAG, "Mismatch PID=0x%x", PID);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int ov7670_init(sensor_t *sensor)
|
|
{
|
|
// Set function pointers
|
|
sensor->reset = reset;
|
|
sensor->init_status = init_status;
|
|
sensor->set_pixformat = set_pixformat;
|
|
sensor->set_framesize = set_framesize;
|
|
sensor->set_colorbar = set_colorbar;
|
|
sensor->set_whitebal = set_whitebal;
|
|
sensor->set_gain_ctrl = set_gain_ctrl;
|
|
sensor->set_exposure_ctrl = set_exposure_ctrl;
|
|
sensor->set_hmirror = set_hmirror;
|
|
sensor->set_vflip = set_vflip;
|
|
|
|
//not supported
|
|
sensor->set_brightness= set_dummy;
|
|
sensor->set_saturation= set_dummy;
|
|
sensor->set_quality = set_dummy;
|
|
sensor->set_gainceiling = set_gainceiling_dummy;
|
|
sensor->set_aec2 = set_dummy;
|
|
sensor->set_aec_value = set_dummy;
|
|
sensor->set_special_effect = set_dummy;
|
|
sensor->set_wb_mode = set_dummy;
|
|
sensor->set_ae_level = set_dummy;
|
|
sensor->set_dcw = set_dummy;
|
|
sensor->set_bpc = set_dummy;
|
|
sensor->set_wpc = set_dummy;
|
|
sensor->set_awb_gain = set_dummy;
|
|
sensor->set_agc_gain = set_dummy;
|
|
sensor->set_raw_gma = set_dummy;
|
|
sensor->set_lenc = set_dummy;
|
|
sensor->set_sharpness = set_dummy;
|
|
sensor->set_denoise = set_dummy;
|
|
|
|
// Retrieve sensor's signature
|
|
sensor->id.MIDH = SCCB_Read(sensor->slv_addr, REG_MIDH);
|
|
sensor->id.MIDL = SCCB_Read(sensor->slv_addr, REG_MIDL);
|
|
sensor->id.PID = SCCB_Read(sensor->slv_addr, REG_PID);
|
|
sensor->id.VER = SCCB_Read(sensor->slv_addr, REG_VER);
|
|
|
|
ESP_LOGD(TAG, "OV7670 Attached");
|
|
|
|
return 0;
|
|
}
|