ESP32 + HX8357d + LVGL + Touchscreen

Electronics

Building on this post, I’ll be adding touchscreen support. Checkout that post for details on the hardware and library edits prior to adding touchscreen support.

Since I’m working out of a different location I needed to point to the new sketch folders before downloading the new library required for Adafruit touchscreen:

Since the all the touchscreen support was removed it must be added back in. I went back to the Random Nerd Tutorials tutorial and pulled the code back in. I also used the Touchscreen example code to verify I was doing the read properly. I then edited it to use the resistive touch library from Adafruit. This article helped me understand the analog pin mapping to digital. I ran into a few runtime errors that certain pins I tried to use were only inputs, which I felt like should have been OK but was unable to get it to work using D33-36. To simplify things I opened up the TouchScreen demo sketch (touchscreendemo) and started debugging the pin selection from there, eventually settling on:

ESP32 Devkit PinTouchscreen Pin
27X+
A15X-
A19Y+
14Y-

As instructed by the comments in the demo code I measured the resistance between X+/X- pins (281 ohms) needed as an input to the library.

Running the sketch I calibrated the X/Y axis values by touching each of the corners multiple times and noting the values. Using the measured extremes and size of the screen I determined the mapping of the touchscreen’s x/y (p.x/p.y) to what the coordinates will be (x/y). It’s worth noting that because of the screen’s rotation compared to the static touchscreen the mapping swaps the x and y-axis.

    x = map(p.y, 970, 205, 1, SCREEN_WIDTH);
    y = map(p.x, 248, 875, 1, SCREEN_HEIGHT);

I pulled the verified touchscreen code from the demo sketch into the final sketch with LVGL. Compiled and the touch tests work as expected on the top button, the toggle button and the slider.

Complete code listing:

#include <lvgl.h>
#include <TFT_eSPI.h>
#include <TouchScreen.h>

#define SCREEN_WIDTH 480
#define SCREEN_HEIGHT 320

#define YP A19 // must be an analog pin, use "An" notation!
#define XM A15 // must be an analog pin, use "An" notation!
#define YM 14  // can be a digital pin
#define XP 27  // can be a digital pin

// Touchscreen coordinates: (x, y) and pressure (z)
// For better pressure precision, we need to know the resistance
// between X+ and X- Use any multimeter to read it
// For the one we're using, its 281 ohms across the X plate
TouchScreen ts = TouchScreen(XP, YP, XM, YM, 281);

int x, y, z;

#define DRAW_BUF_SIZE ((SCREEN_WIDTH * SCREEN_HEIGHT) / 10 * (LV_COLOR_DEPTH / 8))
uint32_t draw_buf[DRAW_BUF_SIZE / 4];

// If logging is enabled, it will inform the user about what is happening in the library
void log_print(lv_log_level_t level, const char * buf) {
  LV_UNUSED(level);
  Serial.println(buf);
  Serial.flush();
}

// Get the Touchscreen data
void touchscreen_read(lv_indev_t * indev, lv_indev_data_t * data) {

  // Get Touchscreen points
    TSPoint p = ts.getPoint();

  // Checks if Touchscreen was touched, and prints X, Y and Pressure (Z)
  if ( p.z > -1 ) { //ts.pressureThreshhold) {
    
    // Calibrate Touchscreen points with map function to the correct width and height
    x = map(p.y, 970, 205, 1, SCREEN_WIDTH);
    y = map(p.x, 248, 875, 1, SCREEN_HEIGHT);
    z = p.z;

    data->state = LV_INDEV_STATE_PRESSED;

    // Set the coordinates
    data->point.x = x;
    data->point.y = y;

    // Print Touchscreen info about X, Y and Pressure (Z) on the Serial Monitor
    Serial.print("X = ");
    Serial.print(x);
    Serial.print(" | Y = ");
    Serial.print(y);
    Serial.print(" | Pressure = ");
    Serial.print(z);
    Serial.println();
  }
  else {
    data->state = LV_INDEV_STATE_RELEASED;
  }
}

int btn1_count = 0;
// Callback that is triggered when btn1 is clicked
static void event_handler_btn1(lv_event_t * e) {
  lv_event_code_t code = lv_event_get_code(e);
  if(code == LV_EVENT_CLICKED) {
    btn1_count++;
    LV_LOG_USER("Button clicked %d", (int)btn1_count);
  }
}

// Callback that is triggered when btn2 is clicked/toggled
static void event_handler_btn2(lv_event_t * e) {
  lv_event_code_t code = lv_event_get_code(e);
  lv_obj_t * obj = (lv_obj_t*) lv_event_get_target(e);
  if(code == LV_EVENT_VALUE_CHANGED) {
    LV_UNUSED(obj);
    LV_LOG_USER("Toggled %s", lv_obj_has_state(obj, LV_STATE_CHECKED) ? "on" : "off");
  }
}

static lv_obj_t * slider_label;
// Callback that prints the current slider value on the TFT display and Serial Monitor for debugging purposes
static void slider_event_callback(lv_event_t * e) {
  lv_obj_t * slider = (lv_obj_t*) lv_event_get_target(e);
  char buf[8];
  lv_snprintf(buf, sizeof(buf), "%d%%", (int)lv_slider_get_value(slider));
  lv_label_set_text(slider_label, buf);
  lv_obj_align_to(slider_label, slider, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
  LV_LOG_USER("Slider changed to %d%%", (int)lv_slider_get_value(slider));
}

void lv_create_main_gui(void) {
  // Create a text label aligned center on top ("Hello, world!")
  lv_obj_t * text_label = lv_label_create(lv_screen_active());
  lv_label_set_long_mode(text_label, LV_LABEL_LONG_WRAP);    // Breaks the long lines
  lv_label_set_text(text_label, "Hello, world!");
  lv_obj_set_width(text_label, 150);    // Set smaller width to make the lines wrap
  lv_obj_set_style_text_align(text_label, LV_TEXT_ALIGN_CENTER, 0);
  lv_obj_align(text_label, LV_ALIGN_CENTER, 0, -90);

  lv_obj_t * btn_label;
  // Create a Button (btn1)
  lv_obj_t * btn1 = lv_button_create(lv_screen_active());
  lv_obj_add_event_cb(btn1, event_handler_btn1, LV_EVENT_ALL, NULL);
  lv_obj_align(btn1, LV_ALIGN_CENTER, 0, -50);
  lv_obj_remove_flag(btn1, LV_OBJ_FLAG_PRESS_LOCK);

  btn_label = lv_label_create(btn1);
  lv_label_set_text(btn_label, "Button");
  lv_obj_center(btn_label);

  // Create a Toggle button (btn2)
  lv_obj_t * btn2 = lv_button_create(lv_screen_active());
  lv_obj_add_event_cb(btn2, event_handler_btn2, LV_EVENT_ALL, NULL);
  lv_obj_align(btn2, LV_ALIGN_CENTER, 0, 10);
  lv_obj_add_flag(btn2, LV_OBJ_FLAG_CHECKABLE);
  lv_obj_set_height(btn2, LV_SIZE_CONTENT);

  btn_label = lv_label_create(btn2);
  lv_label_set_text(btn_label, "Toggle");
  lv_obj_center(btn_label);
  
  // Create a slider aligned in the center bottom of the TFT display
  lv_obj_t * slider = lv_slider_create(lv_screen_active());
  lv_obj_align(slider, LV_ALIGN_CENTER, 0, 60);
  lv_obj_add_event_cb(slider, slider_event_callback, LV_EVENT_VALUE_CHANGED, NULL);
  lv_slider_set_range(slider, 0, 100);
  lv_obj_set_style_anim_duration(slider, 2000, 0);

  // Create a label below the slider to display the current slider value
  slider_label = lv_label_create(lv_screen_active());
  lv_label_set_text(slider_label, "0%");
  lv_obj_align_to(slider_label, slider, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
}

void setup() {
  String LVGL_Arduino = String("LVGL Library Version: ") + lv_version_major() + "." + lv_version_minor() + "." + lv_version_patch();
  Serial.begin(115200);
  Serial.println(LVGL_Arduino);

  // Set ADC resolution required for TouchScreen library
  analogReadResolution(10);

  // Start LVGL
  lv_init();
  // Register print function for debugging
  lv_log_register_print_cb(log_print);

  // Create a display object
  lv_display_t * disp;
  // Initialize the TFT display using the TFT_eSPI library
  disp = lv_tft_espi_create(SCREEN_WIDTH, SCREEN_HEIGHT, draw_buf, sizeof(draw_buf));
  
  // Initialize an LVGL input device object (Touchscreen)
  lv_indev_t * indev = lv_indev_create();
  lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
  // Set the callback function to read Touchscreen input
  lv_indev_set_read_cb(indev, touchscreen_read);

  // Function to draw the GUI (text, buttons and sliders)
  lv_create_main_gui();
}

void loop() 
{
  lv_task_handler();  // let the GUI do its work
  lv_tick_inc(5);     // tell LVGL how much time has passed
  delay(5);           // let this time pass
}

Leave a Reply

Your email address will not be published. Required fields are marked *

Scroll to top