diff --git a/components/esp_driver_isp/CMakeLists.txt b/components/esp_driver_isp/CMakeLists.txt index 2a6b037115..7ce185791b 100644 --- a/components/esp_driver_isp/CMakeLists.txt +++ b/components/esp_driver_isp/CMakeLists.txt @@ -10,7 +10,8 @@ set(requires) if(CONFIG_SOC_ISP_SUPPORTED) list(APPEND srcs "src/isp_core.c" - "src/isp_af.c") + "src/isp_af.c" + "src/isp_awb.c") endif() if(CONFIG_SOC_ISP_BF_SUPPORTED) diff --git a/components/esp_driver_isp/include/driver/isp.h b/components/esp_driver_isp/include/driver/isp.h index 50674cb7b2..38088eb938 100644 --- a/components/esp_driver_isp/include/driver/isp.h +++ b/components/esp_driver_isp/include/driver/isp.h @@ -13,4 +13,5 @@ #include "driver/isp_core.h" #include "driver/isp_af.h" +#include "driver/isp_awb.h" #include "driver/isp_bf.h" diff --git a/components/esp_driver_isp/include/driver/isp_af.h b/components/esp_driver_isp/include/driver/isp_af.h index d3d9522d79..67bfa58b52 100644 --- a/components/esp_driver_isp/include/driver/isp_af.h +++ b/components/esp_driver_isp/include/driver/isp_af.h @@ -178,7 +178,7 @@ typedef struct { /** * @brief Prototype of ISP AF Env detector event callback * - * @param[in] handle ISP AF controller handle + * @param[in] af_ctrlr ISP AF controller handle * @param[in] edata ISP AF Env detector event data * @param[in] user_data User registered context, registered when in `esp_isp_af_env_detector_register_event_callbacks()` * diff --git a/components/esp_driver_isp/include/driver/isp_awb.h b/components/esp_driver_isp/include/driver/isp_awb.h new file mode 100644 index 0000000000..64dab9e787 --- /dev/null +++ b/components/esp_driver_isp/include/driver/isp_awb.h @@ -0,0 +1,207 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "esp_err.h" +#include "driver/isp_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief AWB controller config + */ +typedef struct { + isp_awb_sample_point_t sample_point; /*!< AWB sample point of the ISP pipeline. + * ISP_AWB_SAMPLE_POINT_BEFORE_CCM: sample before Color Correction Matrix(CCM). + * ISP_AWB_SAMPLE_POINT_AFTER_CCM: sample after Color Correction Matrix(CCM). + * If your camera support to set the manual gain to the RGB channels, + * then you can choose to sample before CCM, and set the gain to the camera registers. + * If your camera doesn't support the manual gain or don't want to change the camera configuration, + * then you can choose to sample after CCM, and set the calculated gain to the CCM + */ + isp_window_t window; /*!< Statistic window of AWB. + * Suggest to set it at the middle of the image and a little smaller than the whole image. + * It will be more reliable because the edges of image are easily to be overexposure, + * the overexposure pixels are almost at maximum luminance, + * which are not good references to calculate the gain for white balance. + */ + struct { + isp_u32_range_t luminance; /*!< Luminance range of the white patch. Range [0, 255 * 3] + * Not suggest to set the max value to 255 * 3, + * because these pixels are too bright, very possible to be overexposure. + * So the pixels that too bright should not be the reference of the white balance. + * And the minimum value better to be 0 to allow the white balance work under low luminance environment. + */ + isp_float_range_t red_green_ratio; /*!< Red to green ratio of the white patch. Range [0, 4.0). + * The ratio could be as wider as possible, + * so that all the distorted pixels will be counted for the reference of white balance. + */ + isp_float_range_t blue_green_ratio; /*!< Blue to green ratio of the white patch. Range [0, 4.0) + * The ratio could be as wider as possible, + * so that all the distorted pixels will be counted for the reference of white balance. + */ + } white_patch; /*!< white patch configuration */ + int intr_priority; /*!< The interrupt priority, range 0~7, if set to 0, the driver will try to allocate an interrupt with + * a relative low priority (1,2,3) otherwise the larger the higher, 7 is NMI. + */ +} esp_isp_awb_config_t; + +/** + * @brief New an ISP AWB controller + * + * @param[in] isp_proc ISP Processor handle + * @param[in] awb_cfg Pointer to AWB config. Refer to ``esp_isp_awb_config_t``. + * @param[out] ret_hdl AWB controller handle + * + * @return + * - ESP_OK On success + * - ESP_ERR_INVALID_ARG If the combination of arguments is invalid + * - ESP_ERR_INVALID_STATE Invalid state + * - ESP_ERR_NOT_FOUND No free interrupt found with the specified flags + * - ESP_ERR_NO_MEM If out of memory + */ +esp_err_t esp_isp_new_awb_controller(isp_proc_handle_t isp_proc, const esp_isp_awb_config_t *awb_cfg, isp_awb_ctlr_t *ret_hdl); + +/** + * @brief Delete an ISP AWB controller + * + * @param[in] awb_ctlr AWB controller handle + * + * @return + * - ESP_OK On success + * - ESP_ERR_INVALID_ARG If the combination of arguments is invalid. + * - ESP_ERR_INVALID_STATE Driver state is invalid. + */ +esp_err_t esp_isp_del_awb_controller(isp_awb_ctlr_t awb_ctlr); + +/** + * @brief Enable an ISP AWB controller + * + * @param[in] awb_ctlr AWB controller handle + * + * @return + * - ESP_OK On success + * - ESP_ERR_INVALID_ARG If the combination of arguments is invalid. + * - ESP_ERR_INVALID_STATE Driver state is invalid. + */ +esp_err_t esp_isp_awb_controller_enable(isp_awb_ctlr_t awb_ctlr); + +/** + * @brief Disable an ISP AWB controller + * + * @param[in] awb_ctlr AWB controller handle + * + * @return + * - ESP_OK On success + * - ESP_ERR_INVALID_ARG If the combination of arguments is invalid. + * - ESP_ERR_INVALID_STATE Driver state is invalid. + */ +esp_err_t esp_isp_awb_controller_disable(isp_awb_ctlr_t awb_ctlr); + +/** + * @brief Trigger AWB white patch statistics for one time and get the result + * @note This function is a synchronous and block function, + * it only returns when AWB white patch statistics is done or timeout. + * It's a simple method to get the result directly for one time. + * + * @param[in] awb_ctlr AWB controller handle + * @param[in] timeout_ms Timeout in millisecond + * - timeout_ms < 0: Won't return until finished + * - timeout_ms = 0: No timeout, trigger one time statistics and return immediately, + * in this case, the result won't be assigned in this function, + * but you can get the result in the callback `esp_isp_awb_cbs_t::on_statistics_done` + * - timeout_ms > 0: Wait for specified milliseconds, if not finished, then return timeout error + * @param[out] out_res AWB white patch statistics result + * + * @return + * - ESP_OK On success + * - ESP_ERR_TIMEOUT Wait for the result timeout + * - ESP_ERR_INVALID_ARG If the combination of arguments is invalid. + * - ESP_ERR_INVALID_STATE Driver state is invalid. + */ +esp_err_t esp_isp_awb_controller_get_oneshot_statistics(isp_awb_ctlr_t awb_ctlr, int timeout_ms, isp_awb_stat_result_t *out_res); + +/** + * @brief Start AWB continuous statistics of the white patch in the window + * @note This function is an asynchronous and non-block function, + * it will start the continuous statistics and return immediately. + * You have to register the AWB callback and get the result from the callback event data. + * + * @param[in] awb_ctlr AWB controller handle + * @return + * - ESP_OK On success + * - ESP_ERR_INVALID_ARG Null pointer + * - ESP_ERR_INVALID_STATE Driver state is invalid. + */ +esp_err_t esp_isp_awb_controller_start_continuous_statistics(isp_awb_ctlr_t awb_ctlr); + +/** + * @brief Stop AWB continuous statistics of the white patch in the window + * + * @param[in] awb_ctlr AWB controller handle + * @return + * - ESP_OK On success + * - ESP_ERR_INVALID_ARG Null pointer + * - ESP_ERR_INVALID_STATE Driver state is invalid. + */ +esp_err_t esp_isp_awb_controller_stop_continuous_statistics(isp_awb_ctlr_t awb_ctlr); + +/** + * @brief Event data of callbacks + */ +typedef struct { + isp_awb_stat_result_t awb_result; /*!< The AWB white patch statistics result */ +} esp_isp_awb_evt_data_t; + +/** + * @brief Prototype of ISP AWB event callback + * + * @param[in] handle ISP AWB controller handle + * @param[in] edata ISP AWB event data + * @param[in] user_data User registered context, registered when in `esp_isp_awb_env_detector_register_event_callbacks()` + * + * @return Whether a high priority task is woken up by this function + */ +typedef bool (*esp_isp_awb_callback_t)(isp_awb_ctlr_t awb_ctlr, const esp_isp_awb_evt_data_t *edata, void *user_data); + +/** + * @brief Group of ISP AWB callbacks + * + * @note These callbacks are all running in an ISR environment. + * @note When CONFIG_ISP_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM. + * Involved variables should be in internal RAM as well. + */ +typedef struct { + esp_isp_awb_callback_t on_statistics_done; ///< Event callback, invoked when white patches statistic done. +} esp_isp_awb_cbs_t; + +/** + * @brief Register AWB event callbacks + * + * @note User can deregister a previously registered callback by calling this function and setting the to-be-deregistered callback member in + * the `cbs` structure to NULL. + * @note When CONFIG_ISP_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM. + * Involved variables (including `user_data`) should be in internal RAM as well. + * + * @param[in] awb_ctlr AWB controller handle + * @param[in] cbs Group of callback functions + * @param[in] user_data User data, which will be delivered to the callback functions directly + * + * @return + * - ESP_OK: On success + * - ESP_ERR_INVALID_ARG: Invalid arguments + * - ESP_ERR_INVALID_STATE: Driver state is invalid, you shouldn't call this API at this moment + */ +esp_err_t esp_isp_awb_register_event_callbacks(isp_awb_ctlr_t awb_ctlr, const esp_isp_awb_cbs_t *cbs, void *user_data); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_driver_isp/include/driver/isp_types.h b/components/esp_driver_isp/include/driver/isp_types.h index f374793d2c..a8baa1926e 100644 --- a/components/esp_driver_isp/include/driver/isp_types.h +++ b/components/esp_driver_isp/include/driver/isp_types.h @@ -12,6 +12,34 @@ extern "C" { #endif +/** + * @brief ISP unsigned integer range type + * @note Whether the edge value are included depends on the variable itself + */ +typedef struct { + uint32_t min; ///< Minimum unsigned int value + uint32_t max; ///< Maximum unsigned int value +} isp_u32_range_t; + +/** + * @brief ISP float range type + * @note Whether the edge value are included depends on the variable itself + */ +typedef struct { + float min; ///< Minimum float value + float max; ///< Maximum float value +} isp_float_range_t; + +/** + * @brief ISP AWB result + */ +typedef struct { + uint32_t white_patch_num; ///< white patch number that counted by AWB in the window + uint32_t sum_r; ///< The sum of R channel of these white patches + uint32_t sum_g; ///< The sum of G channel of these white patches + uint32_t sum_b; ///< The sum of B channel of these white patches +} isp_awb_stat_result_t; + /** * @brief Type of ISP processor handle */ @@ -22,6 +50,11 @@ typedef struct isp_processor_t *isp_proc_handle_t; */ typedef struct isp_af_controller_t *isp_af_ctlr_t; +/** + * @brief Type of ISP AWB controller handle + */ +typedef struct isp_awb_controller_t *isp_awb_ctlr_t; + #ifdef __cplusplus } #endif diff --git a/components/esp_driver_isp/include/esp_private/isp_private.h b/components/esp_driver_isp/include/esp_private/isp_private.h index 7b25b29882..03b0f07843 100644 --- a/components/esp_driver_isp/include/esp_private/isp_private.h +++ b/components/esp_driver_isp/include/esp_private/isp_private.h @@ -64,6 +64,7 @@ typedef struct isp_processor_t { uint32_t v_res; /* sub module contexts */ isp_af_ctlr_t af_ctlr[SOC_ISP_AF_CTLR_NUMS]; + isp_awb_ctlr_t awb_ctlr; isp_fsm_t bf_fsm; } isp_processor_t; #endif diff --git a/components/esp_driver_isp/src/isp_awb.c b/components/esp_driver_isp/src/isp_awb.c new file mode 100644 index 0000000000..b1a4523d30 --- /dev/null +++ b/components/esp_driver_isp/src/isp_awb.c @@ -0,0 +1,280 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "freertos/FreeRTOS.h" +#include "sdkconfig.h" +#include "esp_log.h" +#include "esp_check.h" +#include "esp_heap_caps.h" +#include "driver/isp_awb.h" +#include "isp_internal.h" + +typedef struct isp_awb_controller_t { + isp_fsm_t fsm; + portMUX_TYPE spinlock; + intr_handle_t intr_handle; + isp_proc_handle_t isp_proc; + QueueHandle_t evt_que; + SemaphoreHandle_t stat_lock; + esp_isp_awb_cbs_t cbs; + void *user_data; +} isp_awb_controller_t; + +static const char *TAG = "ISP_AWB"; + +static void s_isp_awb_default_isr(void *arg); + +/*--------------------------------------------- + AWB +----------------------------------------------*/ +static esp_err_t s_isp_claim_awb_controller(isp_proc_handle_t isp_proc, isp_awb_ctlr_t awb_ctlr) +{ + assert(isp_proc && awb_ctlr); + + esp_err_t ret = ESP_ERR_NOT_FOUND; + portENTER_CRITICAL(&isp_proc->spinlock); + if (!isp_proc->awb_ctlr) { + isp_proc->awb_ctlr = awb_ctlr; + ret = ESP_OK; + } + portEXIT_CRITICAL(&isp_proc->spinlock); + + return ret; +} + +static void s_isp_declaim_awb_controller(isp_awb_ctlr_t awb_ctlr) +{ + if (awb_ctlr && awb_ctlr->isp_proc) { + portENTER_CRITICAL(&awb_ctlr->isp_proc->spinlock); + awb_ctlr->isp_proc->awb_ctlr = NULL; + portEXIT_CRITICAL(&awb_ctlr->isp_proc->spinlock); + } +} + +static void s_isp_awb_free_controller(isp_awb_ctlr_t awb_ctlr) +{ + if (awb_ctlr) { + if (awb_ctlr->intr_handle) { + esp_intr_free(awb_ctlr->intr_handle); + } + if (awb_ctlr->evt_que) { + vQueueDelete(awb_ctlr->evt_que); + } + if (awb_ctlr->stat_lock) { + vSemaphoreDelete(awb_ctlr->stat_lock); + } + free(awb_ctlr); + } +} + +esp_err_t esp_isp_new_awb_controller(isp_proc_handle_t isp_proc, const esp_isp_awb_config_t *awb_cfg, isp_awb_ctlr_t *ret_hdl) +{ + esp_err_t ret = ESP_FAIL; + ESP_RETURN_ON_FALSE(isp_proc && awb_cfg && ret_hdl, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer"); + + isp_awb_ctlr_t awb_ctlr = heap_caps_calloc(1, sizeof(isp_awb_controller_t), ISP_MEM_ALLOC_CAPS); + ESP_RETURN_ON_FALSE(awb_ctlr, ESP_ERR_NO_MEM, TAG, "no mem for awb controller"); + awb_ctlr->evt_que = xQueueCreateWithCaps(1, sizeof(isp_awb_stat_result_t), ISP_MEM_ALLOC_CAPS); + ESP_GOTO_ON_FALSE(awb_ctlr->evt_que, ESP_ERR_NO_MEM, err1, TAG, "no mem for awb event queue"); + awb_ctlr->stat_lock = xSemaphoreCreateBinaryWithCaps(ISP_MEM_ALLOC_CAPS); + ESP_GOTO_ON_FALSE(awb_ctlr->stat_lock, ESP_ERR_NO_MEM, err1, TAG, "no mem for awb semaphore"); + awb_ctlr->fsm = ISP_FSM_INIT; + awb_ctlr->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED; + awb_ctlr->isp_proc = isp_proc; + + // Claim an AWB controller + ESP_GOTO_ON_ERROR(s_isp_claim_awb_controller(isp_proc, awb_ctlr), err1, TAG, "no available controller"); + // Register the AWB ISR + uint32_t intr_st_reg_addr = isp_ll_get_intr_status_reg_addr(isp_proc->hal.hw); + int intr_priority = awb_cfg->intr_priority > 0 && awb_cfg->intr_priority <= 7 ? BIT(awb_cfg->intr_priority) : ESP_INTR_FLAG_LOWMED; + ESP_GOTO_ON_ERROR(esp_intr_alloc_intrstatus(isp_hw_info.instances[isp_proc->proc_id].irq, ISP_INTR_ALLOC_FLAGS | intr_priority, intr_st_reg_addr, ISP_LL_EVENT_AWB_MASK, + s_isp_awb_default_isr, awb_ctlr, &awb_ctlr->intr_handle), err2, TAG, "allocate interrupt failed"); + + // Configure the hardware + isp_ll_awb_enable(isp_proc->hal.hw, false); + isp_ll_awb_set_sample_point(isp_proc->hal.hw, awb_cfg->sample_point); + isp_ll_awb_enable_algorithm_mode(isp_proc->hal.hw, true); + ESP_GOTO_ON_FALSE(isp_hal_awb_set_window_range(&isp_proc->hal, &awb_cfg->window), + ESP_ERR_INVALID_ARG, err2, TAG, "invalid window"); + isp_u32_range_t lum_range = awb_cfg->white_patch.luminance; + ESP_GOTO_ON_FALSE(isp_hal_awb_set_luminance_range(&isp_proc->hal, lum_range.min, lum_range.max), + ESP_ERR_INVALID_ARG, err2, TAG, "invalid luminance range"); + isp_float_range_t rg_range = awb_cfg->white_patch.red_green_ratio; + ESP_GOTO_ON_FALSE(rg_range.min < rg_range.max && rg_range.min >= 0 && + isp_hal_awb_set_rg_ratio_range(&isp_proc->hal, rg_range.min, rg_range.max), + ESP_ERR_INVALID_ARG, err2, TAG, "invalid range of Red Green ratio"); + isp_float_range_t bg_range = awb_cfg->white_patch.blue_green_ratio; + ESP_GOTO_ON_FALSE(bg_range.min < bg_range.max && bg_range.min >= 0 && + isp_hal_awb_set_bg_ratio_range(&isp_proc->hal, bg_range.min, bg_range.max), + ESP_ERR_INVALID_ARG, err2, TAG, "invalid range of Blue to Green ratio"); + + *ret_hdl = awb_ctlr; + + return ESP_OK; + +err2: + s_isp_declaim_awb_controller(awb_ctlr); +err1: + s_isp_awb_free_controller(awb_ctlr); + + return ret; +} + +esp_err_t esp_isp_del_awb_controller(isp_awb_ctlr_t awb_ctlr) +{ + ESP_RETURN_ON_FALSE(awb_ctlr && awb_ctlr->isp_proc, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer"); + ESP_RETURN_ON_FALSE(awb_ctlr->isp_proc->awb_ctlr == awb_ctlr, ESP_ERR_INVALID_ARG, TAG, "controller isn't in use"); + ESP_RETURN_ON_FALSE(awb_ctlr->fsm == ISP_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "controller isn't in init state"); + s_isp_declaim_awb_controller(awb_ctlr); + + isp_ll_awb_enable_algorithm_mode(awb_ctlr->isp_proc->hal.hw, false); + s_isp_awb_free_controller(awb_ctlr); + + return ESP_OK; +} + +esp_err_t esp_isp_awb_controller_enable(isp_awb_ctlr_t awb_ctlr) +{ + ESP_RETURN_ON_FALSE(awb_ctlr && awb_ctlr->isp_proc, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer"); + ESP_RETURN_ON_FALSE(awb_ctlr->fsm == ISP_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "controller isn't in init state"); + + esp_intr_enable(awb_ctlr->intr_handle); + isp_ll_awb_clk_enable(awb_ctlr->isp_proc->hal.hw, true); + isp_ll_enable_intr(awb_ctlr->isp_proc->hal.hw, ISP_LL_EVENT_AWB_MASK, true); + xSemaphoreGive(awb_ctlr->stat_lock); + awb_ctlr->fsm = ISP_FSM_ENABLE; + + return ESP_OK; +} + +esp_err_t esp_isp_awb_controller_disable(isp_awb_ctlr_t awb_ctlr) +{ + ESP_RETURN_ON_FALSE(awb_ctlr && awb_ctlr->isp_proc, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer"); + ESP_RETURN_ON_FALSE(awb_ctlr->fsm == ISP_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "controller isn't in enable state"); + + isp_ll_enable_intr(awb_ctlr->isp_proc->hal.hw, ISP_LL_EVENT_AWB_MASK, false); + isp_ll_awb_clk_enable(awb_ctlr->isp_proc->hal.hw, false); + esp_intr_disable(awb_ctlr->intr_handle); + awb_ctlr->fsm = ISP_FSM_INIT; + xSemaphoreTake(awb_ctlr->stat_lock, 0); + + return ESP_OK; +} + +esp_err_t esp_isp_awb_controller_get_oneshot_statistics(isp_awb_ctlr_t awb_ctlr, int timeout_ms, isp_awb_stat_result_t *out_res) +{ + ESP_RETURN_ON_FALSE_ISR(awb_ctlr && (out_res || timeout_ms == 0), ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer"); + ESP_RETURN_ON_FALSE_ISR(awb_ctlr->fsm == ISP_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "controller isn't in enable state"); + TickType_t ticks = timeout_ms < 0 ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); + + xSemaphoreTake(awb_ctlr->stat_lock, ticks); + // Update state to avoid race condition + awb_ctlr->fsm = ISP_FSM_START; + esp_err_t ret = ESP_OK; + // Reset the queue in case receiving the legacy data in the queue + xQueueReset(awb_ctlr->evt_que); + // Start the AWB white patch statistics and waiting it done + isp_ll_awb_enable(awb_ctlr->isp_proc->hal.hw, true); + // Wait the statistics to finish and receive the result from the queue + if ((ticks > 0) && xQueueReceive(awb_ctlr->evt_que, out_res, ticks) != pdTRUE) { + ret = ESP_ERR_TIMEOUT; + } + // Stop the AWB white patch statistics + isp_ll_awb_enable(awb_ctlr->isp_proc->hal.hw, false); + awb_ctlr->fsm = ISP_FSM_ENABLE; + xSemaphoreGive(awb_ctlr->stat_lock); + + return ret; +} + +esp_err_t esp_isp_awb_controller_start_continuous_statistics(isp_awb_ctlr_t awb_ctlr) +{ + ESP_RETURN_ON_FALSE_ISR(awb_ctlr, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer"); + ESP_RETURN_ON_FALSE_ISR(awb_ctlr->fsm == ISP_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "controller isn't in enable state"); + + if (xSemaphoreTake(awb_ctlr->stat_lock, 0) == pdFALSE) { + ESP_LOGW(TAG, "statistics lock is not acquired, controller is busy"); + return ESP_ERR_INVALID_STATE; + } + awb_ctlr->fsm = ISP_FSM_START; + isp_ll_awb_enable(awb_ctlr->isp_proc->hal.hw, true); + + return ESP_OK; +} + +esp_err_t esp_isp_awb_controller_stop_continuous_statistics(isp_awb_ctlr_t awb_ctlr) +{ + ESP_RETURN_ON_FALSE_ISR(awb_ctlr, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer"); + ESP_RETURN_ON_FALSE_ISR(awb_ctlr->fsm == ISP_FSM_START, ESP_ERR_INVALID_STATE, TAG, "controller isn't in continuous state"); + + isp_ll_awb_enable(awb_ctlr->isp_proc->hal.hw, false); + awb_ctlr->fsm = ISP_FSM_ENABLE; + xSemaphoreGive(awb_ctlr->stat_lock); + + return ESP_OK; +} + +/*--------------------------------------------------------------- + INTR +---------------------------------------------------------------*/ +static void IRAM_ATTR s_isp_awb_default_isr(void *arg) +{ + isp_awb_ctlr_t awb_ctlr = (isp_awb_ctlr_t)arg; + isp_proc_handle_t proc = awb_ctlr->isp_proc; + + uint32_t awb_events = isp_hal_check_clear_intr_event(&proc->hal, ISP_LL_EVENT_AWB_MASK); + + bool need_yield = false; + + if (awb_events & ISP_LL_EVENT_AWB_FDONE) { + isp_awb_ctlr_t awb_ctlr = proc->awb_ctlr; + // Get the statistics result + esp_isp_awb_evt_data_t edata = { + .awb_result = { + .white_patch_num = isp_ll_awb_get_white_patcherence_cnt(proc->hal.hw), + .sum_r = isp_ll_awb_get_accumulated_r_value(proc->hal.hw), + .sum_g = isp_ll_awb_get_accumulated_g_value(proc->hal.hw), + .sum_b = isp_ll_awb_get_accumulated_b_value(proc->hal.hw), + }, + }; + // Invoke the callback if the callback is registered + if (awb_ctlr->cbs.on_statistics_done) { + need_yield |= awb_ctlr->cbs.on_statistics_done(awb_ctlr, &edata, awb_ctlr->user_data); + } + BaseType_t high_task_awake = false; + // Send the event data to the queue, overwrite the legacy one if exist + xQueueOverwriteFromISR(awb_ctlr->evt_que, &edata.awb_result, &high_task_awake); + need_yield |= high_task_awake == pdTRUE; + /* If started continuous sampling, then trigger the next AWB sample */ + if (awb_ctlr->fsm == ISP_FSM_START) { + isp_ll_awb_enable(awb_ctlr->isp_proc->hal.hw, true); + } + } + + if (need_yield) { + portYIELD_FROM_ISR(); + } +} + +esp_err_t esp_isp_awb_register_event_callbacks(isp_awb_ctlr_t awb_ctlr, const esp_isp_awb_cbs_t *cbs, void *user_data) +{ + ESP_RETURN_ON_FALSE(awb_ctlr && cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_FALSE(awb_ctlr->fsm == ISP_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "detector isn't in the init state"); +#if CONFIG_ISP_ISR_IRAM_SAFE + if (cbs->on_statistics_done) { + ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_env_change), ESP_ERR_INVALID_ARG, TAG, "on_env_change callback not in IRAM"); + } + if (user_data) { + ESP_RETURN_ON_FALSE(esp_ptr_internal(user_data), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM"); + } +#endif + awb_ctlr->cbs.on_statistics_done = cbs->on_statistics_done; + awb_ctlr->user_data = user_data; + + return ESP_OK; +} diff --git a/components/esp_driver_isp/test_apps/isp/main/test_isp_driver.c b/components/esp_driver_isp/test_apps/isp/main/test_isp_driver.c index c6a3abcbe9..33b6c84cb9 100644 --- a/components/esp_driver_isp/test_apps/isp/main/test_isp_driver.c +++ b/components/esp_driver_isp/test_apps/isp/main/test_isp_driver.c @@ -56,3 +56,51 @@ TEST_CASE("ISP AF controller exhausted allocation", "[isp]") } TEST_ESP_OK(esp_isp_del_processor(isp_proc)); } + +TEST_CASE("ISP AWB driver basic function", "[isp]") +{ + esp_isp_processor_cfg_t isp_config = { + .clk_hz = 80 * 1000 * 1000, + .input_data_source = ISP_INPUT_DATA_SOURCE_CSI, + .input_data_color_type = ISP_COLOR_RAW8, + .output_data_color_type = ISP_COLOR_RGB565, + }; + isp_proc_handle_t isp_proc = NULL; + TEST_ESP_OK(esp_isp_new_processor(&isp_config, &isp_proc)); + TEST_ESP_OK(esp_isp_enable(isp_proc)); + + isp_awb_ctlr_t awb_ctlr = NULL; + uint32_t image_width = 800; + uint32_t image_height = 600; + /* Default parameters from helper macro */ + esp_isp_awb_config_t awb_config = { + .sample_point = ISP_AWB_SAMPLE_POINT_AFTER_CCM, + .window = { + .top_left = {.x = image_width * 0.2, .y = image_height * 0.2}, + .btm_right = {.x = image_width * 0.8, .y = image_height * 0.8}, + }, + .white_patch = { + .luminance = {.min = 0, .max = 220 * 3}, + .red_green_ratio = {.min = 0.0f, .max = 3.999f}, + .blue_green_ratio = {.min = 0.0f, .max = 3.999f}, + }, + }; + isp_awb_stat_result_t stat_res = {}; + /* Create the awb controller */ + TEST_ESP_OK(esp_isp_new_awb_controller(isp_proc, &awb_config, &awb_ctlr)); + /* Enabled the awb controller */ + TEST_ESP_OK(esp_isp_awb_controller_enable(awb_ctlr)); + /* Start continuous AWB statistics */ + TEST_ESP_OK(esp_isp_awb_controller_start_continuous_statistics(awb_ctlr)); + TEST_ESP_ERR(ESP_ERR_INVALID_STATE, esp_isp_awb_controller_get_oneshot_statistics(awb_ctlr, 0, &stat_res)); + /* Stop continuous AWB statistics */ + TEST_ESP_OK(esp_isp_awb_controller_stop_continuous_statistics(awb_ctlr)); + TEST_ESP_ERR(ESP_ERR_TIMEOUT, esp_isp_awb_controller_get_oneshot_statistics(awb_ctlr, 1, &stat_res)); + /* Disable the awb controller */ + TEST_ESP_OK(esp_isp_awb_controller_disable(awb_ctlr)); + /* Delete the awb controller and free the resources */ + TEST_ESP_OK(esp_isp_del_awb_controller(awb_ctlr)); + + TEST_ESP_OK(esp_isp_disable(isp_proc)); + TEST_ESP_OK(esp_isp_del_processor(isp_proc)); +} diff --git a/components/hal/esp32p4/include/hal/isp_ll.h b/components/hal/esp32p4/include/hal/isp_ll.h index eaf0760e1a..1636882ba9 100644 --- a/components/hal/esp32p4/include/hal/isp_ll.h +++ b/components/hal/esp32p4/include/hal/isp_ll.h @@ -65,6 +65,7 @@ extern "C" { #define ISP_LL_EVENT_ALL_MASK (0x1FFFFFFF) #define ISP_LL_EVENT_AF_MASK (ISP_LL_EVENT_AF_FDONE | ISP_LL_EVENT_AF_ENV) +#define ISP_LL_EVENT_AWB_MASK (ISP_LL_EVENT_AWB_FDONE) /*--------------------------------------------------------------- AF @@ -83,6 +84,22 @@ extern "C" { #define ISP_LL_DVP_DATA_TYPE_RAW10 0x2B #define ISP_LL_DVP_DATA_TYPE_RAW12 0x2C +/*--------------------------------------------------------------- + AWB +---------------------------------------------------------------*/ +#define ISP_LL_AWB_WINDOW_MAX_RANGE ((1<<12) - 1) +#define ISP_LL_AWB_LUM_MAX_RANGE ((1<<10) - 1) +#define ISP_LL_AWB_RGB_RATIO_INT_BITS (2) +#define ISP_LL_AWB_RGB_RATIO_FRAC_BITS (8) + +typedef union { + struct { + uint32_t fraction: ISP_LL_AWB_RGB_RATIO_FRAC_BITS; + uint32_t integer: ISP_LL_AWB_RGB_RATIO_INT_BITS; + }; + uint32_t val; +} isp_ll_awb_rgb_ratio_t; + /** * @brief Env monitor mode */ @@ -992,6 +1009,164 @@ static inline void isp_ll_clear_intr(isp_dev_t *hw, uint32_t mask) hw->int_clr.val = mask; } +/*--------------------------------------------------------------- + AWB +---------------------------------------------------------------*/ +/** + * @brief Enable / Disable AWB clock + * + * @param[in] hw Hardware instance address + * @param[in] enable Enable / Disable + */ +static inline void isp_ll_awb_clk_enable(isp_dev_t *hw, bool enable) +{ + hw->clk_en.clk_awb_force_on = enable; +} + +/** + * @brief Enable AWB statistics + * + * @param[in] hw Hardware instance address + * @param[in] enable Enable / Disable + */ +__attribute__((always_inline)) +static inline void isp_ll_awb_enable(isp_dev_t *hw, bool enable) +{ + hw->cntl.awb_en = enable; +} + +/** + * @brief Set AWB sample point + * + * @param[in] hw Hardware instance address + * @param[in] point Sample point + * - 0: Before CCM + * - 1: After CCM + */ +static inline void isp_ll_awb_set_sample_point(isp_dev_t *hw, isp_awb_sample_point_t point) +{ + hw->awb_mode.awb_sample = point; +} + +/** + * @brief Set AWB algorithm mode + * + * @param[in] hw Hardware instance address + * @param[in] enable Enable algorithm mode 1 + */ +static inline void isp_ll_awb_enable_algorithm_mode(isp_dev_t *hw, bool enable) +{ + hw->awb_mode.awb_mode = enable; +} + +/** + * @brief Set AWB window range + * + * @param[in] hw Hardware instance address + * @param[in] top_left_x Top left pixel x axis value + * @param[in] top_left_y Top left pixel y axis value + * @param[in] bottom_right_x Bottom right pixel x axis value + * @param[in] bottom_right_y Bottom right pixel y axis value + */ +static inline void isp_ll_awb_set_window_range(isp_dev_t *hw, uint32_t top_left_x, uint32_t top_left_y, uint32_t bottom_right_x, uint32_t bottom_right_y) +{ + hw->awb_hscale.awb_lpoint = top_left_x; + hw->awb_vscale.awb_tpoint = top_left_y; + hw->awb_hscale.awb_rpoint = bottom_right_x; + hw->awb_vscale.awb_bpoint = bottom_right_y; +} + +/** + * @brief Set AWB luminance range + * + * @param[in] hw Hardware instance address + * @param[in] min Minimum luminance + * @param[in] max Maximum luminance + */ +static inline void isp_ll_awb_set_luminance_range(isp_dev_t *hw, uint32_t min, uint32_t max) +{ + hw->awb_th_lum.awb_min_lum = min; + hw->awb_th_lum.awb_max_lum = max; +} + +/** + * @brief Set AWB R/G ratio range + * + * @param[in] hw Hardware instance address + * @param[in] min Minimum R/G ratio in fixed-point data type + * @param[in] max Maximum R/G ratio in fixed-point data type + */ +static inline void isp_ll_awb_set_rg_ratio_range(isp_dev_t *hw, isp_ll_awb_rgb_ratio_t min, isp_ll_awb_rgb_ratio_t max) +{ + hw->awb_th_rg.awb_min_rg = min.val; + hw->awb_th_rg.awb_max_rg = max.val; +} + +/** + * @brief Set AWB B/G ratio range + * + * @param[in] hw Hardware instance address + * @param[in] min Minimum B/G ratio in fixed-point data type + * @param[in] max Maximum B/G ratio in fixed-point data type + */ +static inline void isp_ll_awb_set_bg_ratio_range(isp_dev_t *hw, isp_ll_awb_rgb_ratio_t min, isp_ll_awb_rgb_ratio_t max) +{ + hw->awb_th_bg.awb_min_bg = min.val; + hw->awb_th_bg.awb_max_bg = max.val; +} + +/** + * @brief Get AWB white patch count + * + * @param[in] hw Hardware instance address + * @return + * - white patch count + */ +__attribute__((always_inline)) +static inline uint32_t isp_ll_awb_get_white_patcherence_cnt(isp_dev_t *hw) +{ + return hw->awb0_white_cnt.awb0_white_cnt; +} + +/** + * @brief Get AWB accumulated R value of white patches + * + * @param[in] hw Hardware instance address + * @return + * - Accumulated R value of white patches + */ +__attribute__((always_inline)) +static inline uint32_t isp_ll_awb_get_accumulated_r_value(isp_dev_t *hw) +{ + return hw->awb0_acc_r.awb0_acc_r; +} + +/** + * @brief Get AWB accumulated G value of white patches + * + * @param[in] hw Hardware instance address + * @return + * - Accumulated G value of white patches + */ +__attribute__((always_inline)) +static inline uint32_t isp_ll_awb_get_accumulated_g_value(isp_dev_t *hw) +{ + return hw->awb0_acc_g.awb0_acc_g; +} + +/** + * @brief Get AWB accumulated B value of white patches + * + * @param[in] hw Hardware instance address + * @return + * - Accumulated B value of white patches + */ +__attribute__((always_inline)) +static inline uint32_t isp_ll_awb_get_accumulated_b_value(isp_dev_t *hw) +{ + return hw->awb0_acc_b.awb0_acc_b; +} + #ifdef __cplusplus } #endif diff --git a/components/hal/include/hal/isp_hal.h b/components/hal/include/hal/isp_hal.h index c239fa4318..335c8b92dc 100644 --- a/components/hal/include/hal/isp_hal.h +++ b/components/hal/include/hal/isp_hal.h @@ -13,7 +13,9 @@ #pragma once #include +#include #include "hal/isp_types.h" +#include "hal/hal_utils.h" #ifdef __cplusplus extern "C" { @@ -60,7 +62,7 @@ void isp_hal_init(isp_hal_context_t *hal, int isp_id); * * @param[in] hal Context of the HAL layer * @param[in] window_id Window ID - * @param[in] window Window info, see `isp_af_window_t` + * @param[in] window Window info, see `isp_window_t` */ void isp_hal_af_window_config(const isp_hal_context_t *hal, int window_id, const isp_window_t *window); @@ -86,6 +88,69 @@ uint32_t isp_hal_check_clear_intr_event(const isp_hal_context_t *hal, uint32_t m */ void isp_hal_bf_config(isp_hal_context_t *hal, isp_hal_bf_cfg_t *config); +/*--------------------------------------------------------------- + Color Correction Matrix +---------------------------------------------------------------*/ +/** + * @brief Set Color Correction Matrix + * + * @param[in] hal Context of the HAL layer + * @param[in] saturation Whether to enable saturation when float data overflow + * @param[in] flt_matrix 3x3 RGB correction matrix + * @return + * - true Set success + * - false Invalid are + */ +bool isp_hal_ccm_set_matrix(const isp_hal_context_t *hal, bool saturation, const float flt_matrix[3][3]); + +/*--------------------------------------------------------------- + AWB +---------------------------------------------------------------*/ +/** + * @brief Set the window of the AWB + * + * @param[in] hal Context of the HAL layer + * @param[in] win Pointer to the window of the AWB + * @return + * - true Set success + * - false Invalid arg + */ +bool isp_hal_awb_set_window_range(const isp_hal_context_t *hal, const isp_window_t *win); + +/** + * @brief Set the luminance range of the white patch + * + * @param[in] hal Context of the HAL layer + * @param[in] lum_min Minimum luminance + * @param[in] lum_max Maximum luminance + * @return + * - true Set success + * - false Invalid arg + */ +bool isp_hal_awb_set_luminance_range(const isp_hal_context_t *hal, uint32_t lum_min, uint32_t lum_max); + +/** + * @brief Set the R/G ratio of the white patch + * + * @param[in] hal Context of the HAL layer + * @param[in] rg_ratio_range Range of Red to Green ratio + * @return + * - true Set success + * - false Invalid arg + */ +bool isp_hal_awb_set_rg_ratio_range(const isp_hal_context_t *hal, float rg_min, float rg_max); + +/** + * @brief Set the B/R ratio of the white patch + * + * @param[in] hal Context of the HAL layer + * @param[in] bg_ratio_range Range of Blue to Green ratio + * @return + * - true Set success + * - false Invalid arg + */ +bool isp_hal_awb_set_bg_ratio_range(const isp_hal_context_t *hal, float bg_min, float bg_max); + #ifdef __cplusplus } #endif diff --git a/components/hal/include/hal/isp_types.h b/components/hal/include/hal/isp_types.h index 2d02ae3cd8..be08bd177b 100644 --- a/components/hal/include/hal/isp_types.h +++ b/components/hal/include/hal/isp_types.h @@ -108,6 +108,20 @@ typedef enum { ISP_BF_EDGE_PADDING_MODE_CUSTOM_DATA, ///< Fill BF edge padding data with custom pixel data } isp_bf_edge_padding_mode_t; +/*--------------------------------------------------------------- + AWB +---------------------------------------------------------------*/ + +/** + * @brief ISP AWB sample point in the ISP pipeline + * + */ +typedef enum { + ISP_AWB_SAMPLE_POINT_BEFORE_CCM, ///< Sample AWB data before CCM (Color Correction Matrix) + ISP_AWB_SAMPLE_POINT_AFTER_CCM, ///< Sample AWB data after CCM (Color Correction Matrix) +} isp_awb_sample_point_t; + + #ifdef __cplusplus } #endif diff --git a/components/hal/isp_hal.c b/components/hal/isp_hal.c index 16b496a3af..96de231e91 100644 --- a/components/hal/isp_hal.c +++ b/components/hal/isp_hal.c @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include #include #include #include "sdkconfig.h" @@ -13,6 +14,9 @@ #include "hal/isp_hal.h" #include "hal/isp_ll.h" #include "hal/isp_types.h" +#include "hal/hal_utils.h" + +#include "esp_rom_sys.h" /** * ISP HAL layer @@ -68,3 +72,71 @@ uint32_t isp_hal_check_clear_intr_event(const isp_hal_context_t *hal, uint32_t m return triggered_events; } +/*--------------------------------------------------------------- + AWB +---------------------------------------------------------------*/ +bool isp_hal_awb_set_window_range(const isp_hal_context_t *hal, const isp_window_t *win) +{ + if (win->top_left.x > win->btm_right.x || + win->top_left.y > win->btm_right.y || + win->btm_right.x > ISP_LL_AWB_WINDOW_MAX_RANGE || + win->btm_right.y > ISP_LL_AWB_WINDOW_MAX_RANGE) { + return false; + } + isp_ll_awb_set_window_range(hal->hw, win->top_left.x, win->top_left.y, + win->btm_right.x, win->btm_right.y); + return true; +} + +bool isp_hal_awb_set_luminance_range(const isp_hal_context_t *hal, uint32_t lum_min, uint32_t lum_max) +{ + if (lum_min > lum_max || lum_max > ISP_LL_AWB_LUM_MAX_RANGE) { + return false; + } + isp_ll_awb_set_luminance_range(hal->hw, lum_min, lum_max); + return true; +} + +bool isp_hal_awb_set_rg_ratio_range(const isp_hal_context_t *hal, float rg_min, float rg_max) +{ + // Convert to fixed point + isp_ll_awb_rgb_ratio_t fp_rg_min = {}; + isp_ll_awb_rgb_ratio_t fp_rg_max = {}; + hal_utils_fixed_point_t fp_cfg = { + .int_bit = ISP_LL_AWB_RGB_RATIO_INT_BITS, + .frac_bit = ISP_LL_AWB_RGB_RATIO_FRAC_BITS, + .saturation = false, + }; + if (hal_utils_float_to_fixed_point_32b(rg_min, &fp_cfg, &fp_rg_min.val) != 0) { + return false; + } + if (hal_utils_float_to_fixed_point_32b(rg_max, &fp_cfg, &fp_rg_max.val) != 0) { + return false; + } + + // Set AWB white patch R/G ratio range + isp_ll_awb_set_rg_ratio_range(hal->hw, fp_rg_min, fp_rg_max); + return true; +} + +bool isp_hal_awb_set_bg_ratio_range(const isp_hal_context_t *hal, float bg_min, float bg_max) +{ + // Convert to fixed point + isp_ll_awb_rgb_ratio_t fp_bg_min = {}; + isp_ll_awb_rgb_ratio_t fp_bg_max = {}; + hal_utils_fixed_point_t fp_cfg = { + .int_bit = ISP_LL_AWB_RGB_RATIO_INT_BITS, + .frac_bit = ISP_LL_AWB_RGB_RATIO_FRAC_BITS, + .saturation = false, + }; + if (hal_utils_float_to_fixed_point_32b(bg_min, &fp_cfg, &fp_bg_min.val) != 0) { + return false; + } + if (hal_utils_float_to_fixed_point_32b(bg_max, &fp_cfg, &fp_bg_max.val) != 0) { + return false; + } + + // Set AWB white patch B/G ratio range + isp_ll_awb_set_bg_ratio_range(hal->hw, fp_bg_min, fp_bg_max); + return true; +} diff --git a/examples/peripherals/isp/auto_focus/main/isp_af_dsi_main.c b/examples/peripherals/isp/auto_focus/main/isp_af_dsi_main.c index d8f84c2b0c..2e60bac255 100644 --- a/examples/peripherals/isp/auto_focus/main/isp_af_dsi_main.c +++ b/examples/peripherals/isp/auto_focus/main/isp_af_dsi_main.c @@ -92,7 +92,7 @@ static void af_task(void *arg) /** * AF window, windows for ISP hardware to record the - * - lunimance + * - luminance * - definition * of the current windows */