Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 126 additions & 22 deletions sensors/ov3640.c
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,102 @@ static int set_image_options(sensor_t *sensor)
return ret;
}

/* OV3640 readout window used when the crop registers read back as garbage
* (the datasheet default of 0x3024/0x3025 is 0x1800 = 6144, which cannot be a
* real width for a 2048-pixel array). Also matches the scaling input size
* SIZE_IN = 2072 x 1548 that sensor_default_regs relies on. */
#define OV3640_ARRAY_W 2072
#define OV3640_ARRAY_H 1548

/**
* Crop and scale the OV3640 to the requested output size.
*
* The 0x3800..0x3815 windowing block used by the OV3660 driver does not exist
* on this sensor, which is why the size requested by set_framesize() was never
* applied. On the OV3640 the pipeline is (datasheet 4.2 and 5.7):
*
* crop HS/VS/HW/VH (0x3020..0x3027)
* -> zoom out SIZE_IN (0x335F..0x3361) / SIZE_OUT (0x3362..0x3364)
* -> ISP output 0x3088..0x308B, which is what the DVP port sends out
*
* Every stage must be smaller than (or equal to) the previous one.
*
* @param sensor sensor handle
* @param aspect_w crop width wanted, normally ratio_table.max_width
* @param aspect_h crop height wanted, normally ratio_table.max_height
* @param out_w requested output width
* @param out_h requested output height
*/
static int set_window(sensor_t *sensor, uint16_t aspect_w, uint16_t aspect_h,
uint16_t out_w, uint16_t out_h)
{
/* Current readout window; keep the crop inside it so that a wrong guess
* can only lose field of view, never pull in pixels outside the array */
int r_hs = read_reg16(sensor->slv_addr, OV3640_WIN_HS);
int r_vs = read_reg16(sensor->slv_addr, OV3640_WIN_VS);
int r_hw = read_reg16(sensor->slv_addr, OV3640_WIN_HW);
int r_vh = read_reg16(sensor->slv_addr, OV3640_WIN_VH);

uint16_t hs = (r_hs > 0 && r_hs < OV3640_ARRAY_W) ? (uint16_t)r_hs : 0;
uint16_t vs = (r_vs > 0 && r_vs < OV3640_ARRAY_H) ? (uint16_t)r_vs : 0;
uint16_t hw = (r_hw > 0 && r_hw <= OV3640_ARRAY_W - hs) ? (uint16_t)r_hw
: (OV3640_ARRAY_W - hs);
uint16_t vh = (r_vh > 0 && r_vh <= OV3640_ARRAY_H - vs) ? (uint16_t)r_vh
: (OV3640_ARRAY_H - vs);

uint16_t crop_w = (aspect_w < hw) ? aspect_w : hw;
uint16_t crop_h = (aspect_h < vh) ? aspect_h : vh;

/* The readout window can be narrower or shorter than the requested aspect
* ratio, and clamping only one dimension makes the zoom block stretch the
* image. Shrink the other dimension to keep the ratio, e.g. a 4:3 crop of
* 2048x1536 inside a 1787 wide window becomes 1787x1340. */
if ((uint32_t)crop_w * aspect_h > (uint32_t)crop_h * aspect_w) {
crop_w = (uint16_t)((uint32_t)crop_h * aspect_w / aspect_h);
} else if ((uint32_t)crop_w * aspect_h < (uint32_t)crop_h * aspect_w) {
crop_h = (uint16_t)((uint32_t)crop_w * aspect_h / aspect_w);
}

uint16_t crop_x = hs + (hw - crop_w) / 2;
uint16_t crop_y = vs + (vh - crop_h) / 2;

int ret = write_addr_reg(sensor->slv_addr, OV3640_WIN_HS, crop_x, crop_y)
|| write_addr_reg(sensor->slv_addr, OV3640_WIN_HW, crop_w, crop_h);

/* Zoom out: SIZE_OUT stays a hair above the final size so the pipeline has
* room to align, but must remain strictly smaller than SIZE_IN */
uint16_t scale_w = out_w + 8;
uint16_t scale_h = out_h + 4;
if (scale_w >= crop_w) {
scale_w = (out_w < crop_w) ? out_w : (crop_w - 1);
}
if (scale_h >= crop_h) {
scale_h = (out_h < crop_h) ? out_h : (crop_h - 1);
}

ret = ret
|| write_reg_bits(sensor->slv_addr, OV3640_DSP_CTRL_2, OV3640_SCALE_EN, 1)
|| write_reg(sensor->slv_addr, SIZE_IN_MISC, ((crop_h >> 4) & 0x70) | ((crop_w >> 8) & 0x0F))
|| write_reg(sensor->slv_addr, HSIZE_IN_L, crop_w & 0xFF)
|| write_reg(sensor->slv_addr, VSIZE_IN_L, crop_h & 0xFF)
|| write_reg(sensor->slv_addr, SIZE_OUT_MISC, ((scale_h >> 4) & 0x70) | ((scale_w >> 8) & 0x0F))
|| write_reg(sensor->slv_addr, HSIZE_OUT_L, scale_w & 0xFF)
|| write_reg(sensor->slv_addr, VSIZE_OUT_L, scale_h & 0xFF)
|| write_reg(sensor->slv_addr, ISP_XOUT_H, (out_w >> 8) & 0x0F)
|| write_reg(sensor->slv_addr, ISP_XOUT_L, out_w & 0xFF)
|| write_reg(sensor->slv_addr, ISP_YOUT_H, (out_h >> 8) & 0x07)
|| write_reg(sensor->slv_addr, ISP_YOUT_L, out_h & 0xFF);

if (ret == 0) {
ESP_LOGI(TAG, "Crop %u,%u %ux%u (readout %u,%u %ux%u) -> zoom %ux%u -> DVP %ux%u",
crop_x, crop_y, crop_w, crop_h, hs, vs, hw, vh,
scale_w, scale_h, out_w, out_h);
} else {
ESP_LOGE(TAG, "Setting output size to %ux%u failed", out_w, out_h);
}
return ret;
}

static int set_framesize(sensor_t *sensor, framesize_t framesize)
{
int ret = 0;
Expand All @@ -460,22 +556,21 @@ static int set_framesize(sensor_t *sensor, framesize_t framesize)
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);

/* OV3640 does not have the 0x3800..0x3815 windowing/output registers of the
* OV3660 this driver was derived from: crop + zoom-out + ISP output size
* have to be programmed through their own registers. */
/* 0x3012[6:4] selects the array readout mode: 000 QXGA, 001 XGA (2x2
* subsampled), 111 SXGA. Only the XGA mode shortens the readout, but it
* also changes the readout window and the Bayer phase, so it needs the
* full vendor XGA init sequence. Stay on the full QXGA array here. */
ret = write_reg(sensor->slv_addr, OV3640_FMT_CTRL, 0x00);
if (ret == 0) {
ret = set_window(sensor, settings.max_width, settings.max_height, 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);
}
Expand All @@ -497,16 +592,25 @@ static int set_framesize(sensor_t *sensor, framesize_t framesize)
ret = set_pll(sensor, false, 30, 1, 3, false, 0, true, 10);
}
} else {
//tuned for 16MHz XCLK and 8MHz PCLK
if (framesize > FRAMESIZE_HVGA) {
//8MHz SYSCLK and 8MHz PCLK (4.44 FPS)
ret = set_pll(sensor, false, 4, 1, 0, false, 2, true, 2);
} else if (framesize >= FRAMESIZE_QVGA) {
//16MHz SYSCLK and 8MHz PCLK (10.25 FPS)
ret = set_pll(sensor, false, 8, 1, 0, false, 2, true, 4);
} else {
//32MHz SYSCLK and 8MHz PCLK (17.77 FPS)
ret = set_pll(sensor, false, 8, 1, 0, false, 0, true, 8);
/* OV3640 has none of the OV3660 PLL registers this driver inherited:
* 0x303a..0x303d are the AEC metering window (AVS/AHW) on this part, so
* the old set_pll() calls were silently corrupting auto exposure on
* every frame size change, while 0x3824 / 0x460c do not exist at all.
* The real clock lives in:
* 0x300e[5:0] Rx_PLL -> PLLDiv = 64 - Rx_PLL
* 0x300f[7:6] FreqDiv, [5:4] Bit8Div, [3] bypass, [1:0] InDiv
* 0x3010[7:6] DvpDiv, [5] LaneDiv, [4] SensorDiv, [3:0] ScaleDiv
* The frame time is HTS (0x3028) x VTS (0x302a) / core clock and does
* not depend on the output size, so cropping cannot raise the frame
* rate. Measured with a 20MHz XCLK: frame rate ~= 0.414 x PLLDiv, i.e.
* PLLDiv 14 -> 5.8 fps and PLLDiv 7 -> 2.9 fps. Keep the vendor default
* (PLLDiv 14); larger values scale the DVP clock with it, which some
* hosts cannot sample reliably. */
ret = write_reg(sensor->slv_addr, OV3640_PLL_1, 0x32)
|| write_reg(sensor->slv_addr, OV3640_PLL_2, 0x21)
|| write_reg(sensor->slv_addr, OV3640_PLL_3, 0x20);
if (ret == 0) {
ESP_LOGD(TAG, "Clock: 0x300e=0x32 (PLLDiv=14) 0x300f=0x21 0x3010=0x20");
}
}

Expand Down
44 changes: 44 additions & 0 deletions sensors/private_include/ov3640_regs.h
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,50 @@
// 0: Disable
// 1: Enable

/* Image cropping (datasheet 4.2, table 4-2). OV3640 has no 0x3800..0x3815
* block: HS/VS are the window start, HW/VH the window width/height. */
#define OV3640_WIN_HS 0x3020 // {0x3020[15:8], 0x3021[7:0]} horizontal start
#define OV3640_WIN_VS 0x3022 // {0x3022[15:8], 0x3023[7:0]} vertical start
#define OV3640_WIN_HW 0x3024 // {0x3024[15:8], 0x3025[7:0]} horizontal width
#define OV3640_WIN_VH 0x3026 // {0x3026[15:8], 0x3027[7:0]} vertical height

/* DSP control 2 (datasheet 5.7): bit[5] Scale_En enables the zoom-out block */
#define OV3640_DSP_CTRL_2 0x3302
#define OV3640_SCALE_EN 0x20

/* OV3640 sizing chain (datasheet 5.7 + OV3640 Software Application Notes):
* SIZE_IN (0x335F..0x3361) -> SIZE_OUT (0x3362..0x3364) -> ISP OUT (0x3088..0x308B)
* Unlike OV3660/OV5640 the DVP output size is NOT taken from 0x3808/0x380A;
* those two alone leave the sensor running whatever sensor_default_regs set up
* (QVGA 320x240). Hsize_Out/Vsize_Out must be smaller than Hsize_In/Vsize_In
* (datasheet 5.7: "due to zoom out function limit"). */
#define SIZE_IN_MISC 0x335F // Bit[6:4]: Vsize_In[10:8], Bit[3:0]: Hsize_In[11:8]
#define HSIZE_IN_L 0x3360 // Hsize_In[7:0]
#define VSIZE_IN_L 0x3361 // Vsize_In[7:0]
#define SIZE_OUT_MISC 0x3362 // Bit[6:4]: Vsize_Out[10:8], Bit[3:0]: Hsize_Out[11:8]
#define HSIZE_OUT_L 0x3363 // Hsize_Out[7:0]
#define VSIZE_OUT_L 0x3364 // Vsize_Out[7:0]
#define ISP_XOUT_H 0x3088 // Bit[3:0]: X_out[11:8]
#define ISP_XOUT_L 0x3089 // X_out[7:0]
#define ISP_YOUT_H 0x308A // Bit[2:0]: Y_out[10:8]
#define ISP_YOUT_L 0x308B // Y_out[7:0]

/* System clock (datasheet table 7-1, sheets 2-3). The OV3660/OV5640 PLL
* registers this driver inherited (0x303a..0x303d / 0x3824 / 0x460c) do not
* exist on the OV3640: 0x303A..0x303D are the AEC metering window (AVS/AHW),
* so writing the inherited PLL values there corrupts auto exposure. */
#define OV3640_PLL_1 0x300E // Bit[5:0]: Rx_PLL, PLLDiv = 64 - Rx_PLL
#define OV3640_PLL_2 0x300F // Bit[7:6]: FreqDiv, [5:4]: Bit8Div, [3]: bypass,
// [1:0]: InDiv
#define OV3640_PLL_3 0x3010 // Bit[7:6]: DvpDiv, [5]: LaneDiv, [4]: SensorDiv,
// [3:0]: ScaleDiv
#define OV3640_CLK_CTRL 0x3011 // Bit[7]: freq doubler, [6]: bypass, [5:0]: CLK
// divider, CLK = XCLK1 / (value + 1)
#define OV3640_FMT_CTRL 0x3012 // Bit[7]: SRST, [6:4]: array resolution
// 000 QXGA, 001 XGA, 111 SXGA
#define OV3640_HTS_H 0x3028 // {0x3028, 0x3029}: horizontal total (default 2375)
#define OV3640_VTS_H 0x302A // {0x302A, 0x302B}: vertical total (default 1568)

#define SCALE_CTRL_1 0x5601 // Bit[6:4]: HDIV RW
// DCW scale times
// 000: DCW 1 time
Expand Down
Loading