/* * This file is part of the OpenMV project. * author: Juan Schiavoni * This work is licensed under the MIT license, see the file LICENSE for details. * * OV7725 driver. * */ #include #include #include #include "sccb.h" #include "ov7670.h" #include "ov7670_regs.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include #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[5].reg_num = 0xFF; frame[5].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, 10, 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; }