ESP32 Touchscreen

I purchased one of these ESP32 Touchscreens to put a small display in a few rooms. I’ve used ESPHome for several small devices but this is my first venture into using a display with ESPHome.

So I’ve borrowed some code from examples I found and have the device setup with a basic configuration that seems to mostly work except I can’t get anything to print on the screen. The backlight comes on after boot and the “auto on-dim-off” script works when the screen is tapped. Able to toggle the LED on the back. The touch screen calibration works.

This seems to be the spec sheet for the part number (ESP32-2432S028) printed on the device. Pretty sure I have the pin assignments correct based on the IO Table .

But instead of a friendly “Hello World” greeting, I just get this.
Am I missing something or do I possibly have a bad display?
Debug log included below the code.
image

substitutions:
  name: "bedroom-display"
  friendly_name: bedroom-display

esphome:
  name: ${name}
  friendly_name: ${friendly_name}
  min_version: 2024.6.0
  name_add_mac_suffix: false
  project:
    name: esphome.web
    version: dev

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:
  logs:
    component: ERROR

# Enable Home Assistant API
api:
  encryption: 
    key: !secret haapi_key

# Allow Over-The-Air updates
ota:
  - platform: esphome
    password: !secret ota_password

# Allow provisioning Wi-Fi via serial
improv_serial:

# Allow provisioning Wi-Fi via serial
#improv_serial:

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  manual_ip:
    static_ip: 10.0.0.135
    gateway: 10.0.0.1
    subnet: 255.255.255.0
    dns1: 75.75.75.75
    dns2: 75.75.76.76
  
  fast_connect: off

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Bedroom Display"
    password: !secret ap_password

# In combination with the `ap` this allows the user
# to provision wifi credentials to the device via WiFi AP.
# captive_portal:

dashboard_import:
  package_import_url: github://esphome/example-configs/esphome-web/esp32.yaml@main
  import_full_config: true

# Sets up Bluetooth LE (Only on ESP32) to allow the user
# to provision wifi credentials to the device.
# esp32_improv:
#  authorizer: none

# To have a "next url" for improv serial
web_server:

time:
  - platform: homeassistant
    id: esptime

spi:
  - id: lcd
    clk_pin: GPIO14
    mosi_pin: GPIO13
    miso_pin: GPIO12
  - id: touch
    clk_pin: GPIO25
    mosi_pin: GPIO32
    miso_pin: GPIO39

i2c:
  sda: GPIO27
  scl: GPIO22
  scan: true
  id: i2c_bus_a

color:
  - id: my_red
    red: 100%
    green: 0%
    blue: 0%
  - id: my_yellow
    red: 100%
    green: 100%
    blue: 0%
  - id: my_green
    red: 0%
    green: 100%
    blue: 0%
  - id: my_blue
    red: 0%
    green: 0%
    blue: 100%
  - id: my_gray
    red: 50%
    green: 50%
    blue: 50%
  - id: my_orange
    red: 100%
    green: 50%
    blue: 0%
  - id: my_teal
    red: 0%
    green: 100%
    blue: 100%
  - id: my_white
    red: 100%
    green: 100%
    blue: 100%
  - id: my_black
    red: 0%
    green: 0%
    blue: 0%

#Font declaration
font:
  - file: 'gfonts://Roboto'
    id: roboto_20
    size: 20

display:
  - platform: ili9xxx
    model: ili9341
    spi_id: lcd
    dc_pin: GPIO02
    lambda: |-
      it.print(0, 0, id(roboto_20), id(my_black), "Hello World");

# Define pins for backlight display and rear LED
output:
  - platform: ledc
    pin: GPIO21
    id: backlight
  - platform: ledc
    id: output_red
    pin: GPIO4
    inverted: true
  - platform: ledc
    id: output_green
    pin: GPIO16
    inverted: true
  - platform: ledc
    id: output_blue
    pin: GPIO17
    inverted: true
  # speaker P4
#  - platform: ledc
#    pin: GPIO26
#    id: rtttl_out

# Define a monochromatic, dimmable light for the backlight
light:
  - platform: monochromatic
    output: backlight
    name: "Display Backlight"
    id: back_light
    restore_mode: ALWAYS_ON
  - platform: rgb
    name: LED
    red: output_red
    id: led
    green: output_green
    blue: output_blue
    restore_mode: ALWAYS_OFF

touchscreen:
  platform: xpt2046
  spi_id: touch
  cs_pin: 33
  interrupt_pin: 36
  update_interval: 50ms
  # report_interval: 1s
  threshold: 400
  calibration:
    x_min: 288
    x_max: 3779
    y_min: 193
    y_max: 3647
  on_touch:
    - script.stop: backlight_timer
    - script.execute: backlight_timer
  #Uncomment for calibrations
#    - lambda: |-
#          ESP_LOGI("cal", "x=%d, y=%d, x_raw=%d, y_raw=%0d",
#              touch.x,
#              touch.y,
#              touch.x_raw,
#              touch.y_raw
#              );
  #Uncomment for calibrations

#This is the script for turn off the backlight after some seconds of inactivity
script:
  id: backlight_timer
  then:
  - light.turn_on:
      id: back_light
      brightness: 100%
  - delay: 5s
  - light.turn_on:
      id: back_light
      brightness: 50%
  - delay: 10s
  - light.turn_off: back_light

INFO Upload took 9.33 seconds, waiting for result...
INFO OTA successful
INFO Successfully uploaded program.
INFO Starting log output from xxx using esphome API
INFO Successfully connected to bedroom-display @ xxx in 7.184s
INFO Successful handshake with bedroom-display @ xxx in 0.099s
[13:29:24][I][app:100]: ESPHome version 2024.8.3 compiled on Sep 14 2024, 13:28:41
[13:29:24][I][app:102]: Project esphome.web version dev
[13:29:24][C][wifi:600]: WiFi:
[13:29:24][C][wifi:428]:   Local MAC: xxx
[13:29:24][C][wifi:433]:   SSID: xxx
[13:29:24][C][wifi:436]:   IP Address: xxx
[13:29:24][C][wifi:440]:   BSSID: xxx
[13:29:24][C][wifi:441]:   Hostname: 'bedroom-display'
[13:29:24][C][wifi:443]:   Signal strength: -29 dB ▂▄▆█
[13:29:24][C][wifi:447]:   Channel: 11
[13:29:24][C][wifi:448]:   Subnet: 255.255.255.0
[13:29:24][C][wifi:449]:   Gateway: xxx
[13:29:24][C][wifi:450]:   DNS1: 75.75.75.75
[13:29:24][C][wifi:451]:   DNS2: 75.75.76.76
[13:29:24][C][logger:185]: Logger:
[13:29:24][C][logger:186]:   Level: DEBUG
[13:29:24][C][logger:188]:   Log Baud Rate: 115200
[13:29:24][C][logger:189]:   Hardware UART: UART0
[13:29:24][C][logger:193]:   Level for 'component': ERROR
[13:29:24][C][spi:064]: SPI bus:
[13:29:24][C][spi:065]:   CLK Pin: GPIO14
[13:29:24][C][spi:066]:   SDI Pin: GPIO12
[13:29:24][C][spi:067]:   SDO Pin: GPIO13
[13:29:24][C][spi:072]:   Using HW SPI: SPI
[13:29:24][C][spi:064]: SPI bus:
[13:29:24][C][spi:065]:   CLK Pin: GPIO25
[13:29:24][C][spi:066]:   SDI Pin: GPIO39
[13:29:24][C][spi:067]:   SDO Pin: GPIO32
[13:29:24][C][spi:072]:   Using HW SPI: HSPI
[13:29:24][C][i2c.arduino:071]: I2C Bus:
[13:29:24][C][i2c.arduino:072]:   SDA Pin: GPIO27
[13:29:24][C][i2c.arduino:073]:   SCL Pin: GPIO22
[13:29:24][C][i2c.arduino:074]:   Frequency: 50000 Hz
[13:29:24][C][i2c.arduino:086]:   Recovery: bus successfully recovered
[13:29:24][I][i2c.arduino:096]: Results from i2c bus scan:
[13:29:24][I][i2c.arduino:098]: Found no i2c devices!
[13:29:24][C][ili9xxx:094]: ili9xxx
[13:29:24][C][ili9xxx:094]:   Rotations: 0 °
[13:29:24][C][ili9xxx:094]:   Dimensions: 240px x 320px
[13:29:24][C][ili9xxx:095]:   Width Offset: 0
[13:29:24][C][ili9xxx:096]:   Height Offset: 0
[13:29:24][C][ili9xxx:105]:   Color mode: 8bit 332 mode
[13:29:24][C][ili9xxx:111]:   Data rate: 40MHz
[13:29:24][C][ili9xxx:115]:   DC Pin: GPIO2
[13:29:24][C][ili9xxx:117]:   Color order: BGR
[13:29:24][C][ili9xxx:118]:   Swap_xy: NO
[13:29:24][C][ili9xxx:119]:   Mirror_x: YES
[13:29:24][C][ili9xxx:120]:   Mirror_y: NO
[13:29:24][C][ili9xxx:125]:   Update Interval: 1.0s
[13:29:24][C][ledc.output:176]: LEDC Output:
[13:29:24][C][ledc.output:177]:   Pin GPIO21
[13:29:24][C][ledc.output:178]:   LEDC Channel: 0
[13:29:24][C][ledc.output:179]:   PWM Frequency: 1000.0 Hz
[13:29:24][C][ledc.output:180]:   Phase angle: 0.0°
[13:29:24][C][ledc.output:181]:   Bit depth: 16
[13:29:24][C][ledc.output:176]: LEDC Output:
[13:29:24][C][ledc.output:177]:   Pin GPIO4
[13:29:24][C][ledc.output:178]:   LEDC Channel: 1
[13:29:24][C][ledc.output:179]:   PWM Frequency: 1000.0 Hz
[13:29:24][C][ledc.output:180]:   Phase angle: 0.0°
[13:29:24][C][ledc.output:181]:   Bit depth: 16
[13:29:24][C][ledc.output:176]: LEDC Output:
[13:29:24][C][ledc.output:177]:   Pin GPIO16
[13:29:24][C][ledc.output:178]:   LEDC Channel: 2
[13:29:24][C][ledc.output:179]:   PWM Frequency: 1000.0 Hz
[13:29:24][C][ledc.output:180]:   Phase angle: 0.0°
[13:29:24][C][ledc.output:181]:   Bit depth: 16
[13:29:24][C][ledc.output:176]: LEDC Output:
[13:29:24][C][ledc.output:177]:   Pin GPIO17
[13:29:24][C][ledc.output:178]:   LEDC Channel: 3
[13:29:24][C][ledc.output:179]:   PWM Frequency: 1000.0 Hz
[13:29:24][C][ledc.output:180]:   Phase angle: 0.0°
[13:29:24][C][ledc.output:181]:   Bit depth: 16
[13:29:24][C][light:103]: Light 'Display Backlight'
[13:29:24][C][light:105]:   Default Transition Length: 1.0s
[13:29:24][C][light:106]:   Gamma Correct: 2.80
[13:29:24][C][light:103]: Light 'LED'
[13:29:24][C][light:105]:   Default Transition Length: 1.0s
[13:29:24][C][light:106]:   Gamma Correct: 2.80
[13:29:24][C][homeassistant.time:010]: Home Assistant Time:
[13:29:25][C][homeassistant.time:011]:   Timezone: 'MST7MDT,M3.2.0,M11.1.0'
[13:29:25][C][xpt2046:062]: XPT2046:
[13:29:25][C][xpt2046:064]:   IRQ Pin: GPIO36
[13:29:25][C][xpt2046:065]:   X min: 288
[13:29:25][C][xpt2046:066]:   X max: 3779
[13:29:25][C][xpt2046:067]:   Y min: 193
[13:29:25][C][xpt2046:068]:   Y max: 3647
[13:29:25][C][xpt2046:070]:   Swap X/Y: NO
[13:29:25][C][xpt2046:071]:   Invert X: NO
[13:29:25][C][xpt2046:072]:   Invert Y: NO
[13:29:25][C][xpt2046:074]:   threshold: 400
[13:29:25][C][xpt2046:076]:   Update Interval: 0.050s
[13:29:25][C][psram:020]: PSRAM:
[13:29:25][C][psram:021]:   Available: NO
[13:29:25][C][web_server:145]: Web Server:
[13:29:25][C][web_server:146]:   Address: xxx
[13:29:25][C][mdns:116]: mDNS:
[13:29:25][C][mdns:117]:   Hostname: bedroom-display
[13:29:25][C][esphome.ota:073]: Over-The-Air updates:
[13:29:25][C][esphome.ota:074]:   Address: xxx
[13:29:25][C][esphome.ota:075]:   Version: 2
[13:29:25][C][esphome.ota:078]:   Password configured
[13:29:25][C][safe_mode:018]: Safe Mode:
[13:29:25][C][safe_mode:020]:   Boot considered successful after 60 seconds
[13:29:25][C][safe_mode:021]:   Invoke after 10 boot attempts
[13:29:25][C][safe_mode:023]:   Remain in safe mode for 300 seconds
[13:29:25][C][api:139]: API Server:
[13:29:25][C][api:140]:   Address: xxx
[13:29:25][C][api:142]:   Using noise encryption: YES
[13:29:25][C][improv_serial:032]: Improv Serial:
[13:29:25][D][api:102]: Accepted xxx
[13:29:25][D][api.connection:1389]: Home Assistant 2024.9.1 (10.0.0.239): Connected successfully
[13:29:25][D][time:051]: Synchronized time: 2024-09-14 13:29:25
[13:30:16][I][safe_mode:041]: Boot seems successful; resetting boot loop counter
[13:30:16][D][esp32.preferences:114]: Saving 1 preferences to flash...
[13:30:16][D][esp32.preferences:143]: Saving 1 preferences to flash: 0 cached, 1 written, 0 failed

For starters, you need to specify the CS pin for the display.

And specify the screen dimensions.

There is thread converting this very device within the last day. Esphome TFT displays question

Not required for ili9341, it has default dimensions, which you will see in the log. Unless also setting transform.

Interesting, in the other thread I pointed to, setting the dimensions seemed to fix it. But I bow to your superior knowledge.

Defining the CS pin got the entire screen to turn on instead of just the bit shown in the picture. But still no Hello World.
(seems this should be a Required parameter in the ILI9341 component definition)

Added the CS pin definition and got the whole screen to turn on.
Tried adding the dimensions as shown in the linked thread but still no Hello World.

No, it’s only required on your specific board. Other boards might have the CS pin hardwired. Same with the reset pin.

try show_test_card: true in the display config rather than the lambda.

Then maybe an Optional mention?

the other thread I pointed to, setting the dimensions seemed to fix it.

It depends what “it” was. Specifying the dimensions may be required to get a particular desired outcome, but not doing so won’t stop the display from working completely, for the pre-defined models including ili9341.

But I bow to your superior knowledge.

Deference not required. We’re all on a learning curve of some kind.

Good catch - it should be listed in the config options and is not - just mentioned once in an example.

The test card looked like what I’d expect a display test to look like.

Put the lambda back in and now I see the Hello World but it just blinks on the screen for a couple hundred mS then off and keeps repeating at about a 1Hz rate.

Probably quite normal with a 1Hz update rate and auto-clear, and using 8 bit colour. Without PSRAM you can’t have a proper 16 bit screen-size buffer so the update performance is miserable. Use LVGL and set buffer_size to 25% and it should be sweet.

Thanks for pointing to the LVGL component. Plan to dig into that today to see if I can get this thing working. Seems it would be helpful to also have a mention of LVGL in the various Display components.

Anyway, your mention of the auto_clear_enabled: property seemed like that might be a good short term workaround as this device is just going to be displaying some environmental values (temp, humidity, pressure, air quality…) that just don’t change that fast so an update rate of 1 second (60 seconds would be fine). So I added the property and set it to false and now the text doesn’t show at all again. So I changed it to true (which is the default) and the text still does not show. I have to remove the property to get the text back but of course it’s still blinking.

Also a little confused on the auto_clear_enabled: definition. It’s True by default which would force the display to clear after each write and indeed, that seems to be what’s happening. Setting it False it states, “keep the existing display content (must overwrite explicitly, e.g., only on data change)”. The way this is worded it seems to be saying that it will only update the display if the data to display has changed since the last update. And this makes sense as it would reduce the load on the device not needing to do a write operation when not needed. But as I stated, that’s not what I’m seeing. I would think it would write once after bootup then not clear the screen and not need to do any more writes because the data hasn’t changed.

Are we possibly seeing just difference in the device itself or is there possibly a bug in this component that’s causing this behavior?

First attempt with LVGL is at least encouraging. Need to figure out the orientation and then work out the lambdas.

Thanks for assistance :beers:

There are plenty of examples in the forum.

Can you please share the yaml that you now have?
This would be a good starting point for me…

This should print the current time and date in the center of the screen in landscape mode. The backlight should turn on after bootup and dim after 30 secs and then off in another 10 secs.

If you want Portrait mode. Change the ‘dimensions’ and ‘transform’ sections in ‘display’ as follows:

dimensions: 
      width: 240
      height: 320
    transform:
      swap_xy: false
      mirror_x: true
      mirror_y: false
substitutions:
  name: "ESP32-2432S028-display"
  friendly_name: ESP32-2432S028-display
  devicename: "ESP32-2432S028 Display"

esphome:
  name: ${name}
  friendly_name: ${friendly_name}
  min_version: 2024.6.0
  name_add_mac_suffix: false
  project:
    name: esphome.web
    version: dev
  on_boot:
    priority: -100
    then:
      - script.execute: backlight_timer

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:
  logs:
    component: ERROR

# Enable Home Assistant API
api:
  encryption: 
    key: !secret haapi_key

# Allow Over-The-Air updates
ota:
  - platform: esphome
    password: !secret ota_password

# Allow provisioning Wi-Fi via serial
#improv_serial:

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  manual_ip:
    static_ip: 10.0.0.135
    gateway: 10.0.0.1
    subnet: 255.255.255.0
    dns1: 75.75.75.75
    dns2: 75.75.76.76
  
  fast_connect: off

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "ESP32-2432S028"
    password: !secret ap_password

# In combination with the `ap` this allows the user
# to provision wifi credentials to the device via WiFi AP.
# captive_portal:

dashboard_import:
  package_import_url: github://esphome/example-configs/esphome-web/esp32.yaml@main
  import_full_config: true

# Sets up Bluetooth LE (Only on ESP32) to allow the user
# to provision wifi credentials to the device.
# esp32_improv:
#  authorizer: none

# To have a "next url" for improv serial
web_server:

time:
  - platform: homeassistant
    id: esptime

spi:
  - id: lcd
    clk_pin: GPIO14
    mosi_pin: GPIO13
    miso_pin: GPIO12
  - id: touch
    clk_pin: GPIO25
    mosi_pin: GPIO32
    miso_pin: GPIO39

i2c:
  sda: GPIO27
  scl: GPIO22
  scan: true
  id: i2c_bus_a


display:
  - platform: ili9xxx
    model: ili9341
    spi_id: lcd
    dc_pin: GPIO2
    cs_pin: GPIO15
    id: display_1
    update_interval: never
    auto_clear_enabled: false
    show_test_card: false
    dimensions: 
      width: 320
      height: 240
    transform:
      swap_xy: true
      mirror_x: false
      mirror_y: false

lvgl:
  displays:
    - display_1
  touchscreens:
    - touchscreen_1
  buffer_size: 25%
  pages:
    - id: main_page
      widgets:
        - label:
            align: CENTER
            text:
              time_format: "%c"

# Define pins for backlight display and rear LED
output:
  - platform: ledc
    pin: GPIO21
    id: backlight
  - platform: ledc
    id: output_red
    pin: GPIO4
    inverted: true
  - platform: ledc
    id: output_green
    pin: GPIO16
    inverted: true
  - platform: ledc
    id: output_blue
    pin: GPIO17
    inverted: true
#  speaker P4
#  - platform: ledc
#    pin: GPIO26
#    id: rtttl_out

# Define a monochromatic, dimmable light for the backlight
light:
  - platform: monochromatic
    output: backlight
    name: "Display Backlight"
    id: back_light
    restore_mode: ALWAYS_OFF
  - platform: rgb
    name: LED
    red: output_red
    id: led
    green: output_green
    blue: output_blue
    restore_mode: ALWAYS_OFF

touchscreen:
  platform: xpt2046
  spi_id: touch
  cs_pin: GPIO33
  interrupt_pin: GPIO36
  update_interval: 50ms
  id: touchscreen_1
  # report_interval: 1s
  threshold: 400
  calibration:
    x_min: 300
    x_max: 3900
    y_min: 200
    y_max: 3700
  on_touch:
    - script.stop: backlight_timer
    - script.execute: backlight_timer
  #Uncomment for calibrations
#    - lambda: |-
#          ESP_LOGI("cal", "x=%d, y=%d, x_raw=%d, y_raw=%0d",
#              touch.x,
#              touch.y,
#              touch.x_raw,
#              touch.y_raw
#              );
  #Uncomment for calibrations

#This is the script for turn off the backlight after some seconds of inactivity
script:
  id: backlight_timer
  then:
  - light.turn_on:
      id: back_light
      brightness: 100%
  - delay: 30s
  - light.turn_on:
      id: back_light
      brightness: 50%
  - delay: 10s
  - light.turn_off: back_light

switch:
  - platform: restart
    name: "ESP32-2432S028 Display Restart"

1 Like