ESP32-S3 1.8inch AMOLED Touch

I’ve just got one of these devices:

SH8601 Display Driver and FT3168 Touch

It is not officially supported by ESPHome, but I wondered if anyone has managed to get it working and could share an example? I’ve had a search through the forum and I can see some references to working with the SH8601 and FT3168 separately.
Thanks

I can’t find a specific example for this device, but I managed to get a basic configuration together by cribbing bits and pieces from data sheets and various ESPHome and non ESPHome configs that other people have built for similar boards.

The display works successfully, but I can’t get the touch screen working. When I tap the screen, I just get the following errors logged:

[15:20:10.094][D][esp-idf:000]: E (29193) i2c.master: I2C hardware NACK detected
[15:20:10.094][D][esp-idf:000]: E (29194) i2c.master: I2C transaction unexpected nack detected
[15:20:10.096][D][esp-idf:000]: E (29195) i2c.master: s_i2c_synchronous_transaction(945): I2C transaction failed
[15:20:10.098][D][esp-idf:000]: E (29197) i2c.master: i2c_master_execute_defined_operations(1401): I2C transaction failed

I’ve experimented with various frequencies, without success.
Config with working display but non-working touch screen:

esphome:
  name: home-monitor-2

esp32:
  variant: esp32s3
  framework:
    type: esp-idf
  flash_size: 16MB

psram:
  mode: octal

# Enable logging
logger:

# Enable Home Assistant API
api:
  on_client_connected:
    - logger.log: "api_connected"

# Allow Over-The-Air updates
ota:
- platform: esphome

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  manual_ip:
    static_ip: 192.168.1.51
    gateway: 192.168.1.254
    subnet: 255.255.255.0

i2c:
  id: i2c_1
  scl: GPIO14
  sda: GPIO15
  frequency: 40kHz
  scan: false

pca9554:
  - id: 'pca9554_device'
  
touchscreen:
  platform: cst816
  display: display_1
  i2c_id: i2c_1
  id: touchscreen_1
  interrupt_pin: GPIO21
  reset_pin: 
    pca9554: pca9554_device
    number: 0
  skip_probe: true
  on_touch:
    - lambda: |-
        ESP_LOGI("cal", "x=%d, y=%d, x_raw=%d, y_raw=%0d",
          touch.x,
          touch.y,
          touch.x_raw,
          touch.y_raw
        );
spi:
  type: quad
  clk_pin: GPIO11
  data_pins: [GPIO4, GPIO5, GPIO6, GPIO7]

# Display configuration
display:
  - platform: mipi_spi
    id: display_1
    bus_mode: quad
    dimensions: 
      width: 368
      height: 448
    model: CUSTOM
    cs_pin: GPIO12
    init_sequence:
      - [0x00]
      - delay 120ms
      - [0x01, 0xD1]
      - [0x00]
      - [0x20]
      - delay 10ms
      - [0x00, 0x00, 0x01, 0x6F]
      - [0x00, 0x00, 0x01, 0xBF]
      - [0x00]
      - delay 10ms
      - [0x00]
      - delay 10ms
      - [0xFF]
      
lvgl:
  #buffer_size: 25%
  widgets:
    - label:
        align: CENTER
        text: 'Hello World!'
    - button:
        x: 84
        y: 100
        width: 200
        height: 80
        id: btn_id
        checkable: true
        widgets:
          - label:
              align: center
              text: "Tap me"
        on_value:
          then:
            - logger.log:
                format: "Button checked state: %d"
                args: [ x ]
    - slider:
        id: dimmer_slider
        x: 20
        y: 50
        width: 30
        height: 220
        pad_all: 8
        min_value: 0
        max_value: 255

schematic gives ext02 for touch reset pin…

You’re correct. The “0” in the config I posted was incorrectly included because I wasn’t sure if I was reading the spec correctly and was experimenting with different values.
I get the same error using pin 2, so I think there is something else wrong with my configuration.

Success!
I dug through the Waveshare example repository (GitHub - waveshareteam/ESP32-display-support: Waveshare ESP32 with screen product code summary) and found the two missing pieces of information to get the touchscreen working:

  1. Backtracking on the preceding discussion, pin 0 is actually the correct value for the pca9554 I/O extender reset_pin, not pin 2 (in retrospect this is obvious from the schematic pin names: LCD_RESET EXIO0 rather than TP_RESET EXIO2)
  2. address: 0x38 is required for the touchscreen: definition

I also removed the frequency: value from my i2c: definition and it seems to be running OK on the default.
There are a lot of other features on this device (like audio I/O) that I have not attempted to configure, but it does appear to be functional for display and touch with ESPHome.

esphome:
  name: home-monitor-2

esp32:
  variant: esp32s3
  framework:
    type: esp-idf
  flash_size: 16MB

psram:
  mode: octal

# Enable logging
logger:

# Enable Home Assistant API
api:
  on_client_connected:
    - logger.log: "api_connected"

# Allow Over-The-Air updates
ota:
- platform: esphome

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  manual_ip:
    static_ip: 192.168.1.51
    gateway: 192.168.1.254
    subnet: 255.255.255.0

i2c:
  id: i2c_1
  scl: GPIO14
  sda: GPIO15
  scan: false

pca9554:
  - id: 'pca9554_device'
  
touchscreen:
  platform: cst816
  display: display_1
  address: 0x38
  i2c_id: i2c_1
  id: touchscreen_1
  interrupt_pin: GPIO21
  reset_pin: 
    pca9554: pca9554_device
    number: 0
  skip_probe: true
  on_touch:
    - lambda: |-
        ESP_LOGI("cal", "x=%d, y=%d, x_raw=%d, y_raw=%0d",
          touch.x,
          touch.y,
          touch.x_raw,
          touch.y_raw
        );
spi:
  type: quad
  clk_pin: GPIO11
  data_pins: [GPIO4, GPIO5, GPIO6, GPIO7]

# Display configuration
display:
  - platform: mipi_spi
    id: display_1
    bus_mode: quad
    dimensions: 
      width: 368
      height: 448
    model: CUSTOM
    cs_pin: GPIO12
    init_sequence:
      - [0x00]
      - delay 120ms
      - [0x01, 0xD1]
      - [0x00]
      - [0x20]
      - delay 10ms
      - [0x00, 0x00, 0x01, 0x6F]
      - [0x00, 0x00, 0x01, 0xBF]
      - [0x00]
      - delay 10ms
      - [0x00]
      - delay 10ms
      - [0xFF]
      
lvgl:
  #buffer_size: 25%
  widgets:
    - label:
        align: CENTER
        text: 'Hello World!'
    - button:
        x: 84
        y: 100
        width: 200
        height: 80
        id: btn_id
        checkable: true
        widgets:
          - label:
              align: center
              text: "Tap me"
        on_value:
          then:
            - logger.log:
                format: "Button checked state: %d"
                args: [ x ]
    - slider:
        id: dimmer_slider
        x: 20
        y: 50
        width: 30
        height: 220
        pad_all: 8
        min_value: 0
        max_value: 255

A small change that fixes some issues and simplifies the config:
I couldn’t get brightness working with the CUSTOM display model: configuration I had cobbled together from some code examples, but discovered that I could drop the custom init_sequence and just use the model: WAVESHARE-ESP32-S3-TOUCH-AMOLED-1.75 option, which seems to work fine (the pin errors in my initial config efforts obscured the fact that this option was valid).

[Edit: I have an unresolved issue with the touchscreen only working immediately after flashing; if I subsequently power cycle the device the touch screen no longer responds ]

Updated config:

esphome:
  name: home-monitor-2

esp32:
  variant: esp32s3
  framework:
    type: esp-idf
  flash_size: 16MB

psram:
  mode: octal

# Enable logging
logger:

# Enable Home Assistant API
api:
  on_client_connected:
    - logger.log: "api_connected"

# Allow Over-The-Air updates
ota:
- platform: esphome

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  manual_ip:
    static_ip: 192.168.1.51
    gateway: 192.168.1.254
    subnet: 255.255.255.0

i2c:
  id: i2c_1
  scl: GPIO14
  sda: GPIO15
  scan: false
  frequency: 100kHz

pca9554:
  - id: 'pca9554_device'
  
touchscreen:
  platform: cst816
  display: display_1
  address: 0x38
  i2c_id: i2c_1
  id: touchscreen_1
  interrupt_pin: GPIO21
  reset_pin: 
    pca9554: pca9554_device
    number: 0
  skip_probe: true
  on_touch:
    - lambda: |-
        ESP_LOGI("cal", "x=%d, y=%d, x_raw=%d, y_raw=%0d",
          touch.x,
          touch.y,
          touch.x_raw,
          touch.y_raw
        );
spi:
  type: quad
  clk_pin: GPIO11
  data_pins: [GPIO4, GPIO5, GPIO6, GPIO7]

# Display configuration
display:
  - platform: mipi_spi
    id: display_1
    brightness: 150
    bus_mode: quad
    dimensions: 
      width: 368
      height: 448
    model: WAVESHARE-ESP32-S3-TOUCH-AMOLED-1.75
    cs_pin: GPIO12
    # init_sequence:
    #   - [0x00]
    #   - delay 120ms
    #   - [0x01, 0xD1]
    #   - [0x00]
    #   - [0x20]
    #   - delay 10ms
    #   - [0x00, 0x00, 0x01, 0x6F]
    #   - [0x00, 0x00, 0x01, 0xBF]
    #   - [0x00]
    #   - delay 10ms
    #   - [0x00]1
    #   - delay 10ms
    #   - [0xFF]
      
lvgl:
  #buffer_size: 25%
  pages:
    - id: main_page
      bg_color: black
      widgets:
        - label:
            align: CENTER
            text: 'Hello World!'
            text_color: white
        - button:
            bg_color: LightGreen
            x: 84
            y: 100
            width: 200
            height: 80
            id: btn_id
            checkable: true
            checked:
              bg_color: Grey
            widgets:
              - label:
                  align: center
                  text: "Tap me"
            on_value:
              then:
                - logger.log:
                    format: "Button checked state: %d"
                    args: [ x ]
        - slider:
            id: dimmer_slider
            x: 20
            y: 50
            width: 30
            height: 220
            pad_all: 8
            min_value: 0
            max_value: 255
2 Likes

You can get the touch screen working reliably like this:

touchscreen:
  - platform: ft5x06
    id: my_touchscreen
    display: my_display
    interrupt_pin: GPIO21
    address: 0x38

Thanks very much. That was a well timed reply because I was just about to post another message after failing to find a solution to the reboot problem.

I switched to platform: ft5x06 and can confirm that the touchscreen is working correctly when flashed, and also continuing to work when the device is rebooted.

My revised configuration is below. With the fx5x06 plaform, the reset_pin and skip_probe options are no longer valid, and the pca9554 definition is therefore no longer required.

Thanks again!

Revised config:

esphome:
  name: home-monitor-2

esp32:
  variant: esp32s3
  framework:
    type: esp-idf
  flash_size: 16MB

psram:
  mode: octal

# Enable logging
logger:

# Enable Home Assistant API
api:
  on_client_connected:
    - logger.log: "api_connected"

# Allow Over-The-Air updates
ota:
- platform: esphome

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  manual_ip:
    static_ip: 192.168.1.51
    gateway: 192.168.1.254
    subnet: 255.255.255.0

i2c:
  id: i2c_1
  scl: GPIO14
  sda: GPIO15
  scan: true
  frequency: 100kHz

touchscreen:
  platform: ft5x06
  display: display_1
  address: 0x38
  i2c_id: i2c_1
  id: touchscreen_1
  interrupt_pin: GPIO21
  on_touch:
    - lambda: |-
        ESP_LOGI("cal", "x=%d, y=%d, x_raw=%d, y_raw=%0d",
          touch.x,
          touch.y,
          touch.x_raw,
          touch.y_raw
        );
spi:
  type: quad
  clk_pin: GPIO11
  data_pins: [GPIO4, GPIO5, GPIO6, GPIO7]

# Display configuration
display:
  - platform: mipi_spi
    id: display_1
    brightness: 150
    bus_mode: quad
    dimensions: 
      width: 368
      height: 448
    model: WAVESHARE-ESP32-S3-TOUCH-AMOLED-1.75
    cs_pin: GPIO12
      
lvgl:
  #buffer_size: 25%
  pages:
    - id: main_page
      on_swipe_right:
        - lvgl.page.previous:
      on_swipe_left:
        - lvgl.page.next:
      bg_color: black
      widgets:
        - label:
            align: CENTER
            text: 'Hello World!'
            text_color: white
        - button:
            bg_color: LightGreen
            x: 84
            y: 100
            width: 200
            height: 80
            id: btn_id
            checkable: true
            checked:
              bg_color: Grey
            widgets:
              - label:
                  align: center
                  text: "Tap me"
            on_value:
              then:
                - logger.log:
                    format: "Button checked state: %d"
                    args: [ x ]
        - slider:
            id: dimmer_slider
            x: 20
            y: 50
            width: 30
            height: 220
            pad_all: 8
            min_value: 0
            max_value: 255
    - id: page_2
      on_swipe_right:
        - lvgl.page.previous:
      on_swipe_left:
        - lvgl.page.next:
      bg_color: black
      widgets:
        - label:
            align: CENTER
            text: 'Hello World 2!'
            text_color: white
1 Like

Audio works fine too, here’s a tap to talk voice assistant with a volume slider:

esphome:
  name: esp-assistant
  friendly_name: esp-assistant

esp32:
  board: esp32-s3-devkitc-1
  framework:
    type: esp-idf

psram:
  mode: octal

logger:

api:

ota:

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

captive_portal:

# I2C Bus - Shared by touchscreen (FT5x06) and audio codec (ES8311)
i2c:
  id: i2c_bus
  sda: GPIO15
  scl: GPIO14
  frequency: 200kHz
  scan: true

# Quad SPI Bus - AMOLED display
spi:
  - id: quad_spi
    type: quad
    clk_pin: GPIO11
    data_pins:
      - GPIO4   # D0
      - GPIO5   # D1
      - GPIO6   # D2
      - GPIO7   # D3

# AMOLED Display (368x448)
display:
  - platform: mipi_spi
    id: my_display
    brightness: 150
    bus_mode: quad
    dimensions:
      width: 368
      height: 448
    model: WAVESHARE-ESP32-S3-TOUCH-AMOLED-1.75
    cs_pin: GPIO12

# FT5x06 Capacitive Touchscreen
touchscreen:
  - platform: ft5x06
    id: my_touchscreen
    display: my_display
    interrupt_pin: GPIO21
    address: 0x38
    transform:
      swap_xy: false
      mirror_x: false
      mirror_y: false

# ============================================================================
# AUDIO CONFIGURATION
# ============================================================================

# I2S Audio Bus - Connects ESP32 to ES8311 codec
i2s_audio:
  - id: i2s_audio_bus
    i2s_lrclk_pin: GPIO45  # Word select / left-right clock
    i2s_bclk_pin: GPIO9     # Bit clock
    i2s_mclk_pin: GPIO16    # Master clock

# ES8311 Audio Codec
audio_dac:
  - platform: es8311
    id: es8311_codec
    address: 0x18
    i2c_id: i2c_bus
    use_mclk: true
    use_microphone: false  # Analog microphone (not PDM digital)
    bits_per_sample: 16bit
    sample_rate: 16000
    mic_gain: 18DB

# Speaker - Audio output through ES8311 DAC
speaker:
  - platform: i2s_audio
    id: external_speaker
    dac_type: external
    audio_dac: es8311_codec
    i2s_audio_id: i2s_audio_bus
    i2s_dout_pin: GPIO8
    sample_rate: 16000
    bits_per_sample: 16bit
    timeout: 2s

# Microphone - Audio input through ES8311 ADC
microphone:
  - platform: i2s_audio
    id: external_microphone
    adc_type: external
    i2s_audio_id: i2s_audio_bus
    i2s_din_pin: GPIO10
    bits_per_sample: 16bit
    sample_rate: 16000

# Power Amplifier - GPIO46 controls speaker amplifier enable
switch:
  - platform: gpio
    pin: GPIO46
    id: pa_enable
    name: "Speaker Amplifier"
    restore_mode: ALWAYS_ON
    internal: false

# Volume Control - Synced with ES8311 hardware volume and UI slider
number:
  - platform: template
    id: volume_control
    name: "Volume"
    min_value: 0
    max_value: 100
    initial_value: 70
    step: 5
    optimistic: true
    set_action:
      - lambda: |-
          id(es8311_codec).set_volume(x / 100.0);
          ESP_LOGI("volume", "Volume set to: %.0f%%", x);

# RTTTL Ringtone Player - Plays simple tones and melodies
rtttl:
  id: buzzer
  speaker: external_speaker

# ============================================================================
# VOICE ASSISTANT
# ============================================================================

voice_assistant:
  id: va
  microphone: external_microphone
  speaker: external_speaker

  # Listening state - Button turns blue
  on_listening:
    - logger.log: "Voice assistant started listening"
    - lvgl.label.update:
        id: status_label
        text: "Listening..."
    - lvgl.widget.update:
        id: btn_id
        bg_color: DodgerBlue

  # Speech-to-text complete - Display recognized text
  on_stt_end:
    - logger.log:
        format: "Speech recognized: %s"
        args: [ 'x.c_str()' ]
    - lvgl.label.update:
        id: status_label
        text: !lambda 'return "You said: " + x;'

  # Text-to-speech starting - Display speaking status
  on_tts_start:
    - logger.log: "Speaking response..."
    - lvgl.label.update:
        id: status_label
        text: "Speaking..."

  # Session complete - Button returns to green
  on_end:
    - logger.log: "Voice assistant session ended"
    - lvgl.label.update:
        id: status_label
        text: "Ready"
    - lvgl.widget.update:
        id: btn_id
        bg_color: LightGreen

  # Error handling - Flash red, then return to green
  on_error:
    - logger.log:
        format: "Voice assistant error: %s"
        args: [ 'code.c_str()' ]
    - lvgl.label.update:
        id: status_label
        text: "Error!"
    - lvgl.widget.update:
        id: btn_id
        bg_color: Red
    - delay: 1s
    - lvgl.widget.update:
        id: btn_id
        bg_color: LightGreen
    - lvgl.label.update:
        id: status_label
        text: "Ready"

# ============================================================================
# USER INTERFACE (LVGL)
# ============================================================================

lvgl:
  pages:
    - id: main_page
      bg_color: black
      widgets:
        # Status label - Shows current voice assistant state
        - label:
            id: status_label
            align: CENTER
            y: 50
            text: 'Ready'
            text_color: white

        # Main voice assistant button - Tap to activate
        - button:
            bg_color: LightGreen
            x: 84
            y: 100
            width: 200
            height: 160
            id: btn_id
            widgets:
              - label:
                  align: center
                  text: "Tap to Talk"

            on_click:
              then:
                - if:
                    condition:
                      not: voice_assistant.is_running
                    then:
                      - rtttl.play: 'beep:d=16,o=5,b=100:c6'
                      - delay: 200ms
                      - voice_assistant.start:
                    else:
                      - logger.log: "Ignored tap: Voice Assistant already running"

        # Volume slider - Vertical slider on left side
        - slider:
            id: volume_slider
            x: 20
            y: 50
            width: 30
            height: 308
            pad_all: 8
            min_value: 0
            max_value: 100
            value: 70
            on_value:
              then:
                - logger.log:
                    format: "Volume slider: %d"
                    args: [ x ]
                - number.set:
                    id: volume_control
                    value: !lambda 'return x;'

        # Volume label
        - label:
            x: 60
            y: 180
            text_color: white
            text: "Vol"
2 Likes