Feather ESP32-S3 Reverse TFT not detecting all button presses

I am running ESPHome on a device with three onboard buttons mapped to GPIO0 through GPIO2. These buttons are configured to cycle pages on an integrated display. While the page-flipping logic itself works, the button responsiveness is very poor.

Specifically:

  • Short presses are rarely detected.
  • Longer presses are more reliable but still intermittent.
  • The logs only show activity when a press is successfully registered; there is no log output for the “missed” presses.

I have already experimented with debounce and various delayed_on/off filters, but the sensitivity hasn’t improved.

yaml:

  substitutions:
    devicename: test_esp32_s3_ftf
    prettyname: "Test-ESP32-S3-TFT"
  
  esphome:
    name: ${devicename}
    friendly_name: ${prettyname}
    on_boot:
      then:
        - display.page.show: startup
  
  esp32:
    board: adafruit_feather_esp32s3_tft
    variant: esp32s3
    framework:
      type: esp-idf
  
  # Enable logging
  logger:
  
  # Enable Home Assistant API
  api:
    encryption:
      key: !secret test_esp32_s3_ftf_api_encryption
  
  ota:
    - platform: esphome
      password: !secret test_esp32_s3_ftf_ota_password
  
  wifi:
    ssid: !secret wifi_ssid
    password: !secret wifi_password
  
  web_server:
    port: 80
  
  captive_portal:
  
  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_black
      red: 0%
      green: 0%
      blue: 0%
  
  font:
    - file: "gfonts://Roboto"
      id: font20
      size: 20
    - file: "gfonts://Roboto"
      id: large
      size: 55
    - file: "gfonts://Roboto"
      id: medium
      size: 35
    - file: "gfonts://Roboto"
      id: roboto
      size: 20
  
  binary_sensor:
    - platform: status
      name: "Node Status"
      id: system_status
    - platform: gpio
      pin:
        number: GPIO1
        mode:
          input: true
          pulldown: true
      name: "D1"
      filters:
        - delayed_on: 0ms
        - delayed_off: 0ms
      on_press: 
        then:
          - display.page.show: page2
    - platform: gpio
      pin:
        number: GPIO2
        # mode:
        #   input: true
        #   pulldown: true
      name: "D2"
      # filters:
      #   - delayed_on: 0ms
      #   - delayed_off: 0ms
      on_press: 
        then:
          - display.page.show: page1
  
  text_sensor:
    - platform: homeassistant
      entity_id: sensor.outdoor_status
      name: "Outdoor status"
      id: outdoor_status
      on_value:
        then:
          - display.page.show: page1
    - platform: homeassistant
      entity_id: sensor.outdoor_status_2
      name: "Outdoor status 2"
      id: outdoor_status_2
  
  time:
    - platform: homeassistant
      id: esptime
  
  spi:
    clk_pin: GPIO36
    mosi_pin: GPIO35
    miso_pin: GPIO37
    interface: software
  
  power_supply:
    - id: tft_pwr_en
      enable_on_boot: true
      pin: GPIO7
  
  display:
    - platform: st7789v
      model: TTGO TDisplay 135x240
      backlight_pin: GPIO45
      cs_pin: GPIO42
      dc_pin: GPIO40
      reset_pin: GPIO41
      power_supply: tft_pwr_en
      height: 240
      width: 135
      offset_height: 52
      offset_width: 40
      update_interval: 2s
      rotation: 90
      pages:
        - id: page1
          lambda: |-
            it.fill(id(my_red));
            it.printf(10, 10, id(font20), id(my_black), "Test");
        - id: page2
          lambda: |-
            it.fill(id(my_gray));
            it.printf(10, 10, id(font20), id(my_black), "Test");
        - id: startup
          lambda: |-
            it.fill(id(my_gray));
            it.printf(10, 10, id(font20), id(my_blue), "Loading...");

First of all, you need to always try remembering to include product links for whichever hardware your OP refers to. People are always using vague/generic board types in posts like this, “Im using an esp32” and no one really knows wtf that is because there are dozens of different types, versions, esp boards built into custom IC’s or unique brand name Dev boards and etc, etc. Not all esp32 boards are made identical so, you reaally need to provide specific details and most people prefer that you just include a product link to wherever you purchased it or an identical board elsewhere is ok too.

Without knowing with any certainty which esp32 board you’re using then its next to impossible to look up its pinout and verify those pins you chose are both Ok to use and whether they have any secondary things to be aware of or that may be a problem with using any of thise pins.

How do you have your buttons wired up to the esp32? Was there a specific reason for why you opted to use internall pulldown resistors opposed to a pull up??

Also, whats with setting delayed_on/off filters to 0ms?? If they’re 0ms then they just need gotten rid of completely rather than setting 0ms.

Did you ever get to a point where the buttons worked consistently and reliably before you started with adding random filters and whatever else extra you’ve tried including prior?

You’ve only configured 2 buttons, not 3 so IDK if you noticed that because not configuring a gpio button would certainly be a reason for at least that one to not work correctly or at all. Also, of the 2 youve got typed in your config, one of the 2 buttons is nearly all commented out and with so much of it hidden/removed from your butto config, well thats abother excellent reason for why another button isnt or wont work at all. You’ve basically only got 1 actual button configured enough to where it could potentially work if you wired it correctly.

Provide more specific details as mentioned above and also fix your code so that all 3 physical buttons are in there and configured because you’ve only got 1 currently.

You also mention none of the buttons short/long press options are working either… Well, they’re not going to work if you dont ever configure those settings and provide the required details for setting its upper/lower time limits for short/long press.

Set gpio0 to pullup: true and inverted: true.
Gpios 1 and 2 pulldown: true and inverted: false
And delayed on/off 20ms to all of them.

The hardware in use is an Adafruit ESP32-S3 Reverse TFT Feather (https://learn.adafruit.com/esp32-s3-reverse-tft-feather/pinouts). Since the buttons are integrated into the PCB, the wiring is fixed and follows the manufacturer’s schematic:

  • GPIO 0: Hardwired to the Boot button (Excluded from config to avoid conflicts).
  • GPIO 1 (D1): Configured with custom filters for testing.
  • GPIO 2 (D2): Configured using default ESPHome settings.

Both buttons are pulled down by default via the onboard circuitry, so no external resistors are required or used.

To clarify the YAML structure: both buttons are active. I have intentionally left the customizations for D2 commented out to serve as a “control group” using standard ESPHome values. This allows me to compare the behavior of the default settings against the custom filters applied to D1.

Troubleshooting Steps Taken

I have systematically tested several delayed_on and delayed_off values—specifically 0ms, 20ms, and 100ms—to eliminate contact bounce as the culprit. Regardless of these timing adjustments, the symptoms remain identical across both buttons.

My goal is simple state detection; I am not attempting to differentiate between short and long presses. However, the responsiveness is inconsistent:

  • Short presses: Frequently fail to trigger the on_press event, resulting in no page change.
  • Long presses: Significantly more reliable and usually trigger the event as expected.

Despite the variations in the YAML filters, the hardware is not reliably catching “momentary” interactions.

You could post your logs showing what’s going on when you press the button.

These are the logs when the button press is successful:

[22:51:55.163][D][binary_sensor:044]: 'D1' >> ON
[22:51:57.159][D][binary_sensor:044]: 'D1' >> OFF
[22:52:01.174][D][binary_sensor:044]: 'D2' >> ON
[22:52:03.176][D][binary_sensor:044]: 'D2' >> OFF

When the press is not detected there are no log entries in the logs.

Ok - so figured it out.

The reason was using the software interface for SPI.

spi:
  clk_pin: GPIO36
  mosi_pin: GPIO35
  miso_pin: GPIO37
  interface: software

Changed it to hardware:

spi:
  clk_pin: GPIO36
  mosi_pin: GPIO35
  miso_pin: GPIO37
  interface: hardware

And now it responds to all types of button presses.

I guess the software interface took to many cycles in the loop…

Only thing weird is that you didn’t get evidences on log.
I receive logs like “component x took long time…” if something is jamming even if everything works perfectly

Yeah - maybe the “jamming” did not take long enough for the warnings