282 lines
11 KiB
C++
282 lines
11 KiB
C++
#ifndef TFT_HPP
|
|
#define TFT_HPP
|
|
|
|
/*
|
|
* Adapted from an example under the MIT license:
|
|
* Copyright © 2022 atanisoft (github.com/atanisoft)
|
|
*
|
|
* MIT LICENSE:
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include <driver/gpio.h>
|
|
#include <driver/ledc.h>
|
|
#include <driver/spi_master.h>
|
|
#include <esp_err.h>
|
|
#include <esp_freertos_hooks.h>
|
|
#include <esp_log.h>
|
|
#include <esp_lcd_panel_io.h>
|
|
#include <esp_lcd_panel_vendor.h>
|
|
#include <esp_lcd_panel_ops.h>
|
|
#include <esp_lcd_ili9488.h>
|
|
#include <esp_timer.h>
|
|
#include <freertos/FreeRTOS.h>
|
|
#include <freertos/task.h>
|
|
#include <lvgl.h>
|
|
#include <stdio.h>
|
|
#include "sdkconfig.h"
|
|
|
|
// Uncomment the following line to enable using double buffering of LVGL color
|
|
// data.
|
|
// #define USE_DOUBLE_BUFFERING 1
|
|
|
|
static const char *TFT_TAG = "tft_driver";
|
|
|
|
#define DISPLAY_HORIZONTAL_PIXELS 320
|
|
#define DISPLAY_VERTICAL_PIXELS 480
|
|
#define DISPLAY_COMMAND_BITS 8
|
|
#define DISPLAY_PARAMETER_BITS 8
|
|
#define DISPLAY_REFRESH_HZ 40000000
|
|
#define DISPLAY_SPI_QUEUE_LEN 10
|
|
#define SPI_MAX_TRANSFER_SIZE 32768
|
|
|
|
#define TFT_PIN_MOSI GPIO_NUM_17
|
|
#define TFT_PIN_MISO GPIO_NUM_18
|
|
#define TFT_PIN_CLK GPIO_NUM_16
|
|
#define TFT_PIN_CS GPIO_NUM_NC
|
|
#define TFT_PIN_DC GPIO_NUM_15
|
|
#define TFT_PIN_RESET GPIO_NUM_8
|
|
|
|
#define TFT_INVERT_COLOR false
|
|
|
|
// Default to 50 lines of color data
|
|
#define LV_BUFFER_SIZE DISPLAY_HORIZONTAL_PIXELS * 50
|
|
#define LVGL_UPDATE_PERIOD_MS 5
|
|
|
|
#define BACKLIGHT_LEDC_MODE LEDC_LOW_SPEED_MODE
|
|
#define BACKLIGHT_LEDC_CHANNEL LEDC_CHANNEL_0
|
|
#define BACKLIGHT_LEDC_TIMER LEDC_TIMER_1
|
|
#define BACKLIGHT_LEDC_TIMER_RESOLUTION LEDC_TIMER_10_BIT
|
|
#define BACKLIGHT_LEDC_FRQUENCY 5000
|
|
|
|
static esp_lcd_panel_io_handle_t lcd_io_handle = NULL;
|
|
static esp_lcd_panel_handle_t lcd_handle = NULL;
|
|
|
|
static lv_disp_draw_buf_t lv_disp_buf;
|
|
static lv_disp_drv_t lv_disp_drv;
|
|
static lv_disp_t *lv_display = NULL;
|
|
static lv_color_t *lv_buf_1 = NULL;
|
|
static lv_color_t *lv_buf_2 = NULL;
|
|
static lv_obj_t *meter = NULL;
|
|
static lv_style_t style_screen;
|
|
|
|
static void update_meter_value(void *indic, int32_t v)
|
|
{
|
|
lv_meter_set_indicator_end_value(meter, (lv_meter_indicator_t*)indic, v);
|
|
}
|
|
|
|
static bool notify_lvgl_flush_ready(
|
|
esp_lcd_panel_io_handle_t panel_io,
|
|
esp_lcd_panel_io_event_data_t *edata,
|
|
void *user_ctx
|
|
) {
|
|
lv_disp_drv_t *disp_driver = (lv_disp_drv_t *)user_ctx;
|
|
lv_disp_flush_ready(disp_driver);
|
|
return false;
|
|
}
|
|
|
|
static void lvgl_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map) {
|
|
esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t) drv->user_data;
|
|
|
|
int offsetx1 = area->x1;
|
|
int offsetx2 = area->x2;
|
|
int offsety1 = area->y1;
|
|
int offsety2 = area->y2;
|
|
esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, color_map);
|
|
}
|
|
|
|
static void IRAM_ATTR lvgl_tick_cb(void *param) {
|
|
lv_tick_inc(LVGL_UPDATE_PERIOD_MS);
|
|
}
|
|
|
|
void initialize_spi() {
|
|
ESP_LOGI(TFT_TAG, "Initializing SPI bus (MOSI:%d, MISO:%d, CLK:%d)",
|
|
TFT_PIN_MOSI, TFT_PIN_MISO, TFT_PIN_CLK);
|
|
|
|
spi_bus_config_t bus = {
|
|
.mosi_io_num = TFT_PIN_MOSI,
|
|
.miso_io_num = TFT_PIN_MISO,
|
|
.sclk_io_num = TFT_PIN_CLK,
|
|
.quadwp_io_num = GPIO_NUM_NC,
|
|
.quadhd_io_num = GPIO_NUM_NC,
|
|
.data4_io_num = GPIO_NUM_NC,
|
|
.data5_io_num = GPIO_NUM_NC,
|
|
.data6_io_num = GPIO_NUM_NC,
|
|
.data7_io_num = GPIO_NUM_NC,
|
|
.max_transfer_sz = SPI_MAX_TRANSFER_SIZE,
|
|
.flags = SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MISO |
|
|
SPICOMMON_BUSFLAG_MOSI | SPICOMMON_BUSFLAG_MASTER,
|
|
.isr_cpu_id = ESP_INTR_CPU_AFFINITY_AUTO,
|
|
.intr_flags = ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM
|
|
};
|
|
|
|
ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &bus, SPI_DMA_CH_AUTO));
|
|
}
|
|
|
|
void initialize_display() {
|
|
const esp_lcd_panel_io_spi_config_t io_config = {
|
|
.cs_gpio_num = TFT_PIN_CS,
|
|
.dc_gpio_num = TFT_PIN_DC,
|
|
.spi_mode = 0,
|
|
.pclk_hz = DISPLAY_REFRESH_HZ,
|
|
.trans_queue_depth = DISPLAY_SPI_QUEUE_LEN,
|
|
.on_color_trans_done = notify_lvgl_flush_ready,
|
|
.user_ctx = &lv_disp_drv,
|
|
.lcd_cmd_bits = DISPLAY_COMMAND_BITS,
|
|
.lcd_param_bits = DISPLAY_PARAMETER_BITS,
|
|
.flags = {
|
|
.dc_high_on_cmd = 0, /*!< If enabled, DC level = 1 indicates command transfer */
|
|
.dc_low_on_data = 0, /*!< If enabled, DC level = 0 indicates color data transfer */
|
|
.dc_low_on_param = 0, /*!< If enabled, DC level = 0 indicates parameter transfer */
|
|
.octal_mode = 0, /*!< transmit with octal mode (8 data lines), this mode is used to simulate Intel 8080 timing */
|
|
.quad_mode = 0, /*!< transmit with quad mode (4 data lines), this mode is useful when transmitting LCD parameters (Only use one line for command) */
|
|
.sio_mode = 0, /*!< Read and write through a single data line (MOSI) */
|
|
.lsb_first = 0, /*!< transmit LSB bit first */
|
|
.cs_high_active = 0, /*!< CS line is high active */
|
|
}
|
|
};
|
|
|
|
|
|
const esp_lcd_panel_dev_config_t lcd_config = {
|
|
.reset_gpio_num = TFT_PIN_RESET,
|
|
.color_space = LCD_RGB_ELEMENT_ORDER_BGR,
|
|
.data_endian = LCD_RGB_DATA_ENDIAN_BIG,
|
|
.bits_per_pixel = 18,
|
|
.flags = {
|
|
.reset_active_high = 0
|
|
},
|
|
.vendor_config = NULL
|
|
};
|
|
|
|
ESP_ERROR_CHECK(
|
|
esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)SPI2_HOST, &io_config, &lcd_io_handle));
|
|
|
|
ESP_ERROR_CHECK(esp_lcd_new_panel_ili9488(lcd_io_handle, &lcd_config, LV_BUFFER_SIZE, &lcd_handle));
|
|
|
|
ESP_ERROR_CHECK(esp_lcd_panel_reset(lcd_handle));
|
|
ESP_ERROR_CHECK(esp_lcd_panel_init(lcd_handle));
|
|
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(lcd_handle, false));
|
|
ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(lcd_handle, false));
|
|
ESP_ERROR_CHECK(esp_lcd_panel_mirror(lcd_handle, true, false));
|
|
ESP_ERROR_CHECK(esp_lcd_panel_set_gap(lcd_handle, 0, 0));
|
|
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
|
ESP_ERROR_CHECK(esp_lcd_panel_disp_off(lcd_handle, false));
|
|
#else
|
|
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(lcd_handle, true));
|
|
#endif
|
|
}
|
|
|
|
void initialize_lvgl() {
|
|
ESP_LOGI(TFT_TAG, "Initializing LVGL");
|
|
lv_init();
|
|
ESP_LOGI(TFT_TAG, "Allocating %zu bytes for LVGL buffer", LV_BUFFER_SIZE * sizeof(lv_color_t));
|
|
lv_buf_1 = (lv_color_t *)heap_caps_malloc(LV_BUFFER_SIZE * sizeof(lv_color_t), MALLOC_CAP_DMA);
|
|
#if USE_DOUBLE_BUFFERING
|
|
ESP_LOGI(TFT_TAG, "Allocating %zu bytes for second LVGL buffer", LV_BUFFER_SIZE * sizeof(lv_color_t));
|
|
lv_buf_2 = (lv_color_t *)heap_caps_malloc(LV_BUFFER_SIZE * sizeof(lv_color_t), MALLOC_CAP_DMA);
|
|
#endif
|
|
ESP_LOGI(TFT_TAG, "Creating LVLG display buffer");
|
|
lv_disp_draw_buf_init(&lv_disp_buf, lv_buf_1, lv_buf_2, LV_BUFFER_SIZE);
|
|
|
|
ESP_LOGI(TFT_TAG, "Initializing %dx%d display", DISPLAY_HORIZONTAL_PIXELS, DISPLAY_VERTICAL_PIXELS);
|
|
lv_disp_drv_init(&lv_disp_drv);
|
|
lv_disp_drv.hor_res = DISPLAY_HORIZONTAL_PIXELS;
|
|
lv_disp_drv.ver_res = DISPLAY_VERTICAL_PIXELS;
|
|
lv_disp_drv.flush_cb = lvgl_flush_cb;
|
|
lv_disp_drv.draw_buf = &lv_disp_buf;
|
|
lv_disp_drv.user_data = lcd_handle;
|
|
lv_display = lv_disp_drv_register(&lv_disp_drv);
|
|
|
|
ESP_LOGI(TFT_TAG, "Creating LVGL tick timer");
|
|
const esp_timer_create_args_t lvgl_tick_timer_args = {
|
|
.callback = &lvgl_tick_cb,
|
|
.arg = NULL,
|
|
.dispatch_method = ESP_TIMER_TASK,
|
|
.name = "lvgl_tick",
|
|
.skip_unhandled_events = false
|
|
};
|
|
esp_timer_handle_t lvgl_tick_timer = NULL;
|
|
ESP_ERROR_CHECK(esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer));
|
|
ESP_ERROR_CHECK(esp_timer_start_periodic(lvgl_tick_timer, LVGL_UPDATE_PERIOD_MS * 1000));
|
|
}
|
|
|
|
void create_demo_ui() {
|
|
lv_obj_t *scr = lv_disp_get_scr_act(NULL);
|
|
|
|
// Set the background color of the display to black.
|
|
lv_style_init(&style_screen);
|
|
lv_style_set_bg_color(&style_screen, lv_color_black());
|
|
lv_obj_add_style(lv_scr_act(), &style_screen, LV_STATE_DEFAULT);
|
|
|
|
// Create a meter which can be animated.
|
|
meter = lv_meter_create(scr);
|
|
lv_obj_center(meter);
|
|
lv_obj_set_size(meter, 200, 200);
|
|
|
|
// Add a scale first
|
|
lv_meter_scale_t *scale = lv_meter_add_scale(meter);
|
|
lv_meter_set_scale_ticks(meter, scale, 41, 2, 10, lv_palette_main(LV_PALETTE_GREY));
|
|
lv_meter_set_scale_major_ticks(meter, scale, 8, 4, 15, lv_color_black(), 10);
|
|
|
|
lv_meter_indicator_t *indic;
|
|
|
|
// Add a blue arc to the start
|
|
indic = lv_meter_add_arc(meter, scale, 3, lv_palette_main(LV_PALETTE_BLUE), 0);
|
|
lv_meter_set_indicator_start_value(meter, indic, 0);
|
|
lv_meter_set_indicator_end_value(meter, indic, 20);
|
|
|
|
// Make the tick lines blue at the start of the scale
|
|
indic = lv_meter_add_scale_lines(meter, scale, lv_palette_main(LV_PALETTE_BLUE), lv_palette_main(LV_PALETTE_BLUE), false, 0);
|
|
lv_meter_set_indicator_start_value(meter, indic, 0);
|
|
lv_meter_set_indicator_end_value(meter, indic, 20);
|
|
|
|
// Add a red arc to the end
|
|
indic = lv_meter_add_arc(meter, scale, 3, lv_palette_main(LV_PALETTE_RED), 0);
|
|
lv_meter_set_indicator_start_value(meter, indic, 80);
|
|
lv_meter_set_indicator_end_value(meter, indic, 100);
|
|
|
|
// Make the tick lines red at the end of the scale
|
|
indic = lv_meter_add_scale_lines(meter, scale, lv_palette_main(LV_PALETTE_RED), lv_palette_main(LV_PALETTE_RED), false, 0);
|
|
lv_meter_set_indicator_start_value(meter, indic, 80);
|
|
lv_meter_set_indicator_end_value(meter, indic, 100);
|
|
|
|
// Add a needle line indicator
|
|
indic = lv_meter_add_needle_line(meter, scale, 4, lv_palette_main(LV_PALETTE_GREY), -10);
|
|
|
|
// Create an animation to set the value
|
|
lv_anim_t a;
|
|
lv_anim_init(&a);
|
|
lv_anim_set_exec_cb(&a, update_meter_value);
|
|
lv_anim_set_var(&a, indic);
|
|
lv_anim_set_values(&a, 0, 100);
|
|
lv_anim_set_time(&a, 2000);
|
|
lv_anim_set_repeat_delay(&a, 100);
|
|
lv_anim_set_playback_time(&a, 500);
|
|
lv_anim_set_playback_delay(&a, 100);
|
|
lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
|
|
lv_anim_start(&a);
|
|
}
|
|
|
|
void init_tft() {
|
|
initialize_spi();
|
|
initialize_display();
|
|
initialize_lvgl();
|
|
|
|
ESP_LOGI(TFT_TAG, "TFT initialization Successful");
|
|
}
|
|
|
|
#endif /* TFT_HPP */ |