rgb_lcd: support yuv converter

This commit is contained in:
morris 2022-08-06 12:44:52 +08:00
parent 104b9c3fb1
commit fc1aa2848b
15 changed files with 345 additions and 11 deletions

View File

@ -21,7 +21,8 @@ repos:
.+test_idf_monitor\/tests\/.+|
.*_pb2.py|
.*.pb-c.h|
.*.pb-c.c
.*.pb-c.c|
.*.yuv
)$
- id: end-of-file-fixer
exclude: *whitespace_excludes

View File

@ -115,7 +115,7 @@ typedef struct {
lcd_clock_source_t clk_src; /*!< Clock source for the RGB LCD peripheral */
esp_lcd_rgb_timing_t timings; /*!< RGB timing parameters, including the screen resolution */
size_t data_width; /*!< Number of data lines */
size_t bits_per_pixel; /*!< Color depth, in bpp, specially, if set to zero, it will default to `data_width`.
size_t bits_per_pixel; /*!< Frame buffer color depth, in bpp, specially, if set to zero, it will default to `data_width`.
When using a Serial RGB interface, this value could be different from `data_width` */
size_t bounce_buffer_size_px; /*!< If it's non-zero, the driver allocates two DRAM bounce buffers for DMA use.
DMA fetching from DRAM bounce buffer is much faster than PSRAM frame buffer. */
@ -209,6 +209,39 @@ esp_err_t esp_lcd_rgb_panel_get_frame_buffer(esp_lcd_panel_handle_t panel, uint3
*/
esp_err_t esp_lcd_rgb_panel_refresh(esp_lcd_panel_handle_t panel);
/**
* @brief LCD color conversion profile
*/
typedef struct {
lcd_color_space_t color_space; /*!< Color space of the image */
lcd_color_range_t color_range; /*!< Color range of the image */
lcd_yuv_sample_t yuv_sample; /*!< YUV sample format of the image */
} esp_lcd_color_conv_profile_t;
/**
* @brief Configuration of YUG-RGB conversion
*/
typedef struct {
lcd_yuv_conv_std_t std; /*!< YUV conversion standard: BT601, BT709 */
esp_lcd_color_conv_profile_t src; /*!< Color conversion profile of the input image */
esp_lcd_color_conv_profile_t dst; /*!< Color conversion profile of the output image */
} esp_lcd_yuv_conv_config_t;
/**
* @brief Configure how to convert the color format between RGB and YUV
*
* @note Pass in `config` as NULL will disable the RGB-YUV converter.
* @note The hardware converter can only parse a "packed" storage format, while "planar" and "semi-planar" format is not supported.
*
* @param[in] panel LCD panel handle, returned from `esp_lcd_new_rgb_panel`
* @param[in] config Configuration of RGB-YUV conversion
* @return
* - ESP_ERR_INVALID_ARG: Configure RGB-YUV conversion failed because of invalid argument
* - ESP_ERR_NOT_SUPPORTED: Configure RGB-YUV conversion failed because the conversion mode is not supported by the hardware
* - ESP_OK: Configure RGB-YUV conversion successfully
*/
esp_err_t esp_lcd_rgb_panel_set_yuv_conversion(esp_lcd_panel_handle_t panel, const esp_lcd_yuv_conv_config_t *config);
#endif // SOC_LCD_RGB_SUPPORTED
#ifdef __cplusplus

View File

@ -83,7 +83,8 @@ struct esp_rgb_panel_t {
int panel_id; // LCD panel ID
lcd_hal_context_t hal; // Hal layer object
size_t data_width; // Number of data lines
size_t bits_per_pixel; // Color depth, in bpp
size_t fb_bits_per_pixel; // Frame buffer color depth, in bpp
size_t output_bits_per_pixel; // Color depth seen from the output data line. Default to fb_bits_per_pixel, but can be changed by YUV-RGB conversion
size_t sram_trans_align; // Alignment for framebuffer that allocated in SRAM
size_t psram_trans_align; // Alignment for framebuffer that allocated in PSRAM
int disp_gpio_num; // Display control GPIO, which is used to perform action like "disp_off"
@ -220,13 +221,13 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
#endif
// bpp defaults to the number of data lines, but for serial RGB interface, they're not equal
size_t bits_per_pixel = rgb_panel_config->data_width;
size_t fb_bits_per_pixel = rgb_panel_config->data_width;
if (rgb_panel_config->bits_per_pixel) { // override bpp if it's set
bits_per_pixel = rgb_panel_config->bits_per_pixel;
fb_bits_per_pixel = rgb_panel_config->bits_per_pixel;
}
// calculate buffer size
size_t fb_size = rgb_panel_config->timings.h_res * rgb_panel_config->timings.v_res * bits_per_pixel / 8;
size_t bb_size = rgb_panel_config->bounce_buffer_size_px * bits_per_pixel / 8;
size_t fb_size = rgb_panel_config->timings.h_res * rgb_panel_config->timings.v_res * fb_bits_per_pixel / 8;
size_t bb_size = rgb_panel_config->bounce_buffer_size_px * fb_bits_per_pixel / 8;
if (bb_size) {
// we want the bounce can always end in the second buffer
ESP_GOTO_ON_FALSE(fb_size % (2 * bb_size) == 0, ESP_ERR_INVALID_ARG, err, TAG,
@ -297,7 +298,8 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
memcpy(rgb_panel->data_gpio_nums, rgb_panel_config->data_gpio_nums, SOC_LCD_RGB_DATA_WIDTH);
rgb_panel->timings = rgb_panel_config->timings;
rgb_panel->data_width = rgb_panel_config->data_width;
rgb_panel->bits_per_pixel = bits_per_pixel;
rgb_panel->fb_bits_per_pixel = fb_bits_per_pixel;
rgb_panel->output_bits_per_pixel = fb_bits_per_pixel; // by default, the output bpp is the same as the frame buffer bpp
rgb_panel->disp_gpio_num = rgb_panel_config->disp_gpio_num;
rgb_panel->flags.disp_en_level = !rgb_panel_config->flags.disp_active_low;
rgb_panel->flags.no_fb = rgb_panel_config->flags.no_fb;
@ -387,6 +389,57 @@ esp_err_t esp_lcd_rgb_panel_refresh(esp_lcd_panel_handle_t panel)
return ESP_OK;
}
esp_err_t esp_lcd_rgb_panel_set_yuv_conversion(esp_lcd_panel_handle_t panel, const esp_lcd_yuv_conv_config_t *config)
{
ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
lcd_hal_context_t *hal = &rgb_panel->hal;
bool en_conversion = config != NULL;
// bits per pixel for different YUV sample
const uint8_t bpp_yuv[] = {
[LCD_YUV_SAMPLE_422] = 16,
[LCD_YUV_SAMPLE_420] = 12,
[LCD_YUV_SAMPLE_411] = 12,
};
if (en_conversion) {
if (memcmp(&config->src, &config->dst, sizeof(config->src)) == 0) {
ESP_RETURN_ON_FALSE(false, ESP_ERR_INVALID_ARG, TAG, "conversion source and destination are the same");
}
if (config->src.color_space == LCD_COLOR_SPACE_YUV && config->dst.color_space == LCD_COLOR_SPACE_RGB) { // YUV->RGB
lcd_ll_set_convert_mode_yuv_to_rgb(hal->dev, config->src.yuv_sample);
// Note, the RGB->YUV conversion only support RGB565
rgb_panel->output_bits_per_pixel = 16;
} else if (config->src.color_space == LCD_COLOR_SPACE_RGB && config->dst.color_space == LCD_COLOR_SPACE_YUV) { // RGB->YUV
lcd_ll_set_convert_mode_rgb_to_yuv(hal->dev, config->dst.yuv_sample);
rgb_panel->output_bits_per_pixel = bpp_yuv[config->dst.yuv_sample];
} else if (config->src.color_space == LCD_COLOR_SPACE_YUV && config->dst.color_space == LCD_COLOR_SPACE_YUV) { // YUV->YUV
lcd_ll_set_convert_mode_yuv_to_yuv(hal->dev, config->src.yuv_sample, config->dst.yuv_sample);
rgb_panel->output_bits_per_pixel = bpp_yuv[config->dst.yuv_sample];
} else {
ESP_RETURN_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, TAG, "unsupported conversion mode");
}
// set conversion standard
lcd_ll_set_yuv_convert_std(hal->dev, config->std);
// set conversion data width
lcd_ll_set_convert_data_width(hal->dev, rgb_panel->data_width);
// set color range
lcd_ll_set_input_color_range(hal->dev, config->src.color_range);
lcd_ll_set_output_color_range(hal->dev, config->dst.color_range);
} else {
// output bpp equals to frame buffer bpp
rgb_panel->output_bits_per_pixel = rgb_panel->fb_bits_per_pixel;
}
// enable or disable RGB-YUV conversion
lcd_ll_enable_rgb_yuv_convert(hal->dev, en_conversion);
return ESP_OK;
}
static esp_err_t rgb_panel_del(esp_lcd_panel_t *panel)
{
esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
@ -425,7 +478,7 @@ static esp_err_t rgb_panel_init(esp_lcd_panel_t *panel)
// configure blank region timing
lcd_ll_set_blank_cycles(rgb_panel->hal.dev, 1, 1); // RGB panel always has a front and back blank (porch region)
lcd_ll_set_horizontal_timing(rgb_panel->hal.dev, rgb_panel->timings.hsync_pulse_width,
rgb_panel->timings.hsync_back_porch, rgb_panel->timings.h_res * rgb_panel->bits_per_pixel / rgb_panel->data_width,
rgb_panel->timings.hsync_back_porch, rgb_panel->timings.h_res * rgb_panel->output_bits_per_pixel / rgb_panel->data_width,
rgb_panel->timings.hsync_front_porch);
lcd_ll_set_vertical_timing(rgb_panel->hal.dev, rgb_panel->timings.vsync_pulse_width,
rgb_panel->timings.vsync_back_porch, rgb_panel->timings.v_res,
@ -500,7 +553,7 @@ static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int
y_end = MIN(y_end, v_res);
}
int bytes_per_pixel = rgb_panel->bits_per_pixel / 8;
int bytes_per_pixel = rgb_panel->fb_bits_per_pixel / 8;
int pixels_per_line = rgb_panel->timings.h_res;
uint32_t bytes_per_line = bytes_per_pixel * pixels_per_line;
uint8_t *fb = rgb_panel->fbs[rgb_panel->cur_fb_index];
@ -862,7 +915,7 @@ static esp_err_t lcd_rgb_panel_select_clock_src(esp_rgb_panel_t *panel, lcd_cloc
static IRAM_ATTR bool lcd_rgb_panel_fill_bounce_buffer(esp_rgb_panel_t *panel, uint8_t *buffer)
{
bool need_yield = false;
int bytes_per_pixel = panel->bits_per_pixel / 8;
int bytes_per_pixel = panel->fb_bits_per_pixel / 8;
if (panel->flags.no_fb) {
if (panel->on_bounce_empty) {
// We don't have a frame buffer here; we need to call a callback to refill the bounce buffer

View File

@ -4,6 +4,9 @@ cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(rgb_lcd_panel_test)
target_add_binary_data(rgb_lcd_panel_test.elf "resources/pictures/hello.yuv" BINARY)
target_add_binary_data(rgb_lcd_panel_test.elf "resources/pictures/world.yuv" BINARY)
if(CONFIG_COMPILER_DUMP_RTL_FILES)
add_custom_target(check_test_app_sections ALL
COMMAND ${PYTHON} $ENV{IDF_PATH}/tools/ci/check_callgraph.py

View File

@ -1,6 +1,10 @@
set(srcs "test_app_main.c"
"test_rgb_panel.c")
if(CONFIG_SOC_LCD_SUPPORT_RGB_YUV_CONV)
list(APPEND srcs "test_yuv_rgb_conv.c")
endif()
# In order for the cases defined by `TEST_CASE` to be linked into the final elf,
# the component can be registered as WHOLE_ARCHIVE
idf_component_register(SRCS ${srcs}

View File

@ -0,0 +1,105 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "unity.h"
#include "esp_lcd_panel_rgb.h"
#include "esp_lcd_panel_ops.h"
#include "esp_random.h"
#include "esp_timer.h"
#include "esp_attr.h"
#include "spi_flash_mmap.h"
#include "test_rgb_board.h"
#define TEST_IMG_SIZE (320 * 320 * sizeof(uint16_t))
// YUV images are embedded in the firmware binary
extern const uint8_t image_hello_yuv_start[] asm("_binary_hello_yuv_start");
extern const uint8_t image_hello_yuv_end[] asm("_binary_hello_yuv_end");
extern const uint8_t image_world_yuv_start[] asm("_binary_world_yuv_start");
extern const uint8_t image_world_yuv_end[] asm("_binary_world_yuv_end");
TEST_CASE("lcd_rgb_panel_yuv422_conversion", "[lcd]")
{
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_rgb_panel_config_t panel_config = {
.data_width = 16,
.psram_trans_align = 64,
.bits_per_pixel = 16, // YUV422: 16bits per pixel
.clk_src = LCD_CLK_SRC_DEFAULT,
.disp_gpio_num = TEST_LCD_DISP_EN_GPIO,
.pclk_gpio_num = TEST_LCD_PCLK_GPIO,
.vsync_gpio_num = TEST_LCD_VSYNC_GPIO,
.hsync_gpio_num = TEST_LCD_HSYNC_GPIO,
.de_gpio_num = TEST_LCD_DE_GPIO,
.data_gpio_nums = {
TEST_LCD_DATA0_GPIO,
TEST_LCD_DATA1_GPIO,
TEST_LCD_DATA2_GPIO,
TEST_LCD_DATA3_GPIO,
TEST_LCD_DATA4_GPIO,
TEST_LCD_DATA5_GPIO,
TEST_LCD_DATA6_GPIO,
TEST_LCD_DATA7_GPIO,
TEST_LCD_DATA8_GPIO,
TEST_LCD_DATA9_GPIO,
TEST_LCD_DATA10_GPIO,
TEST_LCD_DATA11_GPIO,
TEST_LCD_DATA12_GPIO,
TEST_LCD_DATA13_GPIO,
TEST_LCD_DATA14_GPIO,
TEST_LCD_DATA15_GPIO,
},
.timings = {
.pclk_hz = TEST_LCD_PIXEL_CLOCK_HZ,
.h_res = TEST_LCD_H_RES,
.v_res = TEST_LCD_V_RES,
.hsync_back_porch = 68,
.hsync_front_porch = 20,
.hsync_pulse_width = 5,
.vsync_back_porch = 18,
.vsync_front_porch = 4,
.vsync_pulse_width = 1,
},
.flags.fb_in_psram = 1, // allocate frame buffer in PSRAM
};
printf("Create RGB LCD panel\r\n");
TEST_ESP_OK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle));
TEST_ESP_OK(esp_lcd_panel_reset(panel_handle));
printf("Set YUV-RGB conversion profile\r\n");
esp_lcd_yuv_conv_config_t conv_config = {
.std = LCD_YUV_CONV_STD_BT601,
.src = {
.color_range = LCD_COLOR_RANGE_FULL,
.color_space = LCD_COLOR_SPACE_RGB,
},
.dst = {
.color_range = LCD_COLOR_RANGE_FULL,
.color_space = LCD_COLOR_SPACE_RGB,
},
};
TEST_ESP_ERR(ESP_ERR_INVALID_ARG, esp_lcd_rgb_panel_set_yuv_conversion(panel_handle, &conv_config));
conv_config.src.color_space = LCD_COLOR_SPACE_YUV;
conv_config.src.yuv_sample = LCD_YUV_SAMPLE_422;
TEST_ESP_OK(esp_lcd_rgb_panel_set_yuv_conversion(panel_handle, &conv_config));
TEST_ESP_OK(esp_lcd_panel_init(panel_handle));
printf("Draw YUV images\r\n");
for (int i = 0; i < 4; i++) {
TEST_ESP_OK(esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, 320, 320, image_hello_yuv_start));
vTaskDelay(pdMS_TO_TICKS(1000));
TEST_ESP_OK(esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, 320, 320, image_world_yuv_start));
vTaskDelay(pdMS_TO_TICKS(1000));
}
TEST_ESP_OK(esp_lcd_panel_del(panel_handle));
}

View File

@ -0,0 +1,5 @@
# How to generate the YUV image from the PNG image
```bash
ffmpeg -i hello.png -pix_fmt uyvy422 hello.yuv
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

File diff suppressed because one or more lines are too long

View File

@ -28,6 +28,10 @@ extern "C" {
#define LCD_LL_CLK_FRAC_DIV_AB_MAX 64 // LCD_CLK = LCD_CLK_S / (N + b/a), the a/b register is 6 bit-width
#define LCD_LL_PCLK_DIV_MAX 64 // LCD_PCLK = LCD_CLK / MO, the MO register is 6 bit-width
#define LCD_LL_COLOR_RANGE_TO_REG(range) (uint8_t[]){0,1}[(range)]
#define LCD_LL_CONV_STD_TO_REG(std) (uint8_t[]){0,1}[(std)]
#define LCD_LL_YUV_SAMPLE_TO_REG(sample) (uint8_t[]){0,1,2}[(sample)]
/**
* @brief Enable clock gating
*
@ -144,6 +148,92 @@ static inline void lcd_ll_enable_rgb_yuv_convert(lcd_cam_dev_t *dev, bool en)
dev->lcd_rgb_yuv.lcd_conv_bypass = en;
}
/**
* @brief Set convert data line width
*
* @param dev LCD register base address
* @param width data line width (8 or 16)
*/
static inline void lcd_ll_set_convert_data_width(lcd_cam_dev_t *dev, uint32_t width)
{
HAL_ASSERT(width == 8 || width == 16);
dev->lcd_rgb_yuv.lcd_conv_mode_8bits_on = (width == 8) ? 1 : 0;
}
/**
* @brief Set the color range of input data
*
* @param dev LCD register base address
* @param range Color range
*/
static inline void lcd_ll_set_input_color_range(lcd_cam_dev_t *dev, lcd_color_range_t range)
{
dev->lcd_rgb_yuv.lcd_conv_data_in_mode = LCD_LL_COLOR_RANGE_TO_REG(range);
}
/**
* @brief Set the color range of output data
*
* @param dev LCD register base address
* @param range Color range
*/
static inline void lcd_ll_set_output_color_range(lcd_cam_dev_t *dev, lcd_color_range_t range)
{
dev->lcd_rgb_yuv.lcd_conv_data_out_mode = LCD_LL_COLOR_RANGE_TO_REG(range);
}
/**
* @brief Set YUV conversion standard
*
* @param dev LCD register base address
* @param std YUV conversion standard
*/
static inline void lcd_ll_set_yuv_convert_std(lcd_cam_dev_t *dev, lcd_yuv_conv_std_t std)
{
dev->lcd_rgb_yuv.lcd_conv_protocol_mode = LCD_LL_CONV_STD_TO_REG(std);
}
/**
* @brief Set the converter mode: RGB565 to YUV
*
* @param dev LCD register base address
* @param yuv_sample YUV sample mode
*/
static inline void lcd_ll_set_convert_mode_rgb_to_yuv(lcd_cam_dev_t *dev, lcd_yuv_sample_t yuv_sample)
{
dev->lcd_rgb_yuv.lcd_conv_trans_mode = 1;
dev->lcd_rgb_yuv.lcd_conv_yuv_mode = LCD_LL_YUV_SAMPLE_TO_REG(yuv_sample);
dev->lcd_rgb_yuv.lcd_conv_yuv2yuv_mode = 3;
}
/**
* @brief Set the converter mode: YUV to RGB565
*
* @param dev LCD register base address
* @param yuv_sample YUV sample mode
*/
static inline void lcd_ll_set_convert_mode_yuv_to_rgb(lcd_cam_dev_t *dev, lcd_yuv_sample_t yuv_sample)
{
dev->lcd_rgb_yuv.lcd_conv_trans_mode = 0;
dev->lcd_rgb_yuv.lcd_conv_yuv_mode = LCD_LL_YUV_SAMPLE_TO_REG(yuv_sample);
dev->lcd_rgb_yuv.lcd_conv_yuv2yuv_mode = 3;
}
/**
* @brief Set the converter mode: YUV to YUV
*
* @param dev LCD register base address
* @param src_sample Source YUV sample mode
* @param dst_sample Destination YUV sample mode
*/
static inline void lcd_ll_set_convert_mode_yuv_to_yuv(lcd_cam_dev_t *dev, lcd_yuv_sample_t src_sample, lcd_yuv_sample_t dst_sample)
{
HAL_ASSERT(src_sample != dst_sample);
dev->lcd_rgb_yuv.lcd_conv_trans_mode = 1;
dev->lcd_rgb_yuv.lcd_conv_yuv_mode = LCD_LL_YUV_SAMPLE_TO_REG(src_sample);
dev->lcd_rgb_yuv.lcd_conv_yuv2yuv_mode = LCD_LL_YUV_SAMPLE_TO_REG(dst_sample);
}
/**
* @brief Set clock cycles of each transaction phases
*

View File

@ -28,6 +28,39 @@ typedef enum {
LCD_RGB_ENDIAN_BGR, /*!< RGB data endian: BGR */
} lcd_color_rgb_endian_t;
/**
* @brief LCD color space
*/
typedef enum {
LCD_COLOR_SPACE_RGB, /*!< Color space: RGB */
LCD_COLOR_SPACE_YUV, /*!< Color space: YUV */
} lcd_color_space_t;
/**
* @brief LCD color range
*/
typedef enum {
LCD_COLOR_RANGE_LIMIT, /*!< Limited color range */
LCD_COLOR_RANGE_FULL, /*!< Full color range */
} lcd_color_range_t;
/**
* @brief YUV sampling method
*/
typedef enum {
LCD_YUV_SAMPLE_422, /*!< YUV 4:2:2 sampling */
LCD_YUV_SAMPLE_420, /*!< YUV 4:2:0 sampling */
LCD_YUV_SAMPLE_411, /*!< YUV 4:1:1 sampling */
} lcd_yuv_sample_t;
/**
* @brief The standard used for conversion between RGB and YUV
*/
typedef enum {
LCD_YUV_CONV_STD_BT601, /*!< YUV<->RGB conversion standard: BT.601 */
LCD_YUV_CONV_STD_BT709, /*!< YUV<->RGB conversion standard: BT.709 */
} lcd_yuv_conv_std_t;
#ifdef __cplusplus
}
#endif

View File

@ -591,6 +591,10 @@ config SOC_LCD_RGB_DATA_WIDTH
int
default 16
config SOC_LCD_SUPPORT_RGB_YUV_CONV
bool
default y
config SOC_RTC_CNTL_CPU_PD_DMA_BUS_WIDTH
int
default 128

View File

@ -235,6 +235,7 @@
#define SOC_LCD_RGB_PANELS (1U) /*!< Support one RGB LCD panel */
#define SOC_LCD_I80_BUS_WIDTH (16) /*!< Intel 8080 bus width */
#define SOC_LCD_RGB_DATA_WIDTH (16) /*!< Number of LCD data lines */
#define SOC_LCD_SUPPORT_RGB_YUV_CONV (1) /*!< Support color format conversion between RGB and YUV */
/*-------------------------- RTC CAPS --------------------------------------*/
#define SOC_RTC_CNTL_CPU_PD_DMA_BUS_WIDTH (128)