Here you go. This is pseudo code, it has not been tested and is more than likely going to have some glitches and typos in it that need to be fixed.
If you have any issues lemme know.
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_timer.h"
#include "esp_heap_caps.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_lcd_st7796.h"
#include "esp_lcd_touch_xpt2046.h"
#include "driver/spi_common.h"
#include "driver/gpio.h"
#include "rom/gpio.h"
#include "driver/spi_master.h"
#include "soc/spi_pins.h"
#include "lvgl.h"
/*
* application settings
*/
#define TICK_PERIOD_MS 2
/*
* Display settings
*/
#define WIDTH 480
#define HEIGHT 320
#define COLOR_DEPTH 16
/*
* Pin definitions for the display
* if you have the ability to choose the pins you are using
* I strongly suggest using the hardware assigned pins as these are going
* to be faster. I have set the pin numbers to the best ones to use for
* getting the best performance
*/
#define LCD_HOST SPI2_HOST
#define LCD_FREQ (80 * 1000 * 1000)
#define LCD_BKL_ON 1
#define LCD_RST_ON 1
#define LCD_CLK SPI2_IOMUX_PIN_NUM_CLK // 14
#define LCD_MOSI SPI2_IOMUX_PIN_NUM_MOSI // 13
#define LCD_MISO SPI2_IOMUX_PIN_NUM_MISO // 12
#define LCD_DC 16
#define LCD_RST 17
#define LCD_CS SPI2_IOMUX_PIN_NUM_CS // 15
#define LCD_BKL 18
// change this to SPICOMMON_BUSFLAG_GPIO_PINS if
// not using the hardware SPI pin assignments
#define LCD_MUX SPICOMMON_BUSFLAG_IOMUX_PINS
/*
* Touch panel pin definitions.
* These can sometimes share the same "bus" as the display
* if that is what is done then the only thing you will need to change
* is TP_CS, TP_RST, TP_RST_ON and possibly TP_FREQ
*/
#define TP_HOST LCD_HOST
#define TP_FREQ (1 * 1000 * 1000)
#define TP_MISO LCD_MISO
#define TP_MOSI LCD_MOSI
#define TP_CLK LCD_CLK
#define TP_CS 0
#define TP_RST 0
#define TP_RST_ON 1
// If the bus that is used is different for the touch panel then set this to
// SPICOMMON_BUSFLAG_IOMUX_PINS if using hardware assigned SPI pins otherwise
// set it to SPICOMMON_BUSFLAG_GPIO_PINS
#define TP_MUX LCD_MUX
#define _LCD_SPI_FLAGS SPICOMMON_BUSFLAG_MASTER | LCD_MUX
#define _TP_SPI_FLAGS SPICOMMON_BUSFLAG_MASTER | TP_MUX
#define _LCD_BUFFER_SIZE (WIDTH * HEIGHT * (COLOR_DEPTH / 8) / 10)
// forward declarations
void tick_task_loop(void *pvParameters);
void app_task_loop(void *pvParameters);
void indev_read_cb(lv_indev_t *drv, lv_indev_data_t *data);
void display_rotate_cb(lv_event_t * e);
void display_flush_cb(lv_display_t *drv, lv_area_t *area, uint8_t *data);
bool bus_trans_done_cb(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx);
void create_gui(void);
void init_drivers(void);
void *buf1;
void *buf2;
lv_display_t *disp;
lv_indev_t *indev;
esp_lcd_panel_handle_t lcd_panel;
esp_lcd_spi_bus_handle_t lcd_spi_bus_handle;
esp_lcd_panel_io_handle_t lcd_panel_io_handle;
spi_bus_config_t lcd_spi_bus_config;
spi_bus_config_t tp_spi_bus_config;
esp_lcd_panel_io_spi_config_t lcd_panel_io_config;
esp_lcd_panel_dev_config_t lcd_panel_dev_config;
esp_lcd_touch_config_t tp_cfg;
esp_lcd_touch_handle_t tp;
esp_lcd_panel_io_handle_t tp_io_handle;
esp_lcd_panel_io_spi_config_t tp_io_config;
TaskHandle_t app_task = NULL;
TaskHandle_t tick_task = NULL;
#if LCD_BKL != -1
gpio_config_t bk_gpio_config;
#endif
// this is the startup code
void app_main(void)
{
#if LCD_BKL != -1
bk_gpio_config = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = 1ULL << LCD_BKL
};
ESP_ERROR_CHECK(gpio_config(&bk_gpio_config));
// turn off backlight, don't need to see the display doing anything
// funky while it is being initilized
gpio_set_level(LCD_BKL, !LCD_BKL_ON);
#endif
// initilize the drivers
init_drivers();
#if LCD_BKL != -1
// turn the backlight on
gpio_set_level(LCD_BKL, LCD_BKL_ON);
#endif
// create freertos tasks to handle the looping code, This allows us to
// have pauses in the loops without causing a spinning wheel scenario that
// eats up porocessor time.
xTaskCreate(app_task_loop, "APPLOOP", STACK_SIZE, NULL, tskIDLE_PRIORITY, &app_task);
xTaskCreate(tick_task_loop, "TICKLOOP", STACK_SIZE, NULL, tskIDLE_PRIORITY, &tick_task);
vTaskStartScheduler();
}
// This sets up the hardware drivers for the display and also the touch panel
// It also creates the software driver in LVGL for both as well.
void init_drivers(void)
{
buf1 = heap_caps_malloc(_LCD_BUFFER_SIZE, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
buf2 = heap_caps_malloc(_LCD_BUFFER_SIZE, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
lv_init();
// create the LVGL display
disp = lv_display_create(WIDTH, HEIGHT);
lcd_spi_bus_handle = (esp_lcd_spi_bus_handle_t)((uint32_t)LCD_HOST);
lcd_spi_bus_config = (spi_bus_config_t){
.sclk_io_num = LCD_CLK,
.mosi_io_num = LCD_MOSI,
.miso_io_num = LCD_MISO,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.data4_io_num = -1,
.data5_io_num = -1,
.data6_io_num = -1,
.data7_io_num = -1,
.flags = SPICOMMON_BUSFLAG_MASTER | LCD_MUX,
.max_transfer_sz = _LCD_BUFFER_SIZE
};
tp_spi_bus_config = (spi_bus_config_t){
.sclk_io_num = TP_CLK,
.mosi_io_num = TP_MOSI,
.miso_io_num = TP_MISO,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.data4_io_num = -1,
.data5_io_num = -1,
.data6_io_num = -1,
.data7_io_num = -1,
.flags = SPICOMMON_BUSFLAG_MASTER | TP_MUX,
.max_transfer_sz = 512
};
lcd_panel_io_config = (esp_lcd_panel_io_spi_config_t){
.trans_queue_depth = 1,
.cs_gpio_num = LCD_CS,
.dc_gpio_num = LCD_DC,
.spi_mode = 0,
.user_ctx = disp,
.pclk_hz = LCD_FREQ,
.on_color_trans_done = &bus_trans_done_cb,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.flags = {
.dc_low_on_data = 0,
.sio_mode = 0,
.lsb_first = 0,
.cs_high_active = 0,
.octal_mode = 0
}
}
tp_io_config = (esp_lcd_panel_io_spi_config_t){
.trans_queue_depth = 1,
.cs_gpio_num = TP_CS,
.dc_gpio_num = -1,
.spi_mode = 0,
.user_ctx = NULL,
.pclk_hz = TP_FREQ,
.on_color_trans_done = NULL,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.flags = {
.dc_low_on_data = 0,
.sio_mode = 0,
.lsb_first = 0,
.cs_high_active = 0,
.octal_mode = 0
}
}
tp_cfg = (esp_lcd_touch_config_t){
.x_max = WIDTH,
.y_max = HEIGHT,
.rst_gpio_num = TP_RST,
.int_gpio_num = -1,
.levels = {
.reset = TP_RST_ON
},
.flags = {
.swap_xy = 0,
.mirror_x = 0,
.mirror_y = 0
}
};
lcd_panel_dev_config = (esp_lcd_panel_dev_config_t){
.reset_gpio_num = LCD_RST,
.rgb_endian = LCD_RGB_ENDIAN_RGB,
.bits_per_pixel = COLOR_DEPTH,
.flags = { .reset_active_high = LCD_RST_ON },
.vendor_config = NULL
}
if (LCD_HOST == SPI2_HOST) {
ESP_ERROR_CHECK(spi_bus_initialize(LCD_HOST, &spi_bus_config, SPI_DMA_CH1));
} else {
ESP_ERROR_CHECK(spi_bus_initialize(LCD_HOST, &spi_bus_config, SPI_DMA_CH2));
}
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(lcd_spi_bus_handle, &lcd_panel_io_config, &lcd_panel_io_handle));
ESP_ERROR_CHECK(esp_lcd_new_panel_st7796(lcd_panel_io_handle, &lcd_panel_dev_config, &lcd_panel));
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(lcd_panel, true));
ESP_ERROR_CHECK(esp_lcd_panel_reset(lcd_panel));
ESP_ERROR_CHECK(esp_lcd_panel_init(lcd_panel));
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)TP_HOST, &tp_io_config, &tp_io_handle));
ESP_ERROR_CHECK(esp_lcd_touch_new_spi_xpt2046(tp_io_handle, &tp_cfg, &tp));
lv_display_set_flush_cb(disp, display_flush_cb);
lv_display_set_buffers(buf1, buf2, _LCD_BUFFER_SIZE, LV_RENDER_MODE_PARTIAL);
lv_display_set_driver_data(disp, lcd_panel);
lv_display_add_event_cb(disp, &display_rotate_cb, LV_EVENT_RESOLUTION_CHANGED, lcd_panel);
if (COLOR_DEPTH == 16) {
lv_display_set_color_format(disp, LV_COLOR_FORMAT_RGB565);
} else {
lv_display_set_color_format(disp, LV_COLOR_FORMAT_RGB888);
}
indev = lv_indev_create(WIDTH, HEIGHT);
lv_indev_set_read_cb(indev, &indev_read_cb);
lv_indev_set_display(indev, disp);
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
lv_indev_enable(indev, true);
lv_indev_set_driver_data(indev, tp);
lv_display_set_user_data(disp, indev);
}
// place the gui code here
void create_gui(void)
{
// User code for GUI creation goes here
lv_obj_t *scrn = lv_screen_active(disp);
lv_obj_t *slider = lv_slider_create(scrn);
lv_obj_set_size(slider, WIDTH - 30, HEIGHT / 10);
lv_obj_center(slider);
}
// because we are using DMA memory and double buffering we need to know when the
// transfer has completed. This is done so LVGL doesn't write to a buffer that
// is being transmitted to a display. DMA memory allows a buffer to be transmitted
// without using the CPU. This allows the program to continue running doing other
// things at the same time as the buffer being sent.
bool bus_trans_done_cb(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
lv_display_t *disp_driver = (lv_display_t *)user_ctx;
lv_display_flush_ready(disp_driver);
return false;
}
// handles flushing the buffer data to the display
void display_flush_cb(lv_display_t *drv, lv_area_t *area, uint8_t *data)
{
#if COLOR_DEPTH == 16
// this is needed for SPI RGB565 displays. It is because the pixel
// order is reverse when the data gets sent, so we need to swap the
// bytes in each pixel
lv_draw_sw_rgb565_swap(data, (uint32_t)_LCD_BUFFER_SIZE);
#endif
// copy a buffer's content to a specific area of the display
esp_lcd_panel_draw_bitmap(lcd_panel, (int)area->x1, (int)area->y1, (int)area->x2 + 1, (int)area->y2 + 1, data);
}
/* Rotate display and touch, when rotated screen in LVGL. Called when driver parameters are updated. */
void display_rotate_cb(lv_event_t * e)
{
lv_display_t *d = (lv_display_t *)lv_event_get_user_data(e);
esp_lcd_panel_handle_t lcd = (esp_lcd_panel_handle_t)lv_display_get_driver_data(d);
lv_indev_t *idv = (lv_indev_t *)lv_display_get_user_data(d);
esp_lcd_touch_handle_t t = (esp_lcd_touch_handle_t)lv_indev_get_driver_data(idv);
switch (lv_display_get_rotation(d)) {
case LV_DISP_ROT_NONE:
// Rotate LCD display
esp_lcd_panel_swap_xy(lcd, false);
esp_lcd_panel_mirror(lcd, true, false);
// Rotate LCD touch
esp_lcd_touch_set_mirror_y(t, false);
esp_lcd_touch_set_mirror_x(t, false);
break;
case LV_DISP_ROT_90:
// Rotate LCD display
esp_lcd_panel_swap_xy(lcd, true);
esp_lcd_panel_mirror(lcd, true, true);
// Rotate LCD touch
esp_lcd_touch_set_mirror_y(t, false);
esp_lcd_touch_set_mirror_x(t, false);
break;
case LV_DISP_ROT_180:
// Rotate LCD display
esp_lcd_panel_swap_xy(lcd, false);
esp_lcd_panel_mirror(lcd, false, true);
// Rotate LCD touch
esp_lcd_touch_set_mirror_y(t, false);
esp_lcd_touch_set_mirror_x(t, false);
break;
case LV_DISP_ROT_270:
// Rotate LCD display
esp_lcd_panel_swap_xy(lcd, true);
esp_lcd_panel_mirror(lcd, false, false);
// Rotate LCD touch
esp_lcd_touch_set_mirror_y(t, false);
esp_lcd_touch_set_mirror_x(t, false);
break;
}
}
int32_t last_touch_x = -1;
int32_t last_touch_y = -1;
void indev_read_cb(lv_indev_t *drv, lv_indev_data_t *data)
{
uint16_t touchpad_x[1] = {0};
uint16_t touchpad_y[1] = {0};
uint8_t touchpad_cnt = 0;
esp_lcd_touch_handle_t t = (esp_lcd_touch_handle_t)lv_indev_get_driver_data(drv);
/* Read touch controller data */
esp_lcd_touch_read_data(t);
/* Get coordinates */
bool touchpad_pressed = esp_lcd_touch_get_coordinates(t, touchpad_x, touchpad_y, NULL, &touchpad_cnt, 1);
if (touchpad_pressed && touchpad_cnt > 0) {
last_touch_x = (int32_t)touchpad_x[0];
last_touch_y = (int32_t)touchpad_y[0];
data->state = LV_INDEV_STATE_PRESSED;
} else {
data->state = LV_INDEV_STATE_RELEASED;
}
data->point.x = last_touch_x;
data->point.y = last_touch_y;
}
void tick_task_loop(void *pvParameters)
{
uint32_t *pulNotificationValue;
while (1) {
vTaskDelay(pdMS_TO_TICKS(LVGL_TICK_PERIOD_MS));
/* Tell LVGL how many milliseconds has elapsed */
lv_tick_inc(LVGL_TICK_PERIOD_MS);
xTaskNotify(main_task, 1, eSetValueWithOverwrite);
}
}
void app_task_loop(void *pvParameters)
{
uint32_t *pulNotificationValue;
create_gui();
while (1) {
// this is a stall until a tick increment has occured. when that has happened
// this will get released to update the display
// if you have other things that need to be done like checking pins
// you will more than likely want to change this from a wait to a read
// and if the value has been set then run the timer handler.
// The purpose to this is to not have the CPU doing a spinning wheels
// when doing nothing. This allows other things to function better and faster.
xTaskNotifyWait(ULONG_MAX, ULONG_MAX, pulNotificationValue, portMAX_DELAY)
lv_timer_handler();
}
}