CrowPanel 5.0, ESPHome 2026.2 and weird glitches while updating values in LVGL

I’m trying to build a device using CrowPanel 5.0 that will allow me to control two pumps based on temperature sensors.

Sadly, I’m unable to get stable LVGL results, as the screen is jumping every time the sensor value changes.

I’ve searched over the forum and on ESPHome docs, even tried using AI to get stable, non-jumping content, but even I’m not using any animations or graphics I’m still getting those jumps.

Has anyone successfully used CrowPanels from Elecrow with ESPHome? Are there any special settings I should apply in esp32, esphome, psram sections, or in the display?

Below is my entire yaml code:

substitutions:
  device_name: thermo
  device_friendly_name: Termostat
  color_grey: "0xa5a5a5"
  color_warning: "0xd2691e"
  color_dark_grey: "0x282828"
  color_darker_grey: "0x212121"
  color_accent: "0x18bcf2"
  color_white: "0xFFFFFF"
  color_black: "0x000000"
  # Material Design Icons
  mdi_wifi: "\U000F05A9"
  mdi_wifi_off: "\U000F05AA"

  mdi_cog: "\U000F0493"
  mdi_home: "\U000F02DC"
  mdi_plus: "\U000F11EC"
  mdi_minus: "\U000F1639"
  mdi_delta: "\U000F01C2"

esphome:
  name: ${device_name}
  friendly_name: ${device_friendly_name}
  platformio_options:
    board_build.esp-idf.memory_type: qio_opi
    board_build.flash_mode: dio
  on_boot:
    priority: 600
    then:
    #- delay: 1s
    #- component.update: temp_cwu
      - delay: 1s
      - script.execute: turn_on_screen_light
      - delay: 3s
      - script.execute: ui_show

esp32:
  #board: esp32-s3-devkitc-1
  variant: esp32s3
  framework:
    type: esp-idf
    sdkconfig_options:
      CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240: y
      CONFIG_ESP32S3_DATA_CACHE_64KB: y
      CONFIG_SPIRAM_FETCH_INSTRUCTIONS: y
      CONFIG_SPIRAM_RODATA: y

psram:
  mode: octal
  speed: 80MHz

logger:

# Enable Home Assistant API
api:
  encryption:
    key: "xxx"
  on_client_connected:
    - if:
        condition:
          lambda: 'return (0 == client_info.find("Home Assistant "));'
        then:
          - lvgl.label.update:
              id: ha_status_icon_label
              text_color: ${color_white}
              text: ${mdi_wifi}
          - script.execute: update_date_time_label
  on_client_disconnected:
    - if:
        condition:
          lambda: 'return (0 == client_info.find("Home Assistant "));'
        then:
          - lvgl.label.update:
              id: ha_status_icon_label
              text_color: ${color_warning}
              text: ${mdi_wifi_off}
          - lvgl.label.update:
              id: date_time_label
              text: "Disconnected"

ota:
  - platform: esphome
    password: "xxx"

wifi:
  networks:
    - ssid: xxx
      password: xxx
    - ssid: xxx
      password: xxx

  ap:
    ssid: "Thermo Hotspot"
    password: "xxx"

captive_portal:
    


output:
  - platform: ledc
    pin: 2
    frequency: 1220
    id: backlight_pwm

light:
  - platform: monochromatic # Define a monochromatic, dimmable light for the backlight
    output: backlight_pwm
    name: ${device_name} Display Backlight
    id: backlight
    icon: mdi:brightness-percent
    default_transition_length: 500ms
    restore_mode: RESTORE_AND_ON
#    on_turn_on: 
#      then:
#        - script.execute: turn_on_screen_light
    on_turn_off: 
      then:
        - lvgl.pause:

display:
  - platform: rpi_dpi_rgb
    id: main_display
    color_order: RGB
    invert_colors: true
    update_interval: never
    auto_clear_enabled: false # takes 2.8 seconds to clear the display

    #pclk_frequency: 15MHz          # Elecrow: cfg.freq_write = 15000000 :contentReference[oaicite:4]{index=4}
    pclk_inverted: true            # Elecrow: cfg.pclk_active_neg = 1 :contentReference[oaicite:5]{index=5}

    hsync_front_porch: 8           # Elecrow :contentReference[oaicite:6]{index=6}
    hsync_pulse_width: 4           # Elecrow :contentReference[oaicite:7]{index=7}
    hsync_back_porch: 43           # Elecrow :contentReference[oaicite:8]{index=8}
    vsync_front_porch: 8           # Elecrow :contentReference[oaicite:9]{index=9}
    vsync_pulse_width: 4           # Elecrow :contentReference[oaicite:10]{index=10}
    vsync_back_porch: 12           # Elecrow :contentReference[oaicite:11]{index=11}

    dimensions:
      width: 800
      height: 480
    de_pin: 40
    hsync_pin: 39
    vsync_pin: 41
    pclk_pin: 0
    pclk_frequency: 12MHz
    data_pins:
      red:   [45, 48, 47, 21, 14]
      green: [5, 6, 7, 15, 16, 4]
      blue:  [8, 3, 46, 9, 1]

i2c:
  sda: GPIO19
  scl: GPIO20
  scan: true

one_wire:
  - platform: gpio
    pin: GPIO38


# DS18B20 sensors
sensor:
  - platform: dallas_temp
    id: temp_cwu
    name: "Temperatura CWU"
    address: 0xc80661d4465cae28
    resolution: 11
    update_interval: 10s
    unit_of_measurement: "°C"
    icon: "mdi:thermometer-water"
    device_class: "temperature"
    state_class: "measurement"
    on_value:
      - lvgl.label.update:
          id: val_text
          text:
            format: "%.1f °C"
            args: [ 'x' ]
    
switch:
  - platform: gpio
    pin: GPIO43
    id: relay_cwu
    name: "Pompa CWU"

  - platform: gpio
    pin: GPIO44
    id: relay_co
    name: "Pompa CO"


number:
  # Display Backlight Brightness Adjust
  - platform: template
    name: "BackLight Brightness"
    optimistic: true
    id: backlight_brightness
    icon: mdi:brightness-percent
    unit_of_measurement: "%"
    initial_value: 80
    restore_value: true
    min_value: 5
    max_value: 100
    step: 5
    mode: slider
  # Screen Timeout Adjust
  - platform: template
    name: "Screen Timeout"
    optimistic: true
    id: screen_timeout
    icon: mdi:timer
    unit_of_measurement: "s"
    initial_value: 300
    restore_value: true    
    min_value: 10
    max_value: 300
    step: 5
    mode: slider

  - platform: template
    name: "CWU: delta min"
    id: delta_cwu_min
    unit_of_measurement: "°C"
    min_value: 0
    max_value: 30
    step: 1
    optimistic: true
    restore_value: true
    initial_value: 2

climate:
  - platform: thermostat
    id: cl_co
    name: "CO (pokój)"
    # tymczasowo użyj temp_cwu jako sensor, dopóki nie dodasz czujnika pokoju
    # docelowo: sensor: temp_pokoj
    sensor: temp_cwu
    min_heating_off_time: 120s
    min_heating_run_time: 120s
    min_idle_time: 60s
    heat_action:
      - switch.turn_on: relay_co
    idle_action:
      - switch.turn_off: relay_co

  - platform: thermostat
    id: cl_cwu
    name: "CWU (bojler)"
    sensor: temp_cwu
    min_heating_off_time: 60s
    min_heating_run_time: 60s
    min_idle_time: 30s
    heat_action:
      - switch.turn_on: relay_cwu
    idle_action:
      - switch.turn_off: relay_cwu




touchscreen:
  platform: gt911
  on_touch:
    - script.execute: turn_on_screen_light

globals:
  - id: random_int_var
    type: int
    restore_value: no
    initial_value: '0'

time:
  - platform: homeassistant
    id: hass_time
    on_time:
      - seconds: /1  # Update every second
        then:
          - script.execute: update_date_time_label

binary_sensor:
  - platform: lvgl
    widget: button1
    name: Button
    on_press: # genrate a random number between -10 and +10 and update meter
      then:
        # - lambda: |-
        #     id(random_int_var) = (rand() % 20) - 10;
        # - lvgl.indicator.update:
        #     id: power_meter_input
        #     value: !lambda return id(random_int_var);
        # - lvgl.label.update:
        #     id: power_kw
        #     text:
        #       format: "%dkW"
        #       args: [ 'id(random_int_var)' ]

script:
  # Screen Light Script
  - id: turn_on_screen_light
    mode: restart
    then:
      - lvgl.resume:
      - lambda: |-
          // Force LVGL to redraw the whole active screen to clear "snow"/artifacts
          lv_obj_invalidate(lv_scr_act());
          lv_refr_now(NULL);
      - light.turn_on:
          id: backlight
          brightness: !lambda return id(backlight_brightness).state / 100;       
          transition_length: 1s
  - id: idle_screen_off
    mode: restart
    then:
      - light.turn_on:
            id: backlight
            brightness: 50%
            transition_length: 5s
      - delay: 30s
      - light.turn_on:
            id: backlight
            brightness: 35%
            transition_length: 5s
      - delay: 30s
      - light.turn_off:
            id: backlight
      - lvgl.pause:
          show_snow: true
  - id: update_date_time_label
    then:
      - lvgl.label.update:
          id: date_time_label
          text: !lambda |-
            static const char * const day_names[] = {"NIE", "PON", "WTO", "ŚRO", "CZW", "PIĄ", "SOB"};
            static char buf[14];  // Buffer for "XXX 12:59:59"
            auto now = id(hass_time).now();
            snprintf(buf, sizeof(buf), "%s %02d:%02d:%02d", 
                    day_names[now.day_of_week-1], 
                    now.hour,
                    now.minute,
                    now.second);
            return buf;

  - id: ui_show
    mode: restart
    then:
      - lvgl.widget.show: header_bar
      - lvgl.page.show: main_page
      - delay: 50ms
      - lvgl.widget.hide: boot_screen

  - id: ui_refresh_settings_values
    mode: restart
    then:
      - lvgl.label.update:
          id: lbl_co_value
          text: !lambda |-
            static char buf[8];
            snprintf(buf, sizeof(buf), "%.0f", id(cl_co).target_temperature);
            return buf;
      - lvgl.label.update:
          id: lbl_cwu_value
          text: !lambda |-
            static char buf[8];
            snprintf(buf, sizeof(buf), "%.0f", id(cl_cwu).target_temperature);
            return buf;
      - lvgl.label.update:
          id: lbl_delta_value
          text: !lambda |-
            static char buf[8];
            snprintf(buf, sizeof(buf), "%.0f", id(delta_cwu_min).state);
            return buf;

font:
  - file: 'fonts/roboto-400-v1.ttf'
    glyphs: ' 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZŚĄ°.:/+-%–'
    id: roboto_med
    size: 24
    bpp: 4
    extras:
      - file: "fonts/materialdesignicons-webfont.ttf"
        glyphs: 
          - ${mdi_wifi}
          - ${mdi_wifi_off}
          - ${mdi_cog}
          - ${mdi_home}
          - ${mdi_plus}
          - ${mdi_minus}
          - ${mdi_delta}

  - file: 'fonts/roboto-400-v1.ttf'
    glyphs: ' -0123456789.°CWUO'
    id: roboto_huge
    size: 50
    bpp: 4
    extras:
      - file: "fonts/materialdesignicons-webfont.ttf"
        glyphs: 
          - ${mdi_delta}

interval:
  - interval: 2s
    then:
      - script.execute: ui_refresh_settings_values

lvgl:
  on_idle:
  - timeout: !lambda return id(screen_timeout).state * 1000;
    then:
      - script.execute: idle_screen_off

  color_depth: 16
  bg_color: 0xF6F6F6
  text_color: ${color_white}
  scrollbar_mode: "OFF"
  default_font: roboto_med
  align: center
  buffer_size: 12%

  theme:
    label:
      text_font: roboto_med # set all your labels to use your custom defined font
    button:
      bg_color: 0x2F8CD8
      bg_grad_color: 0x005782
      bg_grad_dir: VER
      bg_opa: COVER
      border_color: 0x0077b3
      border_width: 1
      text_color: 0xFFFFFF
      pressed: # set some button colors to be different in pressed state
        bg_color: 0x006699
        bg_grad_color: 0x00334d
      checked: # set some button colors to be different in checked state
        bg_color: 0x1d5f96
        bg_grad_color: 0x03324A
        text_color: 0xfff300
    buttonmatrix:
      bg_opa: TRANSP
      border_color: 0x0077b3
      border_width: 0
      text_color: 0xFFFFFF
      pad_all: 0
      items: # set all your buttonmatrix buttons to use your custom defined styles and font
        bg_color: 0x2F8CD8
        bg_grad_color: 0x005782
        bg_grad_dir: VER
        bg_opa: COVER
        border_color: 0x0077b3
        border_width: 1
        text_color: 0xFFFFFF
        text_font: roboto_med
        pressed:
          bg_color: 0x006699
          bg_grad_color: 0x00334d
        checked:
          bg_color: 0x1d5f96
          bg_grad_color: 0x03324A
          text_color: 0x005580
    switch:
      bg_color: 0xC0C0C0
      bg_grad_color: 0xb0b0b0
      bg_grad_dir: VER
      bg_opa: COVER
      checked:
        bg_color: 0x1d5f96
        bg_grad_color: 0x03324A
        bg_grad_dir: VER
        bg_opa: COVER
      knob:
        bg_color: 0xFFFFFF
        bg_grad_color: 0xC0C0C0
        bg_grad_dir: VER
        bg_opa: COVER
    slider:
      border_width: 1
      border_opa: 15%
      bg_color: 0xcccaca
      bg_opa: 15%
      indicator:
        bg_color: 0x1d5f96
        bg_grad_color: 0x03324A
        bg_grad_dir: VER
        bg_opa: COVER
      knob:
        bg_color: 0x2F8CD8
        bg_grad_color: 0x005782
        bg_grad_dir: VER
        bg_opa: COVER
        border_color: 0x0077b3
        border_width: 1
        text_color: 0xFFFFFF

  style_definitions:
    - id: header_footer
      bg_color: 0x2F8CD8
      bg_grad_color: 0x005782
      bg_grad_dir: VER
      bg_opa: COVER
      border_opa: TRANSP
      radius: 0
      pad_all: 0
      pad_row: 0
      pad_column: 0
      border_color: 0x0077b3
      text_color: 0xFFFFFF
      width: 100%
      height: 50

    - id: meter_style
      border_width: 0
      outline_width: 0
      align: center
      bg_color: 0

    - id: title_style
      text_font: MONTSERRAT_40
      align: center
      text_color: 0xFFFFFF
      bg_opa: TRANSP
      bg_color: 0
      radius: 4
      pad_all: 2

    - id: detail_style
      text_font: MONTSERRAT_18
      align: center
      text_color: 0xFFFFFF
      bg_opa: TRANSP
      bg_color: 0
      radius: 4
      pad_all: 2

  top_layer:
    widgets:
      - obj:
          id: header_bar
          hidden: true
          align: top_mid
          width: 100%
          height: 60
          styles: header_footer
          widgets:
            - label:
                id: date_time_label
                text: "Connecting..."
                align: left_mid
                x: 10
                text_color: ${color_white}
            - label:
                id: title_label
                text: "USTAWIENIA"
                align: center
                text_color: ${color_white}
                hidden: true
            - label:
                id: ha_status_icon_label
                text: ${mdi_wifi_off}  # Ikona statusu połączenia
                align: right_mid
                x: -10
                text_color: ${color_warning}

            - button:
                id: btn_home
                hidden: true
                align: right_mid
                x: -120
                widgets:
                - label:
                    align: center
                    text: ${mdi_home}  # Ikona strzałki w lewo
                on_press:
                  then:
                    - lvgl.widget.hide: btn_home
                    - lvgl.widget.show: btn_settings
                    - lvgl.page.show: main_page
                    - lvgl.widget.hide: title_label

            - button:
                id: btn_settings
                align: right_mid
                x: -60
                widgets:
                - label:
                    align: center
                    text: ${mdi_cog}  # Ikona trybika
                on_press:
                  then:
                    - lvgl.widget.show: btn_home
                    - lvgl.widget.hide: btn_settings
                    - lvgl.page.show: settings_page
                    - lvgl.widget.show: title_label
                    - script.execute: ui_refresh_settings_values

      - obj:
          id: boot_screen
          x: 0
          y: 0
          width: 100%
          height: 100%
          bg_color: ${color_darker_grey}
          bg_opa: COVER
          radius: 0
          pad_all: 0
          border_width: 0
          widgets:
            - spinner:
                align: CENTER
                height: 50
                width: 50
                spin_time: 1s
                arc_length: 60deg
                arc_width: 8
                indicator:
                  arc_color: ${color_accent}
                  arc_width: 8

  pages:
    - id: main_page
      widgets:
        - obj:
            id: main_content
            x: 0
            y: 60
            width: 800
            height: 420
            pad_all: 0
            border_width: 0
            bg_opa: TRANSP
            widgets:
              - button: # Button1
                  id: button1
                  height: 100
                  width: 200
                  x: 20
                  y: 40
                  border_width: 0
                  outline_width: 0
                  align: TOP_LEFT
                  checkable: true
                  widgets:
                  - label:
                      align: center
                      text: "CWU"
                  on_press:
                    then:
                      - switch.toggle: relay_cwu
              - button: # Button2
                  id: button2
                  height: 100
                  width: 200
                  x: 20
                  y: 160
                  border_width: 0
                  outline_width: 0
                  align: TOP_LEFT
                  checkable: true
                  widgets:
                  - label:
                      align: center
                      text: "CO"
                  on_press:
                    then:
                      - switch.toggle: relay_co
              - label:
                  id: val_text
                  text_font: roboto_huge
                  align: TOP_LEFT
                  text: "--  °C"
                  x: 20
                  y: 280
                  height: 100
                  width: 200

    - id: settings_page
      widgets:
        - obj:
            width: 165
            height: 100
            x: 40
            y: 100
            border_opa: TRANSP
            bg_opa: TRANSP
            widgets:
              - label:
                  text: "CO"
                  align: center
                  text_font: roboto_huge
                  text_color: ${color_black}

        - button:
            id: b2
            width: 165
            height: 100
            x: 225
            y: 100
            widgets:
              - label:
                  align: CENTER
                  text: ${mdi_minus}
            on_press:
              then:
                - climate.control:
                    id: cl_co
                    target_temperature: !lambda "return id(cl_co).target_temperature - 1.0;"
                - script.execute: ui_refresh_settings_values

        - button:
            id: b3
            width: 165
            height: 100
            x: 410
            y: 100
            bg_color: 0x2F8CD8
            bg_grad_color: 0x005782
            bg_grad_dir: VER
            bg_opa: COVER
            border_color: 0x0077b3
            border_width: 1
            radius: 4
            widgets:
              - label:
                  id: lbl_co_value
                  align: CENTER
                  text: "--"

        - button:
            id: b4
            width: 165
            height: 100
            x: 595
            y: 100
            widgets:
              - label:
                  align: CENTER
                  text: ${mdi_plus}
            on_press:
              then:
                - climate.control:
                    id: cl_co
                    target_temperature: !lambda "return id(cl_co).target_temperature + 1.0;"
                - script.execute: ui_refresh_settings_values


        - obj:
            width: 165
            height: 100
            x: 40
            y: 220
            border_opa: TRANSP
            bg_opa: TRANSP
            widgets:
              - label:
                  text: "CWU"
                  align: center
                  text_font: roboto_huge
                  text_color: ${color_black}

        - button:
            id: b6
            width: 165
            height: 100
            x: 225
            y: 220
            widgets:
              - label:
                  align: CENTER
                  text: ${mdi_minus}

        - button:
            id: b7
            width: 165
            height: 100
            x: 410
            y: 220
            bg_color: 0x2F8CD8
            bg_grad_color: 0x005782
            bg_grad_dir: VER
            bg_opa: COVER
            border_color: 0x0077b3
            border_width: 1
            radius: 4
            widgets:
              - label:
                  id: lbl_cwu_value
                  align: CENTER
                  text: "--"

        - button:
            id: b8
            width: 165
            height: 100
            x: 595
            y: 220
            widgets:
              - label:
                  align: CENTER
                  text: ${mdi_plus}

        - obj:
            width: 165
            height: 100
            x: 40
            y: 340
            border_opa: TRANSP
            bg_opa: TRANSP
            widgets:
              - label:
                  text: ${mdi_delta}
                  align: center
                  text_font: roboto_huge
                  text_color: ${color_black}

        - button:
            id: b10
            width: 165
            height: 100
            x: 225
            y: 340
            widgets:
              - label:
                  align: CENTER
                  text: ${mdi_minus}
            on_press:
              then:
                - number.set:
                    id: delta_cwu_min
                    value: !lambda "return id(delta_cwu_min).state - 1.0;"
                - script.execute: ui_refresh_settings_values

        - button:
            id: b11
            width: 165
            height: 100
            x: 410
            y: 340
            bg_color: 0x2F8CD8
            bg_grad_color: 0x005782
            bg_grad_dir: VER
            bg_opa: COVER
            border_color: 0x0077b3
            border_width: 1
            radius: 4
            widgets:
              - label:
                  id: lbl_delta_value
                  align: CENTER
                  text: "--"

        - button:
            id: b12
            width: 165
            height: 100
            x: 595
            y: 340
            widgets:
              - label:
                  align: CENTER
                  text: ${mdi_plus}
            on_press:
              then:
                - number.set:
                    id: delta_cwu_min
                    value: !lambda "return id(delta_cwu_min).state + 1.0;"
                - script.execute: ui_refresh_settings_values

The one-wire component blocks interrupts, which is what causes the glitches on the display.

That component (1-wire) should be rewritten to use hardware timing, currently it uses busy-loops with interrupts turned off.

Thank you for the response.
At least now I know there is nothing wrong with my code.

@clydebarrow I have something working quite well: Comparing esphome:dev...Misiu:one_wire · esphome/esphome · GitHub

basically I created a separate platform called esp32_rmt_one_wire

it can be tested by using:

external_components:
  - source:
      type: git
      url: https://github.com/Misiu/esphome
      ref: one_wire
    components: [esp32_rmt_one_wire, gpio]
    refresh: 1h

esp32_rmt_one_wire:
  - platform: gpio
    pin: GPIO38

But I’m not sure if a separate platform is needed, or maybe another way is preferred.

I’d like to move this forward, so this will be merged into ESPHome, but as I didn’t contribute to the project in the past, this might take ages for me.

Do you think this should be a separate platform? Id like to have your opinion before I submit this as PR.

I think it should replace the existing implementation, which has fundamental problems. The RMT should be a good way to do it.

Please go ahead with raising a PR, ask any questions you need (the Discord #dev channel is the best place, will get the best and fastest responses.)