ESP32-S3 3.5-inch capacitive touch IPS module 320 * 480

Hi,
Just bought one of these off AliExpress (about £20)…
The device worked fine with the stock software but I couldn’t connect to wifi. I wanted to connected it to HA anyway but couldn’t fine how to do it on the internet. So after a couple of days hacking around I can now report some success.


This is showing my solar battery State-Of-Charge (SOC) but is fairly general purpose.

There is lots of info around for the ‘T-Display S3 Long’ display but this uses a JC3248W535C module requiring the axs15231 drivers - not a lot of info around for that.

I use this for my S3-long which uses the axs15231 driver.


substitutions:
  name: t_display_long
  friendly_name: T-display S3 Long

esphome:
  name: ${name}
  friendly_name: ${friendly_name}
  platformio_options:
    upload_speed: 921600
    build_unflags: -Werror=all
    board_build.flash_mode: dio
    board_build.f_flash: 80000000L
    board_build.f_cpu: 240000000L

esp32:
  board: esp32-s3-devkitc-1
  flash_size: 16MB
  framework:
    type: esp-idf
# flash 16mb psram 8mb    

psram:
  mode: octal
  speed: 120MHz

# Enable logging
logger:

wifi:
  ssid: !secret wifi_ssid2
  password: !secret wifi_password
  ap:
    ssid: "Fallback Hotspot"
    password: !secret wifi_password

# Enable Home Assistant API
api:
  password: !secret ota_password
    

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

external_components:
  - source: github://buglloc/esphome-components
    refresh: 10min

# PIN_BAT_VOLT          2
# PIN_BUTTON_1          0
# PIN_BUTTON_2          21

spi:
  id: quad_spi
  type: quad
  clk_pin: 17
  data_pins:
    - 13
    - 18
    - 21
    - 14

i2c:
  sda: 15
  scl: 10
  id: touchscreen_bus
  frequency: 50hz

touchscreen:
  - platform: axs15231
    id: main_touch
    display: main_display
    i2c_id: touchscreen_bus
    transform:
      swap_xy: true
#      mirror_x: false
 #     mirror_y: 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
                );


display:
  - platform: axs15231
    id: main_display
    spi_id: quad_spi
    dimensions:
      height: 640
      width: 180
    cs_pin: 12
    reset_pin: 16
    backlight_pin: 1
    update_interval: 1s
    transform:
      swap_xy: false
    rotation: 90
#    auto_clear_enabled: true
    lambda: |-
      it.printf(20, 70, id(roboto), Color(255, 0, 0), id(ha_time).now().strftime("%I:%M:%S %p").c_str());

time:
  - platform: homeassistant
    id: ha_time


font:
  - file: "gfonts://Roboto"
    id: roboto
    size: 40

Lovely, but are you going to share?

???

Code is attached

My project is still ongoing, I want to be able to interact with the display by touch but I’m stuck on this at the moment. As mentioned before the S3 Long code posted doesn’t work with this device. The yaml so far is posted below but is still work in progress. I’m also new here so go easy please :grinning:
I would be interested if anyone else is using this device.

################################################################################
# Substitution Variables
################################################################################
substitutions:
  device_internal_name: esphome_16
  device_wifi_name: esphome-16
  device_friendly_name: ESPHome 16
  device_ip_address: 192.168.1.16
  device_sampling_time: 30s

################################################################################
# Globals
################################################################################
globals: ##to set default reboot behavior
  - id: wifi_connection
    type: bool
    restore_value: no
    initial_value: "false"

  - id: bgcolor
    type: Color
    initial_value: "Color::random_color()"



################################################################################
# Board Configuration
################################################################################
esphome:
  name: ${device_internal_name}
  friendly_name: ${device_friendly_name}

psram:
  mode: octal
  speed: 120MHz

esp32:
  board: esp32-s3-devkitc-1
  variant: ESP32S3
  flash_size: 16MB
  framework:
    type: esp-idf
    
external_components:
  - source: github://buglloc/esphome-components
    components: [ axs15231 ]

################################################################################
# Enable logging
################################################################################
logger:
  logs:
    component: ERROR

################################################################################
# Enable Home Assistant API
################################################################################
api:
  reboot_timeout: 0s
  encryption:
    key: !secret api_encryption_key

################################################################################
# OTA
################################################################################
ota:
  platform: esphome
  password: !secret web_server_password

################################################################################
# WiFi
################################################################################
wifi:
  networks:
    - ssid: !secret wifi_ssid
      password: !secret wifi_password
  manual_ip:
    static_ip: ${device_ip_address}
    gateway: !secret gateway_address
    subnet: !secret subnet_address
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: ${device_wifi_name}
    password: !secret web_server_password

################################################################################
# Web Server
################################################################################
web_server:
  port: 80
  version: 2
  include_internal: true
  auth:
    username: !secret web_server_user
    password: !secret web_server_password
  local: true



################################################################################
# Switch
################################################################################
switch:
  - platform: restart
    name: "Restart"
    id: device_restart
  
  - platform: safe_mode
    name: Use Safe Mode
    id: device_safe_mode

  - platform: output
    name: "LCD LED"
    output: lcdbacklight
    id: switch_lcdbacklight
    restore_mode: ALWAYS_ON
 
################################################################################
# Interval
################################################################################
interval:
  - interval: 10s
    then:
      - if:
          condition:
            wifi.connected:
          then:
            - lambda: |-
                id(wifi_connection) = true;
          else:
            - lambda: |-
                id(wifi_connection) = false;

time:
  - platform: homeassistant
    id: esptime

################################################################################
# Sensors
################################################################################
sensor:
  # WiFi
  - platform: wifi_signal
    name: "WiFi Signal Sensor"
    id: ${device_internal_name}_wifi_signal_sensor
    update_interval: ${device_sampling_time}

  # Uptime
  - platform: uptime
    name: "Uptime Sensor"
    id: ${device_internal_name}_uptime_sensor
    update_interval: ${device_sampling_time}
    internal: true
    on_raw_value:
      then:
        - text_sensor.template.publish:
            id: ${device_internal_name}_uptime_human
            state: !lambda |-
              int seconds = round(id(${device_internal_name}_uptime_sensor).raw_state);
              int days = seconds / (24 * 3600);
              seconds = seconds % (24 * 3600);
              int hours = seconds / 3600;
              seconds = seconds % 3600;
              int minutes = seconds /  60;
              seconds = seconds % 60;
              return (
                (days ? to_string(days) + "d " : "") +
                (hours ? to_string(hours) + "h " : "") +
                (minutes ? to_string(minutes) + "m " : "") +
                (to_string(seconds) + "s")
              ).c_str();

  - platform: homeassistant
    id: battery_soc
    entity_id: sensor.1be6e4_remaining_battery_capacity
    internal: true


    
################################################################################
# Text Sensors
################################################################################
text_sensor:
  #-------------------------------------------------------------------------------
  # ESP32 internal sensors
  #-------------------------------------------------------------------------------
  - platform: wifi_info
    ip_address:
      name: IP Address
      id: ${device_internal_name}_ip_address
    ssid:
      name: Connected SSID
      id: ${device_internal_name}_connected_ssid
    mac_address:
      name: Mac Wifi Address
      id: ${device_internal_name}_mac_address
  #-------------------------------------------------------------------------------
  # Custom Text sensors
  #-------------------------------------------------------------------------------
  - platform: template
    name: Uptime Human Readable
    id: ${device_internal_name}_uptime_human
    icon: mdi:clock-start

binary_sensor:
  # Global Button
  #- platform: touchscreen
  #  name: Page_Switch
  #  x_min: 0
  #  x_max: 320
  #  y_min: 0
  #  y_max: 480
  #  on_press:
  #    - logger.log: "Page Switch was touched."
  #    - display.page.show_next: main_display

image:
  - file: "image.png"
    id: my_image
    # resize: 640x180


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_white
    red: 100%
    green: 100%
    blue: 100%
  - id: my_black
    red: 0%
    green: 0%
    blue: 0%

font:
  - file: "gfonts://Roboto"
    id: font_std
    size: 40
    glyphs: "!\"%()+=,-_.:°0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz/\\[]|&@#'"
  - file: "gfonts://Roboto@700"
    id: font_title
    size: 40
    glyphs: "!\"%()+=,-_.:°0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz/\\[]|&@#'"
  - file: "arial.ttf"
    id: arial_96
    size: 96
  - file: "arial.ttf"
    id: arial_128
    size: 128
  - file: "arial.ttf"
    id: arial_48
    size: 48
  - file: "arial.ttf"
    id: arial_24
    size: 24
  - file: "arial.ttf"
    id: arial_12
    size: 12

captive_portal:

spi:
  - id: display_qspi
    type: quad
    clk_pin: 47
    data_pins:
      - 21
      - 48
      - 40
      - 39

i2c:
  sda: 4
  scl: 8
  id: touchscreen_bus

display:
  - platform: axs15231
    id: main_display
    spi_id: display_qspi
    dimensions:
      height: 480
      width: 320
    cs_pin: 45
    reset_pin: 16
    transform:
      swap_xy: false
    rotation: 90
    auto_clear_enabled: true
    lambda: |-
      it.rectangle(0,  0, it.get_width(), it.get_height(), id(my_blue));
      it.filled_rectangle(20,  20, it.get_width()-40, it.get_height()-40, id(my_gray));
      it.strftime((it.get_width() / 2), (it.get_height() / 6) * 1, id(arial_24), id(my_black), TextAlign::CENTER, "%Y-%m-%d", id(esptime).now());
      it.strftime((it.get_width() / 2), (it.get_height() / 6) * 2, id(arial_24), id(my_black), TextAlign::CENTER, "%H:%M:%S", id(esptime).now());
      it.printf((it.get_width() / 2), (it.get_height() / 6) * 3 - 25, id(arial_128), id(my_yellow), TextAlign::TOP_CENTER , "%.0f%%", id(battery_soc).state);
      

touchscreen:
  - platform: axs15231
    id: main_touch
    display: main_display
    i2c_id: touchscreen_bus
    transform:
      swap_xy: true
    on_touch:
      - lambda: |-
          Color newColor;
          do { newColor =  Color::random_color(); } while (newColor == id(bgcolor));
          id(bgcolor) = newColor;
    
          ESP_LOGI("cal", "x=%d, y=%d, x_raw=%d, y_raw=%0d",
            touch.x,
            touch.y,
            touch.x_raw,
            touch.y_raw
          );

output:
  - platform: gpio
    pin:
      number: GPIO01
      # allow_other_uses: true  
    id: lcdbacklight


Sorry, I wasn’t referring to you, but I perhaps didn’t make that clear.

Thank you.

Update: I’ve had some more success with this display - left display in photo.
My solar and battery charging status is looking good. I can draw shapes and images on the screen using the esphome graphics and interact with other HA devices using the buttons on the right side. Thats the good news.
The bad news in that I cant get the display working with LVGL properly. The device on the right in the photo shows the standard LVGL meter and displays fine on first boot. Any update to the screen, however, seems to corrupt the image. It looks like it’s creating an update section for the image and locating it at 0,0. This may be down to the device settings but I’ve tried many many changes and not got any further.
Has anyone seen this sort behaviour using LVGL?

Do you have it git adress where we can observe your updated code.
and maybe use it to make it beter. i order a display my self, so i hope in a few days i can try to build somthing myself

Hi Arnold,
Sorry no git address set up yet - very new here. I’ve only been successful in using standard esphome graphics as show in the screen above. Still haven’t got the LVGL working properly yet. But… I have also invested in this 7" Waveshare ( ESP32-S3-Touch-LCD-7) device. It’s taken a few days but tonight I have successfully got LVGL working on it. So, what I want to do next is go back to the 3.5" display and apply what I’ve learnt to see if I can progess it further. Let me know how you get on with your device.

I both the same display, using your link in the OP. I am having a hard time getting any meaningful response from it. I copied your code, but only set a screen full of colorful “static” and the following repeating errors in the logs:

[11:23:26][W][component:237]: Component display took a long time for an operation (52 ms).
[11:23:26][W][component:238]: Components should block for at most 30 ms.
[11:23:26][E][axs15231.touchscreen:040]: Failed to communicate!
[11:23:26][E][axs15231.touchscreen:040]: Failed to communicate!
[11:23:26][E][axs15231.touchscreen:040]: Failed to communicate!
[11:23:26][E][axs15231.touchscreen:040]: Failed to communicate!

Have you (or anyone else) seen that behavior? If so how did you solve it? I’m wondering if something changes in the hardware revisions, even though I used your original link to purchase…

TIA,
Terry

Hi Terry,
Make sure you copied my code not the S3 Long code as mentioned, that would give that coloured effect.
I’ve just recompiled mine as there has been a recent ESPHome update but all is working fine.
cheers
Derham

I’m an idiot. I did in fact copy the wrong code and just figured that out. Thanks for the reponse!! One question on the arial font though - do I need to copy that somewhere onto the device or in the ESS folder structure? I seem to be getting errors referencing it.

Terry

Great. Yes, I copied the arial.ttf file from my Windows laptop into the config\esphome folder. There are lots of Google fonts you can reference online, might be worth a look.
I’ve also modified the board config, using the ESP32S3BOX, which now looks like this…

################################################################################
# Board Configuration
################################################################################
esphome:
  name: ${device_internal_name}
  friendly_name: ${device_friendly_name}
  platformio_options:
    build_flags: "-DBOARD_HAS_PSRAM"
    board_build.arduino.memory_type: qio_opi

psram:
  mode: octal
  speed: 120MHz

esp32:
  board: esp32s3box
  variant: ESP32S3
  flash_size: 16MB
  framework:
    type: esp-idf
    sdkconfig_options:
      COMPILER_OPTIMIZATION_SIZE: y
      CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240: "y"
      CONFIG_ESP32S3_DATA_CACHE_64KB: "y"
      CONFIG_ESP32S3_DATA_CACHE_LINE_64B: "y"
      CONFIG_SPIRAM_FETCH_INSTRUCTIONS: y
      CONFIG_SPIRAM_RODATA: y
      CONFIG_ESPTOOLPY_FLASHSIZE_8MB: "y"

Derham

Still no success with LVGL though so it’s a pain working with the touchpad. The 7" device I referenced is a pleasure to do the LVGL although I can’t use the standard graphics on that, very strange.

i have my display up and runnning, so far so good.
now i’m looking into LVGL and how to use it, in the search for a bear minimum example

after some try, i have LVGL running, but the same problem like the topic starter.

What problem exactly?

If you want help, yaml and logs.

here is my yaml, after some trying it isn’t the niced code

log shows no error’s
the display seems to be very slow.
see a video on what is playing https://youtu.be/Yd0yymlYRNM

substitutions:
  name: esphome-web-220778
  friendly_name: WEKKER

color:
  - id: White
    red: 100%
    green: 100%
    blue: 100%
  - id: Black
    red: 0%
    green: 0%
    blue: 0%
  - id: Red
    red: 100%
    green: 0%
    blue: 0%

esphome:
  name: ${name}
  friendly_name: ${friendly_name}
  min_version: 2024.6.0
  name_add_mac_suffix: false
  platformio_options:
    build_flags: "-DBOARD_HAS_PSRAM"
    board_build.arduino.memory_type: qio_opi

  project:
    name: esphome.web
    version: dev


psram:
  mode: octal
  speed: 120MHz

esp32:
  board: esp32s3box
  variant: ESP32S3
  flash_size: 16MB
  framework:
    type: esp-idf
    sdkconfig_options:
      COMPILER_OPTIMIZATION_SIZE: y
      CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240: "y"
      CONFIG_ESP32S3_DATA_CACHE_64KB: "y"

external_components:
  - source: github://ageurtse/esphome-components
    components: [ axs15231 ]

# Enable logging
logger:
  logs:
    component: NONE
# Enable Home Assistant API
api:

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

# Allow provisioning Wi-Fi via serial
improv_serial:

wifi:
  # Set up a wifi access point
  ap: {}

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

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

spi:
  - id: display_qspi
    type: quad
    clk_pin: 47
    data_pins:
      - 21
      - 48
      - 40
      - 39

i2c:
  sda: 4
  scl: 8
  id: touchscreen_bus

switch:
  - platform: output
    name: "LCD LED"
    output: lcdbacklight
    id: switch_lcdbacklight
    restore_mode: ALWAYS_ON

output:
  - platform: gpio
    pin:
      number: GPIO01
      # allow_other_uses: true  
    id: lcdbacklight

display:
  - platform: axs15231
    id: main_display
    spi_id: display_qspi
 #   backlight_pin: GPIO01
    dimensions:
      height: 480
      width: 320
    cs_pin: 45
    reset_pin: 16
    transform:
      swap_xy: true
    rotation: 270
    auto_clear_enabled: false


touchscreen:
  - platform: axs15231
    id: main_touch
    display: main_display
    i2c_id: touchscreen_bus
    transform:
      swap_xy: false
    #  mirror_x: false
    #  mirror_y: 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
                );
time:
  - platform: homeassistant
    id: homeassistant_time
    on_time_sync:
      - script.execute: time_update
      - if:
          condition:
            lambda: 'return id(device_last_restart).state == "";'
          then:
          - text_sensor.template.publish:
              id: device_last_restart
              state: !lambda 'return id(homeassistant_time).now().strftime("%a %d %b %Y - %I:%M:%S %p");'
    # Update the clock every second
    on_time:
      - seconds: "*"
        then:
          - script.execute: time_update
 
script:
  - id: time_update
    then:
      - lvgl.indicator.update:
          id: second_hand
          value: !lambda |-
            return id(homeassistant_time).now().second;
      - lvgl.indicator.update:
          id: minute_hand
          value: !lambda |-
            return id(homeassistant_time).now().minute;
      - lvgl.indicator.update:
          id: hour_hand
          value: !lambda |-
            auto now = id(homeassistant_time).now();
            return std::fmod(now.hour, 12) * 60 + now.minute;   

text_sensor:
  - platform: version
    id: esphome_version
    name: "ESPHome Version"
  - platform: template
    name: 'Last Restart'
    id: device_last_restart
    icon: mdi:clock
    entity_category: diagnostic

binary_sensor:
  - platform: status
    name: "Status"
    entity_category: diagnostic  

lvgl:
  displays: 
    - main_display
  touchscreens: 
    - main_touch
  
  style_definitions:
    - id: no_checked_color
      bg_color: 0xCC5E14
      text_color: 0xB6B6B6

  theme:
    button:
      text_font: roboto24
      scroll_on_focus: true
      radius: 0px
      width: 150px
      height: 109px
      pad_all: 10px
      shadow_width: 0 # This is required even though the default is suposed to be 0
      bg_color: 0x313131
      text_color: 0xB6B6B6
      checked:
        bg_color: 0xCC5E14
        text_color: 0xB6B6B6

  page_wrap: false
  
  top_layer:
    widgets:
      - obj:
          id: boot_screen
          layout: 
            type: flex
            flex_flow: COLUMN_WRAP 
          width: 480px
          height: 320px
          text_font: roboto24
          scrollbar_mode: 'off'
          bg_color: White
          bg_opa: COVER
          radius: 0
          pad_all: 0
          widgets:
            - image:
                src: boot_logo
                pad_bottom: 0
            - label:
                id: wifi_signal_db_percent_label
                text: "WiFi Strength: 0%"
            - label:
                text:
                  format: "ESPHome Version: %s"
                  args: [ 'id(esphome_version).state.c_str()' ]
            - obj:
               radius: 0
               pad_all: 0
               border_width: 0
               width: 480px
               height: 320px
               flex_grow: 1           
               widgets:
                 - button:
                    align: center
                    radius: 15
                    width: 200
                    height: 100
                    checkable: true
                    widgets:
                     - label:
                         text_color: White
                         align: center
                         text: "OK"
                    on_press:
                     - lvgl.widget.hide: boot_screen
  pages:
    - id: main_page
      width: 100%
      bg_color: Black
      widgets:
        # Outer Clock Container
        - obj:
            height: 300px
            width: 300px
            align: center
            pad_all: 0
            bg_color: Black
            border_width: 0  
            widgets:
            - meter:
                height: 100%
                width: 100%
                align: center
                bg_color: White

                scales:
                  # Minor ticks
                  - ticks:
                      width: 2
                      count: 61
                      length: 10
                      color: Black
                    range_from: 0
                    range_to: 60
                    angle_range: 360
                    rotation: 270

                    # Minuite hand
                    indicators:
                      - line:
                          id: minute_hand
                          width: 3
                          color: Black
                          r_mod: -1
                  # Major ticks
                  - angle_range: 330
                    rotation: 300
                    range_from: 1
                    range_to: 12
                    ticks:
                      width: 3
                      count: 12
                      length: 20
                      color: Black                       

                  # Hour hand
                  - angle_range: 360
                    rotation: 270
                    range_from: 0
                    range_to: 720
                    indicators:
                      - line:
                          id: hour_hand
                          width: 4
                          color: Black
                          r_mod: -40

                  # Second hand
                  - angle_range: 360
                    rotation: 270
                    range_from: 0
                    range_to: 60
                    indicators:
                      - line:
                          id: second_hand
                          width: 2
                          color: Red
                          r_mod: -10
                          
# -------------------------------------------
# Graphics and Fonts
# -------------------------------------------
image:
  - file: https://esphome.io/_static/logo-text-on-light.svg
    id: boot_logo
    type: RGB565
    resize: 470x95
    use_transparency: true

font:
  - file: "gfonts://Roboto"
    id: roboto24
    size: 20
    bpp: 4
    extras:
      # http://materialdesignicons.com/cdn/7.4.47/
      - file: 'https://github.com/Templarian/MaterialDesign-Webfont/raw/v7.4.47/fonts/materialdesignicons-webfont.ttf'
        glyphs: [
          "\U000F004B",
          "\U0000f0ed",
          "\U000F006E",
          "\U000F012C",
          "\U000F179B",
          "\U000F0748",
          "\U000F1A1B",
          "\U000F02DC",
          "\U000F0A02",
          "\U000F035F",
          "\U000F0156",
          "\U000F0C5F",
          "\U000f0084",
          "\U000f0091",
        ]

edit: new video uploaded