mirror of
https://github.com/esphome/esphome.git
synced 2026-05-23 11:16:52 +08:00
[lvgl] Implement rotation with PPA (#15453)
This commit is contained in:
@@ -187,7 +187,6 @@ def final_validation(config_list):
|
||||
for config in config_list:
|
||||
if (pages := config.get(CONF_PAGES)) and all(p[df.CONF_SKIP] for p in pages):
|
||||
raise cv.Invalid("At least one page must not be skipped")
|
||||
uses_rotation = CONF_ROTATION in config
|
||||
for display_id in config[df.CONF_DISPLAYS]:
|
||||
path = global_config.get_path_for_id(display_id)[:-1]
|
||||
display = global_config.get_config_for_path(path)
|
||||
@@ -196,9 +195,9 @@ def final_validation(config_list):
|
||||
"Using lambda: or pages: in display config is not compatible with LVGL"
|
||||
)
|
||||
# treating 0 as false is intended here.
|
||||
if uses_rotation and display.get(CONF_ROTATION):
|
||||
df.LOGGER.warning(
|
||||
"use of 'rotation' in both LVGL and the display config is not recommended"
|
||||
if display.get(CONF_ROTATION):
|
||||
raise cv.Invalid(
|
||||
"use of 'rotation' in the display config is not compatible with LVGL, please set rotation in the LVGL config instead"
|
||||
)
|
||||
if display.get(CONF_AUTO_CLEAR_ENABLED) is True:
|
||||
raise cv.Invalid(
|
||||
@@ -262,6 +261,7 @@ async def to_code(configs):
|
||||
df.add_define("LV_USE_STDLIB_SPRINTF", "LV_STDLIB_CLIB")
|
||||
df.add_define("LV_USE_STDLIB_STRING", "LV_STDLIB_CLIB")
|
||||
df.add_define("LV_USE_STDLIB_MALLOC", "LV_STDLIB_CUSTOM")
|
||||
df.add_define("LV_DEF_REFR_PERIOD", "16")
|
||||
cg.add_define("USE_LVGL")
|
||||
# suppress default enabling of extra widgets
|
||||
# cg.add_define("LV_KCONFIG_PRESENT")
|
||||
@@ -341,7 +341,10 @@ async def to_code(configs):
|
||||
df.LOGGER.info("LVGL will use hardware rotation via display driver")
|
||||
else:
|
||||
rotation_type = RotationType.ROTATION_SOFTWARE
|
||||
df.LOGGER.info("LVGL will use software rotation")
|
||||
if get_esp32_variant() == VARIANT_ESP32P4:
|
||||
df.LOGGER.info("LVGL will use software rotation (PPA accelerated)")
|
||||
else:
|
||||
df.LOGGER.info("LVGL will use software rotation")
|
||||
lv_component = cg.new_Pvariable(
|
||||
config[CONF_ID],
|
||||
displays,
|
||||
|
||||
@@ -158,8 +158,15 @@ void LvglComponent::dump_config() {
|
||||
" Draw rounding: %d",
|
||||
this->width_, this->height_, 100 / this->buffer_frac_, this->rotation_, (int) this->draw_rounding);
|
||||
if (this->rotation_type_ != ROTATION_UNUSED) {
|
||||
ESP_LOGCONFIG(TAG, " Rotation type: %s",
|
||||
this->rotation_type_ == RotationType::ROTATION_SOFTWARE ? "software" : "hardware via display driver");
|
||||
const char *rot_type = "hardware via display driver";
|
||||
if (this->rotation_type_ == RotationType::ROTATION_SOFTWARE) {
|
||||
#ifdef USE_ESP32_VARIANT_ESP32P4
|
||||
rot_type = this->ppa_client_ != nullptr ? "software (PPA accelerated)" : "software";
|
||||
#else
|
||||
rot_type = "software";
|
||||
#endif
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Rotation type: %s", rot_type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,21 +259,120 @@ void LvglComponent::show_prev_page(lv_screen_load_anim_t anim, uint32_t time) {
|
||||
size_t LvglComponent::get_current_page() const { return this->current_page_; }
|
||||
bool LvPageType::is_showing() const { return this->parent_->get_current_page() == this->index; }
|
||||
|
||||
#ifdef USE_ESP32_VARIANT_ESP32P4
|
||||
bool LvglComponent::ppa_rotate_(const lv_color_data *src, lv_color_data *dst, uint16_t width, uint16_t height,
|
||||
uint32_t height_rounded) {
|
||||
ppa_srm_rotation_angle_t angle;
|
||||
uint16_t out_w, out_h;
|
||||
|
||||
// Map ESPHome clockwise display rotation to PPA counter-clockwise angles
|
||||
switch (this->rotation_) {
|
||||
case display::DISPLAY_ROTATION_90_DEGREES:
|
||||
angle = PPA_SRM_ROTATION_ANGLE_270; // 270° CCW = 90° CW
|
||||
out_w = height_rounded;
|
||||
out_h = width;
|
||||
break;
|
||||
case display::DISPLAY_ROTATION_180_DEGREES:
|
||||
angle = PPA_SRM_ROTATION_ANGLE_180;
|
||||
out_w = width;
|
||||
out_h = height;
|
||||
break;
|
||||
case display::DISPLAY_ROTATION_270_DEGREES:
|
||||
angle = PPA_SRM_ROTATION_ANGLE_90; // 90° CCW = 270° CW
|
||||
out_w = height_rounded;
|
||||
out_h = width;
|
||||
break;
|
||||
default:
|
||||
return false; // No rotation needed
|
||||
}
|
||||
|
||||
// Align buffer size to cache line (LV_DRAW_BUF_ALIGN) as required by PPA DMA
|
||||
// the underlying buffer will be large enough as the size is also padded when allocating.
|
||||
size_t out_buf_size = out_w * out_h * sizeof(lv_color_data);
|
||||
out_buf_size = LV_ROUND_UP(out_buf_size, LV_DRAW_BUF_ALIGN);
|
||||
|
||||
ppa_srm_oper_config_t srm_config{};
|
||||
srm_config.in.buffer = src;
|
||||
srm_config.in.pic_w = width;
|
||||
srm_config.in.pic_h = height;
|
||||
srm_config.in.block_w = width;
|
||||
srm_config.in.block_h = height;
|
||||
#if LV_COLOR_DEPTH == 16
|
||||
srm_config.in.srm_cm = PPA_SRM_COLOR_MODE_RGB565;
|
||||
#elif LV_COLOR_DEPTH == 32
|
||||
srm_config.in.srm_cm = PPA_SRM_COLOR_MODE_ARGB8888;
|
||||
#endif
|
||||
srm_config.out.buffer = dst;
|
||||
srm_config.out.buffer_size = out_buf_size;
|
||||
srm_config.out.pic_w = out_w;
|
||||
srm_config.out.pic_h = out_h;
|
||||
#if LV_COLOR_DEPTH == 16
|
||||
srm_config.out.srm_cm = PPA_SRM_COLOR_MODE_RGB565;
|
||||
#elif LV_COLOR_DEPTH == 32
|
||||
srm_config.out.srm_cm = PPA_SRM_COLOR_MODE_ARGB8888;
|
||||
#endif
|
||||
srm_config.rotation_angle = angle;
|
||||
srm_config.scale_x = 1.0f;
|
||||
srm_config.scale_y = 1.0f;
|
||||
srm_config.mode = PPA_TRANS_MODE_BLOCKING;
|
||||
|
||||
esp_err_t ret = ppa_do_scale_rotate_mirror(this->ppa_client_, &srm_config);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "PPA rotation failed: %s", esp_err_to_name(ret));
|
||||
ESP_LOGW(TAG, "PPA SRM: in=%ux%u src=%p, out=%ux%u dst=%p size=%zu, angle=%d", width, height, src, out_w, out_h,
|
||||
dst, out_buf_size, (int) angle);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif // USE_ESP32_VARIANT_ESP32P4
|
||||
|
||||
void LvglComponent::draw_buffer_(const lv_area_t *area, lv_color_data *ptr) {
|
||||
auto width = lv_area_get_width(area);
|
||||
auto height = lv_area_get_height(area);
|
||||
auto height_rounded = (height + this->draw_rounding - 1) / this->draw_rounding * this->draw_rounding;
|
||||
auto x1 = area->x1;
|
||||
auto y1 = area->y1;
|
||||
if (this->rotation_type_ == RotationType::ROTATION_SOFTWARE) {
|
||||
if (this->rotation_type_ == ROTATION_SOFTWARE) {
|
||||
lv_color_data *dst = reinterpret_cast<lv_color_data *>(this->rotate_buf_);
|
||||
#ifdef USE_ESP32_VARIANT_ESP32P4
|
||||
bool ppa_done = this->ppa_client_ != nullptr && this->ppa_rotate_(ptr, dst, width, height, height_rounded);
|
||||
if (!ppa_done)
|
||||
#endif
|
||||
{
|
||||
switch (this->rotation_) {
|
||||
case display::DISPLAY_ROTATION_90_DEGREES:
|
||||
for (lv_coord_t x = height; x-- != 0;) {
|
||||
for (lv_coord_t y = 0; y != width; y++) {
|
||||
dst[y * height_rounded + x] = *ptr++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case display::DISPLAY_ROTATION_180_DEGREES:
|
||||
for (lv_coord_t y = height; y-- != 0;) {
|
||||
for (lv_coord_t x = width; x-- != 0;) {
|
||||
dst[y * width + x] = *ptr++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case display::DISPLAY_ROTATION_270_DEGREES:
|
||||
for (lv_coord_t x = 0; x != height; x++) {
|
||||
for (lv_coord_t y = width; y-- != 0;) {
|
||||
dst[y * height_rounded + x] = *ptr++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
dst = ptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Coordinate adjustments apply regardless of PPA or SW rotation
|
||||
switch (this->rotation_) {
|
||||
case display::DISPLAY_ROTATION_90_DEGREES:
|
||||
for (lv_coord_t x = height; x-- != 0;) {
|
||||
for (lv_coord_t y = 0; y != width; y++) {
|
||||
dst[y * height_rounded + x] = *ptr++;
|
||||
}
|
||||
}
|
||||
y1 = x1;
|
||||
x1 = this->width_ - area->y1 - height;
|
||||
height = width;
|
||||
@@ -274,21 +380,11 @@ void LvglComponent::draw_buffer_(const lv_area_t *area, lv_color_data *ptr) {
|
||||
break;
|
||||
|
||||
case display::DISPLAY_ROTATION_180_DEGREES:
|
||||
for (lv_coord_t y = height; y-- != 0;) {
|
||||
for (lv_coord_t x = width; x-- != 0;) {
|
||||
dst[y * width + x] = *ptr++;
|
||||
}
|
||||
}
|
||||
x1 = this->width_ - x1 - width;
|
||||
y1 = this->height_ - y1 - height;
|
||||
break;
|
||||
|
||||
case display::DISPLAY_ROTATION_270_DEGREES:
|
||||
for (lv_coord_t x = 0; x != height; x++) {
|
||||
for (lv_coord_t y = width; y-- != 0;) {
|
||||
dst[y * height_rounded + x] = *ptr++;
|
||||
}
|
||||
}
|
||||
x1 = y1;
|
||||
y1 = this->height_ - area->x1 - width;
|
||||
height = width;
|
||||
@@ -296,7 +392,6 @@ void LvglComponent::draw_buffer_(const lv_area_t *area, lv_color_data *ptr) {
|
||||
break;
|
||||
|
||||
default:
|
||||
dst = ptr;
|
||||
break;
|
||||
}
|
||||
ptr = dst;
|
||||
@@ -664,6 +759,15 @@ void LvglComponent::setup() {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
#ifdef USE_ESP32_VARIANT_ESP32P4
|
||||
ppa_client_config_t ppa_config{};
|
||||
ppa_config.oper_type = PPA_OPERATION_SRM;
|
||||
ppa_config.max_pending_trans_num = 1;
|
||||
if (ppa_register_client(&ppa_config, &this->ppa_client_) != ESP_OK) {
|
||||
ESP_LOGW(TAG, "PPA client registration failed, using software rotation");
|
||||
this->ppa_client_ = nullptr;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if (this->draw_start_callback_ != nullptr) {
|
||||
lv_display_add_event_cb(this->disp_, render_start_cb, LV_EVENT_RENDER_START, this);
|
||||
@@ -804,7 +908,7 @@ static unsigned cap_bits = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT; // NOLINT
|
||||
|
||||
static void *lv_alloc_draw_buf(size_t size, bool internal) {
|
||||
void *buffer;
|
||||
size = ((size + LV_DRAW_BUF_ALIGN - 1) / LV_DRAW_BUF_ALIGN) * LV_DRAW_BUF_ALIGN;
|
||||
size = LV_ROUND_UP(size, LV_DRAW_BUF_ALIGN);
|
||||
buffer = heap_caps_aligned_alloc(LV_DRAW_BUF_ALIGN, size, internal ? MALLOC_CAP_8BIT : cap_bits); // NOLINT
|
||||
if (buffer == nullptr)
|
||||
ESP_LOGW(esphome::lvgl::TAG, "Failed to allocate %zu bytes for %sdraw buffer", size, internal ? "internal " : "");
|
||||
|
||||
@@ -26,6 +26,10 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#ifdef USE_ESP32_VARIANT_ESP32P4
|
||||
#include "driver/ppa.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_FONT
|
||||
#include "esphome/components/font/font.h"
|
||||
#endif // USE_LVGL_FONT
|
||||
@@ -229,6 +233,9 @@ class LvglComponent : public PollingComponent {
|
||||
display::DisplayRotation get_rotation() const { return this->rotation_; }
|
||||
void rotate_coordinates(int32_t &x, int32_t &y) const;
|
||||
|
||||
uint16_t get_width() const { return lv_display_get_horizontal_resolution(this->disp_); }
|
||||
uint16_t get_height() const { return lv_display_get_vertical_resolution(this->disp_); }
|
||||
|
||||
protected:
|
||||
void set_resolution_() const;
|
||||
void draw_end_();
|
||||
@@ -238,6 +245,10 @@ class LvglComponent : public PollingComponent {
|
||||
|
||||
void write_random_();
|
||||
void draw_buffer_(const lv_area_t *area, lv_color_data *ptr);
|
||||
#ifdef USE_ESP32_VARIANT_ESP32P4
|
||||
bool ppa_rotate_(const lv_color_data *src, lv_color_data *dst, uint16_t width, uint16_t height,
|
||||
uint32_t height_rounded);
|
||||
#endif
|
||||
void flush_cb_(lv_display_t *disp_drv, const lv_area_t *area, uint8_t *color_p);
|
||||
|
||||
std::vector<display::Display *> displays_{};
|
||||
@@ -266,6 +277,9 @@ class LvglComponent : public PollingComponent {
|
||||
void *rotate_buf_{};
|
||||
display::DisplayRotation rotation_{display::DISPLAY_ROTATION_0_DEGREES};
|
||||
RotationType rotation_type_;
|
||||
#ifdef USE_ESP32_VARIANT_ESP32P4
|
||||
ppa_client_handle_t ppa_client_{};
|
||||
#endif
|
||||
};
|
||||
|
||||
class IdleTrigger : public Trigger<> {
|
||||
|
||||
@@ -21,7 +21,7 @@ binary_sensor:
|
||||
ignore_strapping_warning: true
|
||||
|
||||
display:
|
||||
- platform: ili9xxx
|
||||
- platform: mipi_spi
|
||||
spi_id: spi_bus
|
||||
model: st7789v
|
||||
id: second_display
|
||||
@@ -41,7 +41,7 @@ display:
|
||||
invert_colors: false
|
||||
update_interval: never
|
||||
|
||||
- platform: ili9xxx
|
||||
- platform: mipi_spi
|
||||
spi_id: spi_bus
|
||||
model: st7789v
|
||||
id: tft_display
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
display:
|
||||
- platform: mipi_dsi
|
||||
model: WAVESHARE-ESP32-P4-WIFI6-TOUCH-LCD-3.4C
|
||||
lvgl:
|
||||
byte_order: little_endian
|
||||
rotation: 90
|
||||
|
||||
psram:
|
||||
|
||||
esp_ldo:
|
||||
- channel: 3
|
||||
voltage: 2.5V
|
||||
Reference in New Issue
Block a user