From 8ce8846ae52fda5bdd5816e3b7228329b535005a Mon Sep 17 00:00:00 2001 From: Marc Hoersken Date: Mon, 3 Nov 2025 23:10:04 +0100 Subject: [PATCH 1/4] gc0308: add proper support for sensor image and effect settings This includes contrast, brightness, saturation, sharpness, white-balance, gain & exposure levels and special effects. --- sensors/gc0308.c | 300 +++++++++++++++++++++++--- sensors/private_include/gc0308_regs.h | 124 ++++++++++- 2 files changed, 395 insertions(+), 29 deletions(-) diff --git a/sensors/gc0308.c b/sensors/gc0308.c index 469f0385e2..ca8c664fcb 100644 --- a/sensors/gc0308.c +++ b/sensors/gc0308.c @@ -277,32 +277,131 @@ static int set_framesize(sensor_t *sensor, framesize_t framesize) return 0; } -static int set_contrast(sensor_t *sensor, int contrast) +static int set_contrast(sensor_t *sensor, int level) { - if (contrast > 0) { - sensor->status.contrast = contrast; - write_reg(sensor->slv_addr, 0xfe, 0x00); - write_reg(sensor->slv_addr, 0xb3, contrast); + int ret = 0; + // GC0308 contrast range: -2 to +2 (mapped to register values) + if (level < -2 || level > 2) { + return -1; } - return 0; + ret = write_reg(sensor->slv_addr, 0xfe, 0x00); + // Adjust contrast (CONTRAST register) + // Default value is 0x40, adjust based on level + uint8_t contrast_val = 0x40 + (level * 0x10); + ret |= write_reg(sensor->slv_addr, CONTRAST, contrast_val); + if (ret == 0) { + sensor->status.contrast = level; + ESP_LOGD(TAG, "Set contrast to: %d", level); + } + return ret; } -static int set_global_gain(sensor_t *sensor, int gain_level) +static int set_brightness(sensor_t *sensor, int level) { - if (gain_level != 0) { - write_reg(sensor->slv_addr, 0xfe, 0x00); - write_reg(sensor->slv_addr, 0x50, gain_level); + int ret = 0; + // GC0308 brightness range: -2 to +2 (mapped to register values) + if (level < -2 || level > 2) { + return -1; } - return 0; + ret = write_reg(sensor->slv_addr, 0xfe, 0x00); + // Adjust brightness via LSC target Y base (LSC_RED_B2) + // Default value is 0x48, adjust based on level + uint8_t brightness_val = 0x48 + (level * 0x10); + ret |= write_reg(sensor->slv_addr, LSC_RED_B2, brightness_val); + if (ret == 0) { + sensor->status.brightness = level; + ESP_LOGD(TAG, "Set brightness to: %d", level); + } + return ret; +} + +static int set_saturation(sensor_t *sensor, int level) +{ + int ret = 0; + // GC0308 saturation range: -2 to +2 + if (level < -2 || level > 2) { + return -1; + } + ret = write_reg(sensor->slv_addr, 0xfe, 0x00); + // Adjust saturation via Cb and Cr gain (SATURATION_CB1, SATURATION_CR1) + // Default values: SATURATION_CB=0xcb, SATURATION_CB1=0x10, SATURATION_CR1=0x90 + uint8_t sat_base = 0x40 + (level * 0x10); + ret |= write_reg(sensor->slv_addr, SATURATION_CB1, sat_base); + ret |= write_reg(sensor->slv_addr, SATURATION_CR1, sat_base + 0x50); + if (ret == 0) { + sensor->status.saturation = level; + ESP_LOGD(TAG, "Set saturation to: %d", level); + } + return ret; +} + +static int set_sharpness(sensor_t *sensor, int level) +{ + int ret = 0; + // GC0308 sharpness range: -2 to +2 + if (level < -2 || level > 2) { + return -1; + } + ret = write_reg(sensor->slv_addr, 0xfe, 0x00); + // Adjust sharpness via edge enhancement (EDGE_DEC_SA1-SA3) + // Lower values = less sharp, higher values = more sharp + uint8_t sharp_val = 0x20 + (level * 0x08); + ret |= write_reg(sensor->slv_addr, EDGE_DEC_SA1, sharp_val); + ret |= write_reg(sensor->slv_addr, EDGE_DEC_SA2, sharp_val); + ret |= write_reg(sensor->slv_addr, EDGE_DEC_SA3, sharp_val); + if (ret == 0) { + sensor->status.sharpness = level; + ESP_LOGD(TAG, "Set sharpness to: %d", level); + } + return ret; +} + +static int set_whitebal(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg(sensor->slv_addr, 0xfe, 0x00); + // AWB enable/disable via AEC_MODE1 register, AWB_ENABLE bit + ret |= set_reg_bits(sensor->slv_addr, AEC_MODE1, 1, 0x01, enable != 0); + if (ret == 0) { + sensor->status.awb = enable; + ESP_LOGD(TAG, "Set AWB to: %d", enable); + } + return ret; +} + +static int set_gain_ctrl(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg(sensor->slv_addr, 0xfe, 0x00); + // AGC enable/disable via AEC_MODE1 register, AGC_ENABLE bit + ret |= set_reg_bits(sensor->slv_addr, AEC_MODE1, 2, 0x01, enable != 0); + if (ret == 0) { + sensor->status.agc = enable; + ESP_LOGD(TAG, "Set AGC to: %d", enable); + } + return ret; +} + +static int set_exposure_ctrl(sensor_t *sensor, int enable) +{ + int ret = 0; + ret = write_reg(sensor->slv_addr, 0xfe, 0x00); + // AEC enable/disable via AEC_MODE1 register, AEC_ENABLE bit + ret |= set_reg_bits(sensor->slv_addr, AEC_MODE1, 0, 0x01, enable != 0); + if (ret == 0) { + sensor->status.aec = enable; + ESP_LOGD(TAG, "Set AEC to: %d", enable); + } + return ret; } static int set_hmirror(sensor_t *sensor, int enable) { int ret = 0; - sensor->status.hmirror = enable; ret = write_reg(sensor->slv_addr, 0xfe, 0x00); - ret |= set_reg_bits(sensor->slv_addr, 0x14, 0, 0x01, enable != 0); + ret |= set_reg_bits(sensor->slv_addr, CISCTL_MODE1, 0, 0x01, enable != 0); if (ret == 0) { + sensor->status.hmirror = enable; ESP_LOGD(TAG, "Set h-mirror to: %d", enable); } return ret; @@ -311,20 +410,43 @@ static int set_hmirror(sensor_t *sensor, int enable) static int set_vflip(sensor_t *sensor, int enable) { int ret = 0; - sensor->status.vflip = enable; ret = write_reg(sensor->slv_addr, 0xfe, 0x00); - ret |= set_reg_bits(sensor->slv_addr, 0x14, 1, 0x01, enable != 0); + ret |= set_reg_bits(sensor->slv_addr, CISCTL_MODE1, 1, 0x01, enable != 0); if (ret == 0) { + sensor->status.vflip = enable; ESP_LOGD(TAG, "Set v-flip to: %d", enable); } return ret; } + +static int set_agc_gain(sensor_t *sensor, int gain) +{ + int ret = 0; + // GC0308 AGC gain range: 0-30 (standard sensor API range) + // Maps to hardware register values 0x00-0x3F (6-bit effective range) + // Hardware default is 0x14 (20) + if (gain < 0) { + gain = 0; + } else if (gain > 30) { + gain = 30; + } + // Map API range 0-30 to hardware range 0-63 (approximately 2x multiplier) + uint8_t gain_value = (gain * 63) / 30; + ret = write_reg(sensor->slv_addr, 0xfe, 0x00); + ret |= write_reg(sensor->slv_addr, GLOBAL_GAIN, gain_value); + if (ret == 0) { + sensor->status.agc_gain = gain; + ESP_LOGD(TAG, "Set AGC gain to: %d (hw: 0x%02x)", gain, gain_value); + } + return ret; +} + static int set_colorbar(sensor_t *sensor, int enable) { int ret = 0; ret = write_reg(sensor->slv_addr, 0xfe, 0x00); - ret |= set_reg_bits(sensor->slv_addr, 0x2e, 0, 0x01, enable); + ret |= set_reg_bits(sensor->slv_addr, OUT_CTRL, 0, 0x01, enable); if (ret == 0) { sensor->status.colorbar = enable; ESP_LOGD(TAG, "Set colorbar to: %d", enable); @@ -332,6 +454,132 @@ static int set_colorbar(sensor_t *sensor, int enable) return ret; } +static int set_special_effect(sensor_t *sensor, int effect) +{ + int ret = 0; + // Effect values: 0=Normal, 1=Negative, 2=Grayscale, 3=Red Tint, 4=Green Tint, 5=Blue Tint, 6=Sepia + if (effect < 0 || effect > 6) { + return -1; + } + ret = write_reg(sensor->slv_addr, 0xfe, 0x00); + switch (effect) { + case 0: // Normal + ret |= set_reg_bits(sensor->slv_addr, SPECIAL_EFFECT, 0, 0x03, EFFECT_NORMAL); + ret |= write_reg(sensor->slv_addr, EFFECT_MODE, 0x0a); + ret |= write_reg(sensor->slv_addr, FIXED_CB, 0x7f); + ret |= write_reg(sensor->slv_addr, FIXED_CR, 0xfa); + break; + case 1: // Negative + ret |= set_reg_bits(sensor->slv_addr, SPECIAL_EFFECT, 0, 0x03, EFFECT_NEGATIVE); + break; + case 2: // Grayscale (B&W) + ret |= set_reg_bits(sensor->slv_addr, SPECIAL_EFFECT, 0, 0x03, EFFECT_GRAYSCALE); + ret |= write_reg(sensor->slv_addr, EFFECT_MODE, 0x0a); + ret |= write_reg(sensor->slv_addr, FIXED_CB, 0x7f); + ret |= write_reg(sensor->slv_addr, FIXED_CR, 0xfa); + break; + case 3: // Red Tint + ret |= set_reg_bits(sensor->slv_addr, SPECIAL_EFFECT, 0, 0x03, EFFECT_GRAYSCALE); + ret |= write_reg(sensor->slv_addr, EFFECT_MODE, 0x0a); + ret |= write_reg(sensor->slv_addr, FIXED_CB, 0x80); + ret |= write_reg(sensor->slv_addr, FIXED_CR, 0xc0); + break; + case 4: // Green Tint + ret |= set_reg_bits(sensor->slv_addr, SPECIAL_EFFECT, 0, 0x03, EFFECT_GRAYSCALE); + ret |= write_reg(sensor->slv_addr, EFFECT_MODE, 0x0a); + ret |= write_reg(sensor->slv_addr, FIXED_CB, 0x50); + ret |= write_reg(sensor->slv_addr, FIXED_CR, 0x50); + break; + case 5: // Blue Tint + ret |= set_reg_bits(sensor->slv_addr, SPECIAL_EFFECT, 0, 0x03, EFFECT_GRAYSCALE); + ret |= write_reg(sensor->slv_addr, EFFECT_MODE, 0x0a); + ret |= write_reg(sensor->slv_addr, FIXED_CB, 0xa0); + ret |= write_reg(sensor->slv_addr, FIXED_CR, 0x80); + break; + case 6: // Sepia + ret |= set_reg_bits(sensor->slv_addr, SPECIAL_EFFECT, 0, 0x03, EFFECT_GRAYSCALE); + ret |= write_reg(sensor->slv_addr, EFFECT_MODE, 0x0a); + ret |= write_reg(sensor->slv_addr, FIXED_CB, 0xd0); + ret |= write_reg(sensor->slv_addr, FIXED_CR, 0x28); + break; + default: + ret = -1; + break; + } + if (ret == 0) { + sensor->status.special_effect = effect; + ESP_LOGD(TAG, "Set special effect to: %d", effect); + } + return ret; +} + +static int set_wb_mode(sensor_t *sensor, int mode) +{ + int ret = 0; + // WB modes: 0=Auto, 1=Sunny, 2=Cloudy, 3=Office, 4=Home + if (mode < 0 || mode > 4) { + return -1; + } + ret = write_reg(sensor->slv_addr, 0xfe, 0x00); + if (mode == 0) { + // Auto WB - enable AWB via AEC_MODE1 + ret |= set_reg_bits(sensor->slv_addr, AEC_MODE1, 1, 0x01, 1); + } else { + // Manual WB - disable AWB and set specific gains + ret |= set_reg_bits(sensor->slv_addr, AEC_MODE1, 1, 0x01, 0); + switch (mode) { + case 1: // Sunny + ret |= write_reg(sensor->slv_addr, AWB_R_GAIN, 0x74); + ret |= write_reg(sensor->slv_addr, AWB_G_GAIN, 0x52); + ret |= write_reg(sensor->slv_addr, AWB_B_GAIN, 0x40); + break; + case 2: // Cloudy + ret |= write_reg(sensor->slv_addr, AWB_R_GAIN, 0x8c); + ret |= write_reg(sensor->slv_addr, AWB_G_GAIN, 0x50); + ret |= write_reg(sensor->slv_addr, AWB_B_GAIN, 0x40); + break; + case 3: // Office + ret |= write_reg(sensor->slv_addr, AWB_R_GAIN, 0x48); + ret |= write_reg(sensor->slv_addr, AWB_G_GAIN, 0x40); + ret |= write_reg(sensor->slv_addr, AWB_B_GAIN, 0x5c); + break; + case 4: // Home + ret |= write_reg(sensor->slv_addr, AWB_R_GAIN, 0x40); + ret |= write_reg(sensor->slv_addr, AWB_G_GAIN, 0x54); + ret |= write_reg(sensor->slv_addr, AWB_B_GAIN, 0x70); + break; + default: + ret = -1; + break; + } + } + if (ret == 0) { + sensor->status.wb_mode = mode; + ESP_LOGD(TAG, "Set WB mode to: %d", mode); + } + return ret; +} + +static int set_ae_level(sensor_t *sensor, int level) +{ + int ret = 0; + // AE level range: -2 to +2 + if (level < -2 || level > 2) { + return -1; + } + ret = write_reg(sensor->slv_addr, 0xfe, 0x01); + // Adjust AE target via AEC_TARGET_Y register (Page 1) + // Default is around 0x19, adjust based on level + uint8_t ae_target = 0x19 + (level * 0x08); + ret |= write_reg(sensor->slv_addr, AEC_TARGET_Y, ae_target); + ret |= write_reg(sensor->slv_addr, 0xfe, 0x00); + if (ret == 0) { + sensor->status.ae_level = level; + ESP_LOGD(TAG, "Set AE level to: %d", level); + } + return ret; +} + static int get_reg(sensor_t *sensor, int reg, int mask) { int ret = 0; @@ -433,27 +681,27 @@ int esp32_camera_gc0308_init(sensor_t *sensor) sensor->set_pixformat = set_pixformat; sensor->set_framesize = set_framesize; sensor->set_contrast = set_contrast; - sensor->set_brightness = set_dummy; - sensor->set_saturation = set_dummy; - sensor->set_sharpness = set_dummy; + sensor->set_brightness = set_brightness; + sensor->set_saturation = set_saturation; + sensor->set_sharpness = set_sharpness; sensor->set_denoise = set_dummy; sensor->set_gainceiling = set_gainceiling_dummy; sensor->set_quality = set_dummy; sensor->set_colorbar = set_colorbar; - sensor->set_whitebal = set_dummy; - sensor->set_gain_ctrl = set_global_gain; - sensor->set_exposure_ctrl = set_dummy; + 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_aec2 = set_dummy; sensor->set_awb_gain = set_dummy; - sensor->set_agc_gain = set_dummy; + sensor->set_agc_gain = set_agc_gain; 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_special_effect = set_special_effect; + sensor->set_wb_mode = set_wb_mode; + sensor->set_ae_level = set_ae_level; sensor->set_dcw = set_dummy; sensor->set_bpc = set_dummy; diff --git a/sensors/private_include/gc0308_regs.h b/sensors/private_include/gc0308_regs.h index f1cb4532b7..69d899ce81 100644 --- a/sensors/private_include/gc0308_regs.h +++ b/sensors/private_include/gc0308_regs.h @@ -1,10 +1,12 @@ /* * GC0308 register definitions. + * Based on GC0308 datasheet specifications */ #ifndef __GC0308_REG_REGS_H__ #define __GC0308_REG_REGS_H__ -#define RESET_RELATED 0xfe // Bit[7]: Software reset +// System Control Registers +#define RESET_RELATED 0xfe // Bit[7]: Software reset // Bit[6:5]: NA // Bit[4]: CISCTL_restart_n // Bit[3:1]: NA @@ -12,14 +14,130 @@ // 0:page0 // 1:page1 +// Page 0 Registers -// page0: +// Windowing Registers +#define ROW_START_H 0x05 // Row start address high byte +#define ROW_START_L 0x06 // Row start address low byte +#define COL_START_H 0x07 // Column start address high byte +#define COL_START_L 0x08 // Column start address low byte +#define WIN_HEIGHT_H 0x09 // Window height high byte +#define WIN_HEIGHT_L 0x0a // Window height low byte +#define WIN_WIDTH_H 0x0b // Window width high byte +#define WIN_WIDTH_L 0x0c // Window width low byte +// Analog Circuit Control +#define ANALOG_MODE1 0x17 // Analog mode control 1 +#define ANALOG_MODE2 0x1a // Analog mode control 2 +#define PCLK_DIV 0x28 // PCLK divider control (frequency division) +// Sensor Control +#define CISCTL_MODE1 0x14 // Bit[1]: V_flip, Bit[0]: H_mirror + +// Output Format +#define OUTPUT_FMT 0x24 // Output format control + // Bit[3:0]: Output format selection + // 0010: YCbYCr (YUV422) + // 0110: RGB565 + // others: see datasheet + +// AWB/AEC/AGC Control +#define AEC_MODE1 0x22 // Auto control modes + // Bit[0]: AEC enable (1=on, 0=off) + // Bit[1]: AWB enable (1=on, 0=off) + // Bit[2]: AGC enable (1=on, 0=off) + +// Special Effects and Output Control +#define SPECIAL_EFFECT 0x23 // Special effect control + // Bit[1:0]: Effect mode + // 00: Normal + // 01: Negative + // 10: Grayscale/Color tint mode +#define OUT_CTRL 0x2e // Output control + // Bit[0]: Color bar enable + +// Color Effect Registers (for tint effects when in grayscale mode) +#define FIXED_CB 0x20 // Fixed Cb value for special effects +#define FIXED_CR 0x21 // Fixed Cr value for special effects +#define EFFECT_MODE 0x2d // Effect mode additional control + +// Global Gain +#define GLOBAL_GAIN 0x50 // Global gain control (hardware: 0-63, 6-bit) + // API uses 0-30 range, mapped to hardware values + +// Contrast +#define CONTRAST 0xb3 // Contrast control (0-255) + +// Manual White Balance Gain (when AWB disabled) +#define AWB_R_GAIN 0x77 // Red channel gain for manual WB +#define AWB_G_GAIN 0x78 // Green channel gain for manual WB +#define AWB_B_GAIN 0x79 // Blue channel gain for manual WB + +// Edge Enhancement (Sharpness) +#define EDGE_DEC_SA1 0x8b // Edge decimation smooth area 1 +#define EDGE_DEC_SA2 0x8c // Edge decimation smooth area 2 +#define EDGE_DEC_SA3 0x8d // Edge decimation smooth area 3 +#define EDGE_DEC_SA4 0x8e // Edge decimation smooth area 4 +#define EDGE_DEC_SA5 0x8f // Edge decimation smooth area 5 +#define EDGE_DEC_SA6 0x90 // Edge decimation smooth area 6 +#define EDGE_DEC_SA7 0x91 // Edge decimation smooth area 7 +#define EDGE_DEC_SA8 0x92 // Edge decimation smooth area 8 + +// Color Matrix +#define CMX1 0x93 // Color matrix coefficient 1 +#define CMX2 0x94 // Color matrix coefficient 2 +#define CMX3 0x95 // Color matrix coefficient 3 +#define CMX4 0x96 // Color matrix coefficient 4 +#define CMX5 0x97 // Color matrix coefficient 5 +#define CMX6 0x98 // Color matrix coefficient 6 + +// Saturation Control +#define SATURATION_CB 0xd0 // Cb saturation control +#define SATURATION_CB1 0xd1 // Cb saturation control 1 +#define SATURATION_CR1 0xd2 // Cr saturation control 1 + +// Lens Shading Correction (Brightness) +#define LSC_RED_B2 0xd3 // LSC target Y base (affects brightness) + +// Subsample Window (Page 0 for coordinate reference) +#define SUB_COL_N 0xf7 // Subsample column start (units of 4 pixels) +#define SUB_ROW_N 0xf8 // Subsample row start (units of 4 pixels) +#define SUB_COL_N1 0xf9 // Subsample column end (units of 4 pixels) +#define SUB_ROW_N1 0xfa // Subsample row end (units of 4 pixels) + +// Page 1 Registers (access after setting 0xfe = 0x01) + +// AEC Control (Page 1) +#define AEC_TARGET_Y 0x13 // AEC target Y value (brightness target for AE) + +// Subsample Control (Page 1) +#define SUBSAMPLE_EN 0x53 // Bit[7]: Subsample enable +#define SUBSAMPLE_MODE 0x54 // Subsample mode control +#define SUBSAMPLE_EN2 0x55 // Bit[0]: Subsample enable 2 +#define SUBSAMPLE_Y0 0x56 // Y subsample config 0 +#define SUBSAMPLE_Y1 0x57 // Y subsample config 1 +#define SUBSAMPLE_UV0 0x58 // UV subsample config 0 +#define SUBSAMPLE_UV1 0x59 // UV subsample config 1 /** - * @brief register value + * @brief Register bit masks */ +// AEC_MODE1 (0x22) bit masks +#define AEC_ENABLE 0x01 // Bit 0: AEC enable +#define AWB_ENABLE 0x02 // Bit 1: AWB enable +#define AGC_ENABLE 0x04 // Bit 2: AGC enable + +// CISCTL_MODE1 (0x14) bit masks +#define HMIRROR_MASK 0x01 // Bit 0: Horizontal mirror +#define VFLIP_MASK 0x02 // Bit 1: Vertical flip + +// SPECIAL_EFFECT (0x23) bit masks +#define EFFECT_NORMAL 0x00 // Normal mode +#define EFFECT_NEGATIVE 0x01 // Negative/inverse +#define EFFECT_GRAYSCALE 0x02 // B&W or color tint mode + +// OUT_CTRL (0x2e) bit masks +#define COLORBAR_ENABLE 0x01 // Bit 0: Color bar test pattern #endif // __GC0308_REG_REGS_H__ From 77933cb1a3353e1787bdf91e805188a2eeb3e0d8 Mon Sep 17 00:00:00 2001 From: Marc Hoersken Date: Mon, 3 Nov 2025 23:11:53 +0100 Subject: [PATCH 2/4] gc0308: read current setting values on sensor initialization --- sensors/gc0308.c | 139 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 124 insertions(+), 15 deletions(-) diff --git a/sensors/gc0308.c b/sensors/gc0308.c index ca8c664fcb..6f6d0171b3 100644 --- a/sensors/gc0308.c +++ b/sensors/gc0308.c @@ -617,31 +617,140 @@ static int set_reg(sensor_t *sensor, int reg, int mask, int value) static int init_status(sensor_t *sensor) { - write_reg(sensor->slv_addr, 0xfe, 0x00); - sensor->status.brightness = 0; - sensor->status.contrast = 50; - sensor->status.saturation = 0; - sensor->status.sharpness = 0; + int ret; + ret = write_reg(sensor->slv_addr, 0xfe, 0x00); + + // Read brightness from LSC_RED_B2 register (0xd6) + // Default is 0x48, map back to level: (reg_val - 0x48) / 0x10 + ret = read_reg(sensor->slv_addr, LSC_RED_B2); + if (ret >= 0) { + int brightness_level = (ret - 0x48) / 0x10; + sensor->status.brightness = (brightness_level < -2) ? -2 : (brightness_level > 2) ? 2 : brightness_level; + } else { + sensor->status.brightness = 0; + } + + // Read contrast from CONTRAST register (0xb3) + // Default is 0x40, map back to level: (reg_val - 0x40) / 0x10 + ret = read_reg(sensor->slv_addr, CONTRAST); + if (ret >= 0) { + int contrast_level = (ret - 0x40) / 0x10; + sensor->status.contrast = (contrast_level < -2) ? -2 : (contrast_level > 2) ? 2 : contrast_level; + } else { + sensor->status.contrast = 0; + } + + // Read saturation from SATURATION_CB1 register (0x54) + // Default is 0x40, map back to level: (reg_val - 0x40) / 0x10 + ret = read_reg(sensor->slv_addr, SATURATION_CB1); + if (ret >= 0) { + int saturation_level = (ret - 0x40) / 0x10; + sensor->status.saturation = (saturation_level < -2) ? -2 : (saturation_level > 2) ? 2 : saturation_level; + } else { + sensor->status.saturation = 0; + } + + // Read sharpness from EDGE_DEC_SA1 register (0x8b) + // Default is 0x20, map back to level: (reg_val - 0x20) / 0x08 + ret = read_reg(sensor->slv_addr, EDGE_DEC_SA1); + if (ret >= 0) { + int sharpness_level = (ret - 0x20) / 0x08; + sensor->status.sharpness = (sharpness_level < -2) ? -2 : (sharpness_level > 2) ? 2 : sharpness_level; + } else { + sensor->status.sharpness = 0; + } + + // Denoise not supported by GC0308 sensor->status.denoise = 0; - sensor->status.ae_level = 0; + + // Read AE level from AEC_TARGET_Y register (Page 1, 0xf7) + ret = write_reg(sensor->slv_addr, 0xfe, 0x01); + ret = read_reg(sensor->slv_addr, AEC_TARGET_Y); + ret = write_reg(sensor->slv_addr, 0xfe, 0x00); + if (ret >= 0) { + int ae_level = (ret - 0x19) / 0x08; + sensor->status.ae_level = (ae_level < -2) ? -2 : (ae_level > 2) ? 2 : ae_level; + } else { + sensor->status.ae_level = 0; + } + + // Gainceiling not supported by GC0308 sensor->status.gainceiling = 0; - sensor->status.awb = 0; + + // Read AWB, AGC, AEC status from AEC_MODE1 register (0xd2) + sensor->status.awb = check_reg_mask(sensor->slv_addr, AEC_MODE1, AWB_ENABLE); + sensor->status.agc = check_reg_mask(sensor->slv_addr, AEC_MODE1, AGC_ENABLE); + sensor->status.aec = check_reg_mask(sensor->slv_addr, AEC_MODE1, AEC_ENABLE); + + // DCW not supported by GC0308 sensor->status.dcw = 0; - sensor->status.agc = 0; - sensor->status.aec = 0; - sensor->status.hmirror = check_reg_mask(sensor->slv_addr, 0x14, 0x01); - sensor->status.vflip = check_reg_mask(sensor->slv_addr, 0x14, 0x02); - sensor->status.colorbar = 0; + + // Read hmirror and vflip from CISCTL_MODE1 register (0x14) + sensor->status.hmirror = check_reg_mask(sensor->slv_addr, CISCTL_MODE1, HMIRROR_MASK); + sensor->status.vflip = check_reg_mask(sensor->slv_addr, CISCTL_MODE1, VFLIP_MASK); + + // Read colorbar status from OUT_CTRL register (0x2e) + sensor->status.colorbar = check_reg_mask(sensor->slv_addr, OUT_CTRL, 0x01); + + // BPC, WPC not specifically tracked by GC0308 driver sensor->status.bpc = 0; sensor->status.wpc = 0; + + // Raw GMA, LENC not supported by GC0308 sensor->status.raw_gma = 0; sensor->status.lenc = 0; + + // Quality not supported by GC0308 sensor->status.quality = 0; - sensor->status.special_effect = 0; - sensor->status.wb_mode = 0; + + // Read special effect from SPECIAL_EFFECT register (0x23) + ret = read_reg(sensor->slv_addr, SPECIAL_EFFECT); + if (ret >= 0) { + // Map hardware register value to effect enum + // Bit[1:0] of SPECIAL_EFFECT register holds the effect mode + uint8_t effect_bits = ret & 0x03; + if (effect_bits == EFFECT_NORMAL) { + sensor->status.special_effect = 0; // Normal + } else if (effect_bits == EFFECT_NEGATIVE) { + sensor->status.special_effect = 1; // Negative + } else if (effect_bits == EFFECT_GRAYSCALE) { + // Could be Grayscale (2) or tint effects (3-6) - requires checking FIXED_CB/CR + // For simplicity, default to Grayscale when in EFFECT_GRAYSCALE mode + sensor->status.special_effect = 2; // Grayscale/B&W + } else { + sensor->status.special_effect = 0; // Default to Normal + } + } else { + sensor->status.special_effect = 0; + } + + // Read white balance mode from AWB registers + // This is simplified - full WB mode detection would need to check multiple registers + if (sensor->status.awb) { + sensor->status.wb_mode = 0; // Auto + } else { + // Check if specific preset is active (simplified detection) + sensor->status.wb_mode = 0; // Default to Auto + } + + // AWB gain not specifically tracked sensor->status.awb_gain = 0; - sensor->status.agc_gain = 0; + + // Read AGC gain from GLOBAL_GAIN register (0x50) + // Hardware range is 0-63 (6-bit), map to 0-30 for API compatibility + ret = read_reg(sensor->slv_addr, GLOBAL_GAIN); + if (ret >= 0) { + // Reverse map from hardware (0-63) to API (0-30) + sensor->status.agc_gain = (ret * 30) / 63; + if (sensor->status.agc_gain > 30) sensor->status.agc_gain = 30; + } else { + sensor->status.agc_gain = 0; + } + + // AEC value not specifically tracked by GC0308 sensor->status.aec_value = 0; + + // AEC2 not supported by GC0308 sensor->status.aec2 = 0; print_regs(sensor->slv_addr); From 83c6c27e0e371bfbc8fe4eabfe7ae81a6beeded6 Mon Sep 17 00:00:00 2001 From: Marc Hoersken Date: Mon, 3 Nov 2025 23:13:08 +0100 Subject: [PATCH 3/4] gc0308: fix incomplete error handling and use defined values --- sensors/gc0308.c | 89 ++++++++++++++++++++++++------------------------ 1 file changed, 44 insertions(+), 45 deletions(-) diff --git a/sensors/gc0308.c b/sensors/gc0308.c index 6f6d0171b3..dc4395f095 100644 --- a/sensors/gc0308.c +++ b/sensors/gc0308.c @@ -138,9 +138,9 @@ static int reset(sensor_t *sensor) if (ret == 0) { ESP_LOGD(TAG, "Camera defaults loaded"); vTaskDelay(80 / portTICK_PERIOD_MS); - write_reg(sensor->slv_addr, 0xfe, 0x00); + ret = write_reg(sensor->slv_addr, 0xfe, 0x00); #ifdef CONFIG_IDF_TARGET_ESP32 - set_reg_bits(sensor->slv_addr, 0x28, 4, 0x07, 1); //frequency division for esp32, ensure pclk <= 15MHz + ret |= set_reg_bits(sensor->slv_addr, PCLK_DIV, 4, 0x07, 1); //frequency division for esp32, ensure pclk <= 15MHz #endif } return ret; @@ -152,20 +152,19 @@ static int set_pixformat(sensor_t *sensor, pixformat_t pixformat) switch (pixformat) { case PIXFORMAT_RGB565: - write_reg(sensor->slv_addr, 0xfe, 0x00); - ret = set_reg_bits(sensor->slv_addr, 0x24, 0, 0x0f, 6); //RGB565 + ret = write_reg(sensor->slv_addr, 0xfe, 0x00); + ret |= set_reg_bits(sensor->slv_addr, OUTPUT_FMT, 0, 0x0f, 6); //RGB565 break; - case PIXFORMAT_YUV422: - write_reg(sensor->slv_addr, 0xfe, 0x00); - ret = set_reg_bits(sensor->slv_addr, 0x24, 0, 0x0f, 2); //yuv422 Y Cb Y Cr + ret = write_reg(sensor->slv_addr, 0xfe, 0x00); + ret |= set_reg_bits(sensor->slv_addr, OUTPUT_FMT, 0, 0x0f, 2); //yuv422 Y Cb Y Cr break; case PIXFORMAT_GRAYSCALE: - write_reg(sensor->slv_addr, 0xfe, 0x00); - ret = write_reg(sensor->slv_addr, 0x24, 0xb1); + ret = write_reg(sensor->slv_addr, 0xfe, 0x00); + ret |= write_reg(sensor->slv_addr, OUTPUT_FMT, 0xb1); break; default: - ESP_LOGW(TAG, "unsupport format"); + ESP_LOGW(TAG, "Unsupported format %u", pixformat); ret = -1; break; } @@ -230,45 +229,45 @@ static int set_framesize(sensor_t *sensor, framesize_t framesize) } } - write_reg(sensor->slv_addr, 0xfe, 0x00); - - write_reg(sensor->slv_addr, 0x05, H8(row_s)); - write_reg(sensor->slv_addr, 0x06, L8(row_s)); - write_reg(sensor->slv_addr, 0x07, H8(col_s)); - write_reg(sensor->slv_addr, 0x08, L8(col_s)); - write_reg(sensor->slv_addr, 0x09, H8(win_h + 8)); - write_reg(sensor->slv_addr, 0x0a, L8(win_h + 8)); - write_reg(sensor->slv_addr, 0x0b, H8(win_w + 8)); - write_reg(sensor->slv_addr, 0x0c, L8(win_w + 8)); + ret = write_reg(sensor->slv_addr, 0xfe, 0x00); - write_reg(sensor->slv_addr, 0xfe, 0x01); - set_reg_bits(sensor->slv_addr, 0x53, 7, 0x01, 1); - set_reg_bits(sensor->slv_addr, 0x55, 0, 0x01, 1); - write_reg(sensor->slv_addr, 0x54, cfg->reg0x54); - write_reg(sensor->slv_addr, 0x56, cfg->reg0x56); - write_reg(sensor->slv_addr, 0x57, cfg->reg0x57); - write_reg(sensor->slv_addr, 0x58, cfg->reg0x58); - write_reg(sensor->slv_addr, 0x59, cfg->reg0x59); + ret |= write_reg(sensor->slv_addr, ROW_START_H, H8(row_s)); + ret |= write_reg(sensor->slv_addr, ROW_START_L, L8(row_s)); + ret |= write_reg(sensor->slv_addr, COL_START_H, H8(col_s)); + ret |= write_reg(sensor->slv_addr, COL_START_L, L8(col_s)); + ret |= write_reg(sensor->slv_addr, WIN_HEIGHT_H, H8(win_h + 8)); + ret |= write_reg(sensor->slv_addr, WIN_HEIGHT_L, L8(win_h + 8)); + ret |= write_reg(sensor->slv_addr, WIN_WIDTH_H, H8(win_w + 8)); + ret |= write_reg(sensor->slv_addr, WIN_WIDTH_L, L8(win_w + 8)); + + ret |= write_reg(sensor->slv_addr, 0xfe, 0x01); + ret |= set_reg_bits(sensor->slv_addr, SUBSAMPLE_EN, 7, 0x01, 1); + ret |= set_reg_bits(sensor->slv_addr, SUBSAMPLE_EN2, 0, 0x01, 1); + ret |= write_reg(sensor->slv_addr, SUBSAMPLE_MODE, cfg->reg0x54); + ret |= write_reg(sensor->slv_addr, SUBSAMPLE_Y0, cfg->reg0x56); + ret |= write_reg(sensor->slv_addr, SUBSAMPLE_Y1, cfg->reg0x57); + ret |= write_reg(sensor->slv_addr, SUBSAMPLE_UV0, cfg->reg0x58); + ret |= write_reg(sensor->slv_addr, SUBSAMPLE_UV1, cfg->reg0x59); - write_reg(sensor->slv_addr, 0xfe, 0x00); + ret |= write_reg(sensor->slv_addr, 0xfe, 0x00); #elif CONFIG_GC_SENSOR_WINDOWING_MODE - write_reg(sensor->slv_addr, 0xfe, 0x00); - - write_reg(sensor->slv_addr, 0xf7, col_s / 4); - write_reg(sensor->slv_addr, 0xf8, row_s / 4); - write_reg(sensor->slv_addr, 0xf9, (col_s + w) / 4); - write_reg(sensor->slv_addr, 0xfa, (row_s + h) / 4); - - write_reg(sensor->slv_addr, 0x05, H8(row_s)); - write_reg(sensor->slv_addr, 0x06, L8(row_s)); - write_reg(sensor->slv_addr, 0x07, H8(col_s)); - write_reg(sensor->slv_addr, 0x08, L8(col_s)); - - write_reg(sensor->slv_addr, 0x09, H8(h + 8)); - write_reg(sensor->slv_addr, 0x0a, L8(h + 8)); - write_reg(sensor->slv_addr, 0x0b, H8(w + 8)); - write_reg(sensor->slv_addr, 0x0c, L8(w + 8)); + ret = write_reg(sensor->slv_addr, 0xfe, 0x00); + + ret |= write_reg(sensor->slv_addr, SUB_COL_N, col_s / 4); + ret |= write_reg(sensor->slv_addr, SUB_ROW_N, row_s / 4); + ret |= write_reg(sensor->slv_addr, SUB_COL_N1, (col_s + w) / 4); + ret |= write_reg(sensor->slv_addr, SUB_ROW_N1, (row_s + h) / 4); + + ret |= write_reg(sensor->slv_addr, ROW_START_H, H8(row_s)); + ret |= write_reg(sensor->slv_addr, ROW_START_L, L8(row_s)); + ret |= write_reg(sensor->slv_addr, COL_START_H, H8(col_s)); + ret |= write_reg(sensor->slv_addr, COL_START_L, L8(col_s)); + + ret |= write_reg(sensor->slv_addr, WIN_HEIGHT_H, H8(h + 8)); + ret |= write_reg(sensor->slv_addr, WIN_HEIGHT_L, L8(h + 8)); + ret |= write_reg(sensor->slv_addr, WIN_WIDTH_H, H8(w + 8)); + ret |= write_reg(sensor->slv_addr, WIN_WIDTH_L, L8(w + 8)); #endif if (ret == 0) { From 871b7aa7fcb3a734a6e4fb0282865912541c7f61 Mon Sep 17 00:00:00 2001 From: Marc Hoersken Date: Tue, 4 Nov 2025 23:12:34 +0100 Subject: [PATCH 4/4] gc0308: fix sensor setting registers, defaults and setters Brightness is not supported, but AEC value can be used. --- sensors/gc0308.c | 232 ++++++++++++++------------ sensors/private_include/gc0308_regs.h | 102 ++++++----- 2 files changed, 169 insertions(+), 165 deletions(-) diff --git a/sensors/gc0308.c b/sensors/gc0308.c index dc4395f095..fe1b626d8c 100644 --- a/sensors/gc0308.c +++ b/sensors/gc0308.c @@ -295,62 +295,60 @@ static int set_contrast(sensor_t *sensor, int level) return ret; } -static int set_brightness(sensor_t *sensor, int level) +static int set_saturation(sensor_t *sensor, int level) { int ret = 0; - // GC0308 brightness range: -2 to +2 (mapped to register values) + // GC0308 saturation range: -2 to +2 if (level < -2 || level > 2) { return -1; } ret = write_reg(sensor->slv_addr, 0xfe, 0x00); - // Adjust brightness via LSC target Y base (LSC_RED_B2) - // Default value is 0x48, adjust based on level - uint8_t brightness_val = 0x48 + (level * 0x10); - ret |= write_reg(sensor->slv_addr, LSC_RED_B2, brightness_val); + // Adjust saturation via Cb and Cr saturation gains (0xb1, 0xb2) + // Default values from settings: both 0x40 + // Saturation adjustment: increase/decrease both Cb and Cr gains + uint8_t sat_val = 0x40 + (level * 0x10); + ret |= write_reg(sensor->slv_addr, SATURATION_Cb, sat_val); + ret |= write_reg(sensor->slv_addr, SATURATION_Cr, sat_val); if (ret == 0) { - sensor->status.brightness = level; - ESP_LOGD(TAG, "Set brightness to: %d", level); + sensor->status.saturation = level; + ESP_LOGD(TAG, "Set saturation to: %d", level); } return ret; } -static int set_saturation(sensor_t *sensor, int level) +static int set_sharpness(sensor_t *sensor, int level) { int ret = 0; - // GC0308 saturation range: -2 to +2 + // GC0308 sharpness control via Edge Enhancement registers (0x77-0x79) + // Per Linux kernel driver: EDGE12_EFFECT, EDGE_POS_RATIO, EDGE1_MINMAX + // Range: -2 to +2 if (level < -2 || level > 2) { return -1; } ret = write_reg(sensor->slv_addr, 0xfe, 0x00); - // Adjust saturation via Cb and Cr gain (SATURATION_CB1, SATURATION_CR1) - // Default values: SATURATION_CB=0xcb, SATURATION_CB1=0x10, SATURATION_CR1=0x90 - uint8_t sat_base = 0x40 + (level * 0x10); - ret |= write_reg(sensor->slv_addr, SATURATION_CB1, sat_base); - ret |= write_reg(sensor->slv_addr, SATURATION_CR1, sat_base + 0x50); + // Adjust edge enhancement effect + // Default values from Linux kernel driver: 0x38, 0x88 (Page 0) + // Increase for more sharpness, decrease for softer image + uint8_t edge_effect = 0x38 + (level * 0x08); + uint8_t edge_ratio = 0x88 + (level * 0x08); + ret |= write_reg(sensor->slv_addr, EDGE12_EFFECT, edge_effect); + ret |= write_reg(sensor->slv_addr, EDGE_POS_RATIO, edge_ratio); + // EDGE1_MINMAX typically kept at default unless fine-tuning needed if (ret == 0) { - sensor->status.saturation = level; - ESP_LOGD(TAG, "Set saturation to: %d", level); + sensor->status.sharpness = level; + ESP_LOGD(TAG, "Set sharpness to: %d (edge enhancement)", level); } return ret; } -static int set_sharpness(sensor_t *sensor, int level) +static int set_colorbar(sensor_t *sensor, int enable) { int ret = 0; - // GC0308 sharpness range: -2 to +2 - if (level < -2 || level > 2) { - return -1; - } ret = write_reg(sensor->slv_addr, 0xfe, 0x00); - // Adjust sharpness via edge enhancement (EDGE_DEC_SA1-SA3) - // Lower values = less sharp, higher values = more sharp - uint8_t sharp_val = 0x20 + (level * 0x08); - ret |= write_reg(sensor->slv_addr, EDGE_DEC_SA1, sharp_val); - ret |= write_reg(sensor->slv_addr, EDGE_DEC_SA2, sharp_val); - ret |= write_reg(sensor->slv_addr, EDGE_DEC_SA3, sharp_val); + ret |= set_reg_bits(sensor->slv_addr, OUT_CTRL, 0, 0x01, enable); if (ret == 0) { - sensor->status.sharpness = level; - ESP_LOGD(TAG, "Set sharpness to: %d", level); + sensor->status.colorbar = enable; + ESP_LOGD(TAG, "Set colorbar to: %d", enable); } return ret; } @@ -359,8 +357,8 @@ static int set_whitebal(sensor_t *sensor, int enable) { int ret = 0; ret = write_reg(sensor->slv_addr, 0xfe, 0x00); - // AWB enable/disable via AEC_MODE1 register, AWB_ENABLE bit - ret |= set_reg_bits(sensor->slv_addr, AEC_MODE1, 1, 0x01, enable != 0); + // AWB enable/disable via AAAA_EN register, AWB_ENABLE bit + ret |= set_reg_bits(sensor->slv_addr, AAAA_EN, 1, 0x01, enable != 0); if (ret == 0) { sensor->status.awb = enable; ESP_LOGD(TAG, "Set AWB to: %d", enable); @@ -372,8 +370,8 @@ static int set_gain_ctrl(sensor_t *sensor, int enable) { int ret = 0; ret = write_reg(sensor->slv_addr, 0xfe, 0x00); - // AGC enable/disable via AEC_MODE1 register, AGC_ENABLE bit - ret |= set_reg_bits(sensor->slv_addr, AEC_MODE1, 2, 0x01, enable != 0); + // AGC enable/disable via AAAA_EN register, AGC_ENABLE bit + ret |= set_reg_bits(sensor->slv_addr, AAAA_EN, 2, 0x01, enable != 0); if (ret == 0) { sensor->status.agc = enable; ESP_LOGD(TAG, "Set AGC to: %d", enable); @@ -385,8 +383,8 @@ static int set_exposure_ctrl(sensor_t *sensor, int enable) { int ret = 0; ret = write_reg(sensor->slv_addr, 0xfe, 0x00); - // AEC enable/disable via AEC_MODE1 register, AEC_ENABLE bit - ret |= set_reg_bits(sensor->slv_addr, AEC_MODE1, 0, 0x01, enable != 0); + // AEC enable/disable via AAAA_EN register, AEC_ENABLE bit + ret |= set_reg_bits(sensor->slv_addr, AAAA_EN, 0, 0x01, enable != 0); if (ret == 0) { sensor->status.aec = enable; ESP_LOGD(TAG, "Set AEC to: %d", enable); @@ -441,14 +439,27 @@ static int set_agc_gain(sensor_t *sensor, int gain) return ret; } -static int set_colorbar(sensor_t *sensor, int enable) + +static int set_aec_value(sensor_t *sensor, int value) { int ret = 0; + // GC0308 exposure value range: 0-1200 (compatible with API) + // Hardware range: 0-4095 (12-bit), but practical max based on frame timing + // Default is approximately 0x01e0 (480) + if (value < 0) { + value = 0; + } else if (value > 1200) { + value = 1200; + } + // Exposure is 12-bit: high byte (0x03) holds bits[11:4], low byte (0x04) bits[3:0] in upper nibble + uint8_t exp_high = (value >> 4) & 0xFF; + uint8_t exp_low = (value << 4) & 0xF0; ret = write_reg(sensor->slv_addr, 0xfe, 0x00); - ret |= set_reg_bits(sensor->slv_addr, OUT_CTRL, 0, 0x01, enable); + ret |= write_reg(sensor->slv_addr, EXPOSURE_HIGH, exp_high); + ret |= write_reg(sensor->slv_addr, EXPOSURE_LOW, exp_low); if (ret == 0) { - sensor->status.colorbar = enable; - ESP_LOGD(TAG, "Set colorbar to: %d", enable); + sensor->status.aec_value = value; + ESP_LOGD(TAG, "Set AEC value to: %d (0x%03x)", value, value); } return ret; } @@ -464,42 +475,22 @@ static int set_special_effect(sensor_t *sensor, int effect) switch (effect) { case 0: // Normal ret |= set_reg_bits(sensor->slv_addr, SPECIAL_EFFECT, 0, 0x03, EFFECT_NORMAL); - ret |= write_reg(sensor->slv_addr, EFFECT_MODE, 0x0a); - ret |= write_reg(sensor->slv_addr, FIXED_CB, 0x7f); - ret |= write_reg(sensor->slv_addr, FIXED_CR, 0xfa); break; case 1: // Negative ret |= set_reg_bits(sensor->slv_addr, SPECIAL_EFFECT, 0, 0x03, EFFECT_NEGATIVE); break; case 2: // Grayscale (B&W) ret |= set_reg_bits(sensor->slv_addr, SPECIAL_EFFECT, 0, 0x03, EFFECT_GRAYSCALE); - ret |= write_reg(sensor->slv_addr, EFFECT_MODE, 0x0a); - ret |= write_reg(sensor->slv_addr, FIXED_CB, 0x7f); - ret |= write_reg(sensor->slv_addr, FIXED_CR, 0xfa); break; case 3: // Red Tint - ret |= set_reg_bits(sensor->slv_addr, SPECIAL_EFFECT, 0, 0x03, EFFECT_GRAYSCALE); - ret |= write_reg(sensor->slv_addr, EFFECT_MODE, 0x0a); - ret |= write_reg(sensor->slv_addr, FIXED_CB, 0x80); - ret |= write_reg(sensor->slv_addr, FIXED_CR, 0xc0); - break; case 4: // Green Tint - ret |= set_reg_bits(sensor->slv_addr, SPECIAL_EFFECT, 0, 0x03, EFFECT_GRAYSCALE); - ret |= write_reg(sensor->slv_addr, EFFECT_MODE, 0x0a); - ret |= write_reg(sensor->slv_addr, FIXED_CB, 0x50); - ret |= write_reg(sensor->slv_addr, FIXED_CR, 0x50); - break; case 5: // Blue Tint - ret |= set_reg_bits(sensor->slv_addr, SPECIAL_EFFECT, 0, 0x03, EFFECT_GRAYSCALE); - ret |= write_reg(sensor->slv_addr, EFFECT_MODE, 0x0a); - ret |= write_reg(sensor->slv_addr, FIXED_CB, 0xa0); - ret |= write_reg(sensor->slv_addr, FIXED_CR, 0x80); - break; case 6: // Sepia + // Color tint effects: these require grayscale mode + fixed Cb/Cr values + // However, the exact register behavior is undocumented + // Setting to grayscale mode as a safe fallback ret |= set_reg_bits(sensor->slv_addr, SPECIAL_EFFECT, 0, 0x03, EFFECT_GRAYSCALE); - ret |= write_reg(sensor->slv_addr, EFFECT_MODE, 0x0a); - ret |= write_reg(sensor->slv_addr, FIXED_CB, 0xd0); - ret |= write_reg(sensor->slv_addr, FIXED_CR, 0x28); + ESP_LOGW(TAG, "Color tint effects (3-6) may not be fully supported - using grayscale"); break; default: ret = -1; @@ -515,19 +506,21 @@ static int set_special_effect(sensor_t *sensor, int effect) static int set_wb_mode(sensor_t *sensor, int mode) { int ret = 0; - // WB modes: 0=Auto, 1=Sunny, 2=Cloudy, 3=Office, 4=Home if (mode < 0 || mode > 4) { return -1; } ret = write_reg(sensor->slv_addr, 0xfe, 0x00); if (mode == 0) { - // Auto WB - enable AWB via AEC_MODE1 - ret |= set_reg_bits(sensor->slv_addr, AEC_MODE1, 1, 0x01, 1); + // Auto WB - enable AWB and set auto gains + ret |= set_reg_bits(sensor->slv_addr, AAAA_EN, 1, 0x01, 1); + ret |= write_reg(sensor->slv_addr, AWB_R_GAIN, 0x56); + ret |= write_reg(sensor->slv_addr, AWB_G_GAIN, 0x40); + ret |= write_reg(sensor->slv_addr, AWB_B_GAIN, 0x4a); } else { // Manual WB - disable AWB and set specific gains - ret |= set_reg_bits(sensor->slv_addr, AEC_MODE1, 1, 0x01, 0); + ret |= set_reg_bits(sensor->slv_addr, AAAA_EN, 1, 0x01, 0); switch (mode) { - case 1: // Sunny + case 1: // Sunny (Daylight) ret |= write_reg(sensor->slv_addr, AWB_R_GAIN, 0x74); ret |= write_reg(sensor->slv_addr, AWB_G_GAIN, 0x52); ret |= write_reg(sensor->slv_addr, AWB_B_GAIN, 0x40); @@ -537,15 +530,15 @@ static int set_wb_mode(sensor_t *sensor, int mode) ret |= write_reg(sensor->slv_addr, AWB_G_GAIN, 0x50); ret |= write_reg(sensor->slv_addr, AWB_B_GAIN, 0x40); break; - case 3: // Office + case 3: // Office (Incandescent) ret |= write_reg(sensor->slv_addr, AWB_R_GAIN, 0x48); ret |= write_reg(sensor->slv_addr, AWB_G_GAIN, 0x40); ret |= write_reg(sensor->slv_addr, AWB_B_GAIN, 0x5c); break; - case 4: // Home + case 4: // Home (Fluorescent) ret |= write_reg(sensor->slv_addr, AWB_R_GAIN, 0x40); - ret |= write_reg(sensor->slv_addr, AWB_G_GAIN, 0x54); - ret |= write_reg(sensor->slv_addr, AWB_B_GAIN, 0x70); + ret |= write_reg(sensor->slv_addr, AWB_G_GAIN, 0x42); + ret |= write_reg(sensor->slv_addr, AWB_B_GAIN, 0x50); break; default: ret = -1; @@ -566,12 +559,12 @@ static int set_ae_level(sensor_t *sensor, int level) if (level < -2 || level > 2) { return -1; } - ret = write_reg(sensor->slv_addr, 0xfe, 0x01); - // Adjust AE target via AEC_TARGET_Y register (Page 1) - // Default is around 0x19, adjust based on level - uint8_t ae_target = 0x19 + (level * 0x08); + ret = write_reg(sensor->slv_addr, 0xfe, 0x00); + // Adjust AE target via AEC_TARGET_Y register (Page 0, 0xd3) + // This is the expected luminance value for AEC + // Default is 0x48, adjust based on level + uint8_t ae_target = 0x48 + (level * 0x08); ret |= write_reg(sensor->slv_addr, AEC_TARGET_Y, ae_target); - ret |= write_reg(sensor->slv_addr, 0xfe, 0x00); if (ret == 0) { sensor->status.ae_level = level; ESP_LOGD(TAG, "Set AE level to: %d", level); @@ -619,16 +612,6 @@ static int init_status(sensor_t *sensor) int ret; ret = write_reg(sensor->slv_addr, 0xfe, 0x00); - // Read brightness from LSC_RED_B2 register (0xd6) - // Default is 0x48, map back to level: (reg_val - 0x48) / 0x10 - ret = read_reg(sensor->slv_addr, LSC_RED_B2); - if (ret >= 0) { - int brightness_level = (ret - 0x48) / 0x10; - sensor->status.brightness = (brightness_level < -2) ? -2 : (brightness_level > 2) ? 2 : brightness_level; - } else { - sensor->status.brightness = 0; - } - // Read contrast from CONTRAST register (0xb3) // Default is 0x40, map back to level: (reg_val - 0x40) / 0x10 ret = read_reg(sensor->slv_addr, CONTRAST); @@ -639,9 +622,9 @@ static int init_status(sensor_t *sensor) sensor->status.contrast = 0; } - // Read saturation from SATURATION_CB1 register (0x54) + // Read saturation from SATURATION_Cb register (0xb1) // Default is 0x40, map back to level: (reg_val - 0x40) / 0x10 - ret = read_reg(sensor->slv_addr, SATURATION_CB1); + ret = read_reg(sensor->slv_addr, SATURATION_Cb); if (ret >= 0) { int saturation_level = (ret - 0x40) / 0x10; sensor->status.saturation = (saturation_level < -2) ? -2 : (saturation_level > 2) ? 2 : saturation_level; @@ -649,11 +632,12 @@ static int init_status(sensor_t *sensor) sensor->status.saturation = 0; } - // Read sharpness from EDGE_DEC_SA1 register (0x8b) - // Default is 0x20, map back to level: (reg_val - 0x20) / 0x08 - ret = read_reg(sensor->slv_addr, EDGE_DEC_SA1); + // Read sharpness from EDGE12_EFFECT register (0x77) + // GC0308 uses Edge Enhancement registers for sharpness control + // Linux kernel driver uses 0x38 as default + ret = read_reg(sensor->slv_addr, EDGE12_EFFECT); if (ret >= 0) { - int sharpness_level = (ret - 0x20) / 0x08; + int sharpness_level = (ret - 0x38) / 0x08; sensor->status.sharpness = (sharpness_level < -2) ? -2 : (sharpness_level > 2) ? 2 : sharpness_level; } else { sensor->status.sharpness = 0; @@ -662,12 +646,11 @@ static int init_status(sensor_t *sensor) // Denoise not supported by GC0308 sensor->status.denoise = 0; - // Read AE level from AEC_TARGET_Y register (Page 1, 0xf7) - ret = write_reg(sensor->slv_addr, 0xfe, 0x01); + // Read AE level from AEC_TARGET_Y register (P0:0xd3) + // Same register as brightness, but different step size for level calculation ret = read_reg(sensor->slv_addr, AEC_TARGET_Y); - ret = write_reg(sensor->slv_addr, 0xfe, 0x00); if (ret >= 0) { - int ae_level = (ret - 0x19) / 0x08; + int ae_level = (ret - 0x48) / 0x08; sensor->status.ae_level = (ae_level < -2) ? -2 : (ae_level > 2) ? 2 : ae_level; } else { sensor->status.ae_level = 0; @@ -676,10 +659,10 @@ static int init_status(sensor_t *sensor) // Gainceiling not supported by GC0308 sensor->status.gainceiling = 0; - // Read AWB, AGC, AEC status from AEC_MODE1 register (0xd2) - sensor->status.awb = check_reg_mask(sensor->slv_addr, AEC_MODE1, AWB_ENABLE); - sensor->status.agc = check_reg_mask(sensor->slv_addr, AEC_MODE1, AGC_ENABLE); - sensor->status.aec = check_reg_mask(sensor->slv_addr, AEC_MODE1, AEC_ENABLE); + // Read AWB, AGC, AEC status from AAAA_EN register (0x22) + sensor->status.awb = check_reg_mask(sensor->slv_addr, AAAA_EN, AWB_ENABLE); + sensor->status.agc = check_reg_mask(sensor->slv_addr, AAAA_EN, AGC_ENABLE); + sensor->status.aec = check_reg_mask(sensor->slv_addr, AAAA_EN, AEC_ENABLE); // DCW not supported by GC0308 sensor->status.dcw = 0; @@ -723,13 +706,32 @@ static int init_status(sensor_t *sensor) sensor->status.special_effect = 0; } - // Read white balance mode from AWB registers - // This is simplified - full WB mode detection would need to check multiple registers + // Read white balance mode from AWB registers (0x5a-0x5c) if (sensor->status.awb) { - sensor->status.wb_mode = 0; // Auto + sensor->status.wb_mode = 0; // Auto WB } else { - // Check if specific preset is active (simplified detection) - sensor->status.wb_mode = 0; // Default to Auto + // Manual WB - attempt to detect which preset is active by reading gain values + // Per Linux kernel driver: AWB registers are at 0x5a-0x5c + int r_gain = read_reg(sensor->slv_addr, AWB_R_GAIN); + int g_gain = read_reg(sensor->slv_addr, AWB_G_GAIN); + int b_gain = read_reg(sensor->slv_addr, AWB_B_GAIN); + if (r_gain >= 0 && g_gain >= 0 && b_gain >= 0) { + // Match against known presets from Linux kernel driver + // with tolerance for register variation (+/-4 values) + if (r_gain >= 0x70 && r_gain <= 0x78 && g_gain >= 0x4e && g_gain <= 0x56 && b_gain >= 0x3c && b_gain <= 0x44) { + sensor->status.wb_mode = 1; // Sunny/Daylight (0x74, 0x52, 0x40) + } else if (r_gain >= 0x88 && r_gain <= 0x90 && g_gain >= 0x4c && g_gain <= 0x54 && b_gain >= 0x3c && b_gain <= 0x44) { + sensor->status.wb_mode = 2; // Cloudy (0x8c, 0x50, 0x40) + } else if (r_gain >= 0x44 && r_gain <= 0x4c && g_gain >= 0x3c && g_gain <= 0x44 && b_gain >= 0x58 && b_gain <= 0x60) { + sensor->status.wb_mode = 3; // Office/Incandescent (0x48, 0x40, 0x5c) + } else if (r_gain >= 0x3c && r_gain <= 0x44 && g_gain >= 0x3e && g_gain <= 0x46 && b_gain >= 0x4c && b_gain <= 0x54) { + sensor->status.wb_mode = 4; // Home/Fluorescent (0x40, 0x42, 0x50) + } else { + sensor->status.wb_mode = 0; // Unknown/custom - default to Auto + } + } else { + sensor->status.wb_mode = 0; // Read error - default to Auto + } } // AWB gain not specifically tracked @@ -746,8 +748,16 @@ static int init_status(sensor_t *sensor) sensor->status.agc_gain = 0; } - // AEC value not specifically tracked by GC0308 - sensor->status.aec_value = 0; + // Read AEC value from EXPOSURE registers (0x03, 0x04) + // 12-bit value: high byte (0x03) bits[11:4], low byte (0x04) bits[3:0] in upper nibble + int exp_high = read_reg(sensor->slv_addr, EXPOSURE_HIGH); + int exp_low = read_reg(sensor->slv_addr, EXPOSURE_LOW); + if (exp_high >= 0 && exp_low >= 0) { + sensor->status.aec_value = (exp_high << 4) | ((exp_low >> 4) & 0x0F); + if (sensor->status.aec_value > 1200) sensor->status.aec_value = 1200; + } else { + sensor->status.aec_value = 0; + } // AEC2 not supported by GC0308 sensor->status.aec2 = 0; @@ -789,7 +799,7 @@ int esp32_camera_gc0308_init(sensor_t *sensor) sensor->set_pixformat = set_pixformat; sensor->set_framesize = set_framesize; sensor->set_contrast = set_contrast; - sensor->set_brightness = set_brightness; + sensor->set_brightness = set_dummy; sensor->set_saturation = set_saturation; sensor->set_sharpness = set_sharpness; sensor->set_denoise = set_dummy; @@ -805,7 +815,7 @@ int esp32_camera_gc0308_init(sensor_t *sensor) sensor->set_aec2 = set_dummy; sensor->set_awb_gain = set_dummy; sensor->set_agc_gain = set_agc_gain; - sensor->set_aec_value = set_dummy; + sensor->set_aec_value = set_aec_value; sensor->set_special_effect = set_special_effect; sensor->set_wb_mode = set_wb_mode; diff --git a/sensors/private_include/gc0308_regs.h b/sensors/private_include/gc0308_regs.h index 69d899ce81..b6a1dcc700 100644 --- a/sensors/private_include/gc0308_regs.h +++ b/sensors/private_include/gc0308_regs.h @@ -16,6 +16,12 @@ // Page 0 Registers +// Exposure Control (Manual, when AEC disabled) +#define EXPOSURE_HIGH 0x03 // Exposure time high byte (bits 11:4) +#define EXPOSURE_LOW 0x04 // Exposure time low byte (bits 3:0 in upper nibble) + // Total exposure = (EXPOSURE_HIGH << 4) | (EXPOSURE_LOW >> 4) + // Range: 0-4095 (12-bit value) + // Windowing Registers #define ROW_START_H 0x05 // Row start address high byte #define ROW_START_L 0x06 // Row start address low byte @@ -26,26 +32,16 @@ #define WIN_WIDTH_H 0x0b // Window width high byte #define WIN_WIDTH_L 0x0c // Window width low byte -// Analog Circuit Control -#define ANALOG_MODE1 0x17 // Analog mode control 1 -#define ANALOG_MODE2 0x1a // Analog mode control 2 -#define PCLK_DIV 0x28 // PCLK divider control (frequency division) - // Sensor Control #define CISCTL_MODE1 0x14 // Bit[1]: V_flip, Bit[0]: H_mirror -// Output Format -#define OUTPUT_FMT 0x24 // Output format control - // Bit[3:0]: Output format selection - // 0010: YCbYCr (YUV422) - // 0110: RGB565 - // others: see datasheet - -// AWB/AEC/AGC Control -#define AEC_MODE1 0x22 // Auto control modes +// AAAA_EN - Auto Algorithm Enable (AEC/AGC/AWB/ABB) +#define AAAA_EN 0x22 // Auto control enable register + // Per Linux kernel driver: // Bit[0]: AEC enable (1=on, 0=off) // Bit[1]: AWB enable (1=on, 0=off) // Bit[2]: AGC enable (1=on, 0=off) + // Note: This was previously incorrectly named AEC_MODE1 // Special Effects and Output Control #define SPECIAL_EFFECT 0x23 // Special effect control @@ -53,51 +49,52 @@ // 00: Normal // 01: Negative // 10: Grayscale/Color tint mode + +// Output Format +#define OUTPUT_FMT 0x24 // Output format control + // Bit[3:0]: Output format selection + // 0010: YCbYCr (YUV422) + // 0110: RGB565 + // others: see datasheet + +// Analog Circuit Control +#define PCLK_DIV 0x28 // PCLK divider control (frequency division) + +// Output Control #define OUT_CTRL 0x2e // Output control // Bit[0]: Color bar enable -// Color Effect Registers (for tint effects when in grayscale mode) -#define FIXED_CB 0x20 // Fixed Cb value for special effects -#define FIXED_CR 0x21 // Fixed Cr value for special effects -#define EFFECT_MODE 0x2d // Effect mode additional control - // Global Gain #define GLOBAL_GAIN 0x50 // Global gain control (hardware: 0-63, 6-bit) // API uses 0-30 range, mapped to hardware values +// Manual White Balance Gain (when AWB disabled) +// Per Linux kernel driver: registers 0x05a-0x05c control AWB RGB gains +#define AWB_R_GAIN 0x5a // Red channel gain for manual WB +#define AWB_G_GAIN 0x5b // Green channel gain for manual WB +#define AWB_B_GAIN 0x5c // Blue channel gain for manual WB + +// Edge Enhancement (INTPEE - Interpolation and Edge-Enhancement) +// Per Linux kernel driver: registers 0x77-0x79 control edge enhancement +#define EDGE12_EFFECT 0x77 // Edge enhancement effect control +#define EDGE_POS_RATIO 0x78 // Edge position ratio +#define EDGE1_MINMAX 0x79 // Edge1 min/max control + +// Saturation Control (Page 0) +// According to datasheet, saturation is controlled via 0xb1 and 0xb2 +#define SATURATION_Cb 0xb1 // Cb saturation gain (default: 0x40) +#define SATURATION_Cr 0xb2 // Cr saturation gain (default: 0x40) + // Contrast -#define CONTRAST 0xb3 // Contrast control (0-255) +#define CONTRAST 0xb3 // Contrast control (default: 0x40) -// Manual White Balance Gain (when AWB disabled) -#define AWB_R_GAIN 0x77 // Red channel gain for manual WB -#define AWB_G_GAIN 0x78 // Green channel gain for manual WB -#define AWB_B_GAIN 0x79 // Blue channel gain for manual WB - -// Edge Enhancement (Sharpness) -#define EDGE_DEC_SA1 0x8b // Edge decimation smooth area 1 -#define EDGE_DEC_SA2 0x8c // Edge decimation smooth area 2 -#define EDGE_DEC_SA3 0x8d // Edge decimation smooth area 3 -#define EDGE_DEC_SA4 0x8e // Edge decimation smooth area 4 -#define EDGE_DEC_SA5 0x8f // Edge decimation smooth area 5 -#define EDGE_DEC_SA6 0x90 // Edge decimation smooth area 6 -#define EDGE_DEC_SA7 0x91 // Edge decimation smooth area 7 -#define EDGE_DEC_SA8 0x92 // Edge decimation smooth area 8 - -// Color Matrix -#define CMX1 0x93 // Color matrix coefficient 1 -#define CMX2 0x94 // Color matrix coefficient 2 -#define CMX3 0x95 // Color matrix coefficient 3 -#define CMX4 0x96 // Color matrix coefficient 4 -#define CMX5 0x97 // Color matrix coefficient 5 -#define CMX6 0x98 // Color matrix coefficient 6 - -// Saturation Control -#define SATURATION_CB 0xd0 // Cb saturation control -#define SATURATION_CB1 0xd1 // Cb saturation control 1 -#define SATURATION_CR1 0xd2 // Cr saturation control 1 - -// Lens Shading Correction (Brightness) -#define LSC_RED_B2 0xd3 // LSC target Y base (affects brightness) +// AEC Registers - Automatic Exposure Control (Page 0) +// Per Linux kernel driver: AEC control registers are at 0xd0-0xd3 +#define AEC_MODE1 0xd0 // AEC mode control register 1 +#define AEC_MODE2 0xd1 // AEC mode control register 2 +#define AEC_MODE3 0xd2 // AEC mode control register 3 +#define AEC_TARGET_Y 0xd3 // AEC target Y value / Expected luminance (default: 0x48) + // This controls both brightness and AE level // Subsample Window (Page 0 for coordinate reference) #define SUB_COL_N 0xf7 // Subsample column start (units of 4 pixels) @@ -107,9 +104,6 @@ // Page 1 Registers (access after setting 0xfe = 0x01) -// AEC Control (Page 1) -#define AEC_TARGET_Y 0x13 // AEC target Y value (brightness target for AE) - // Subsample Control (Page 1) #define SUBSAMPLE_EN 0x53 // Bit[7]: Subsample enable #define SUBSAMPLE_MODE 0x54 // Subsample mode control @@ -123,7 +117,7 @@ * @brief Register bit masks */ -// AEC_MODE1 (0x22) bit masks +// AAAA_EN (0x22) bit masks - Auto Algorithm Enable #define AEC_ENABLE 0x01 // Bit 0: AEC enable #define AWB_ENABLE 0x02 // Bit 1: AWB enable #define AGC_ENABLE 0x04 // Bit 2: AGC enable