#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_timer.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "driver/gpio.h"
#include "driver/i2c.h"
#include "esp_err.h"
#include "esp_log.h"
// #include "esp_dma_utils.h"
#include "lvgl.h"
static const char *TAG = "SHC24_CHALL";
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// Please update the following configuration according to your LCD spec //////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#define PIN_NUM_DATA0 17
#define PIN_NUM_DATA1 18
#define PIN_NUM_DATA2 8
#define PIN_NUM_DATA3 3
#define PIN_NUM_DATA4 9
#define PIN_NUM_DATA5 10
#define PIN_NUM_DATA6 11
#define PIN_NUM_DATA7 12
#define PIN_NUM_WR 15
#define PIN_NUM_RD 16
#define PIN_NUM_CS 6
#define PIN_NUM_DC 7
#define PIN_NUM_RST 4
#define PIN_NUM_LCD_POWER 1
#define PIN_NUM_BK_LIGHT 2
#define PIN_NUM_BUTTON1 13
#define PIN_NUM_BUTTON2 14
#define PIN_NUM_BUTTON3 21
#define PIN_NUM_BUTTON4 47
#define LCD_H_RES 320
#define LCD_V_RES 240
#define LCD_PIXEL_CLOCK_HZ (15 * 1000 * 1000)
#define LVGL_TICK_PERIOD_MS 2
#define LVGL_TASK_MAX_DELAY_MS 500
#define LVGL_TASK_MIN_DELAY_MS 1
#define LVGL_TASK_STACK_SIZE (4 * 1024)
#define LVGL_TASK_PRIORITY 2
static SemaphoreHandle_t lvgl_mux = NULL;
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_display_t *disp = (lv_display_t *) user_ctx;
lv_disp_flush_ready(disp);
return false;
}
static void lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map) {
esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t) lv_display_get_user_data(disp);
int offsetx1 = area->x1;
int offsetx2 = area->x2;
int offsety1 = area->y1;
int offsety2 = area->y2;
// copy a buffer's content to a specific area of the display
esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, px_map);
}
static void increase_lvgl_tick(void *arg) {
/* Tell LVGL how many milliseconds has elapsed */
lv_tick_inc(LVGL_TICK_PERIOD_MS);
}
bool lvgl_lock(int timeout_ms) {
// Convert timeout in milliseconds to FreeRTOS ticks
// If `timeout_ms` is set to -1, the program will block until the condition is met
const TickType_t timeout_ticks = (timeout_ms == -1) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms);
return xSemaphoreTakeRecursive(lvgl_mux, timeout_ticks) == pdTRUE;
}
void lvgl_unlock(void) {
xSemaphoreGiveRecursive(lvgl_mux);
}
static void lvgl_port_task(void *arg) {
ESP_LOGI(TAG, "Starting LVGL task");
uint32_t task_delay_ms = LVGL_TASK_MAX_DELAY_MS;
while (1) {
// Lock the mutex due to the LVGL APIs are not thread-safe
if (lvgl_lock(-1)) {
task_delay_ms = lv_timer_handler();
// Release the mutex
lvgl_unlock();
}
if (task_delay_ms > LVGL_TASK_MAX_DELAY_MS) {
task_delay_ms = LVGL_TASK_MAX_DELAY_MS;
} else if (task_delay_ms < LVGL_TASK_MIN_DELAY_MS) {
task_delay_ms = LVGL_TASK_MIN_DELAY_MS;
}
vTaskDelay(pdMS_TO_TICKS(task_delay_ms));
}
}
void init_i80_bus(esp_lcd_panel_io_handle_t *io_handle, void *user_ctx) {
ESP_LOGI(TAG, "Initialize Intel 8080 bus");
esp_lcd_i80_bus_handle_t i80_bus = NULL;
esp_lcd_i80_bus_config_t bus_config = {
.clk_src = LCD_CLK_SRC_DEFAULT,
.dc_gpio_num = PIN_NUM_DC,
.wr_gpio_num = PIN_NUM_WR,
.data_gpio_nums = {
PIN_NUM_DATA0,
PIN_NUM_DATA1,
PIN_NUM_DATA2,
PIN_NUM_DATA3,
PIN_NUM_DATA4,
PIN_NUM_DATA5,
PIN_NUM_DATA6,
PIN_NUM_DATA7
},
.bus_width = 8,
.max_transfer_bytes = LCD_H_RES * LCD_V_RES * sizeof(uint16_t) * 3,
.psram_trans_align = 64,
.sram_trans_align = 4,
};
ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus));
esp_lcd_panel_io_i80_config_t io_config = {
.cs_gpio_num = PIN_NUM_CS,
.pclk_hz = LCD_PIXEL_CLOCK_HZ,
.trans_queue_depth = 4,
.dc_levels = {
.dc_idle_level = 0,
.dc_cmd_level = 0,
.dc_dummy_level = 0,
.dc_data_level = 1,
},
.flags = {
.swap_color_bytes = 1, // Swap can be done in LvGL (default) or DMA
},
.on_color_trans_done = notify_lvgl_flush_ready,
.user_ctx = user_ctx,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, io_handle));
}
void init_lcd_panel(esp_lcd_panel_io_handle_t io_handle, esp_lcd_panel_handle_t *panel) {
esp_lcd_panel_handle_t panel_handle = NULL;
ESP_LOGI(TAG, "Install LCD driver of st7789");
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = PIN_NUM_RST,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
.bits_per_pixel = 16,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));
esp_lcd_panel_reset(panel_handle);
esp_lcd_panel_init(panel_handle);
esp_lcd_panel_invert_color(panel_handle, true);
esp_lcd_panel_swap_xy(panel_handle, true);
esp_lcd_panel_mirror(panel_handle, true, false);
*panel = panel_handle;
}
int count = 0;
static void event_handler(lv_event_t * e) {
lv_event_code_t code = lv_event_get_code(e);
// ESP_LOGI(TAG, "Event code: %d", code);
if(code == LV_EVENT_CLICKED) {
LV_LOG_USER("Clicked");
}
else if(code == LV_EVENT_VALUE_CHANGED) {
LV_LOG_USER("Toggled");
}
}
void button_read(lv_indev_t *indev, lv_indev_data_t* data){
static uint32_t last_btn = 0;
int btn_pr = -1;
if (gpio_get_level(PIN_NUM_BUTTON1) == 1) {
btn_pr = 0;
} else if (gpio_get_level(PIN_NUM_BUTTON2) == 1) {
btn_pr = 1;
} else if (gpio_get_level(PIN_NUM_BUTTON3) == 1) {
btn_pr = 2;
} else if (gpio_get_level(PIN_NUM_BUTTON4) == 1) {
btn_pr = 3;
}
if (btn_pr >= 0) {
last_btn = btn_pr;
data->state = LV_INDEV_STATE_PRESSED;
} else {
data->state = LV_INDEV_STATE_RELEASED;
}
data->btn_id = last_btn;
}
void lvgl_demo_ui(lv_disp_t *disp) {
lv_obj_t *scr = lv_display_get_screen_active(disp);
lv_obj_set_size(scr, LCD_H_RES, LCD_V_RES);
lv_obj_set_scrollbar_mode(scr, LV_SCROLLBAR_MODE_OFF);
lv_obj_remove_flag(scr, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_remove_flag(scr, LV_OBJ_FLAG_SCROLL_ON_FOCUS);
lv_obj_set_style_bg_color(scr, lv_color_hex(0x003a57), LV_PART_MAIN);
lv_obj_t *label_hello_world = lv_label_create(scr);
lv_label_set_text(label_hello_world, "Hello world!");
lv_obj_set_style_text_color(scr, lv_color_hex(0xffffff), LV_PART_MAIN);
lv_obj_align(label_hello_world, LV_ALIGN_TOP_MID, 0, 0);
const char *btns[] = {"Button1", "Button2", "Button3", "Button4"};
for (int i = 0; i < 4; i++) {
lv_obj_t *btn = lv_btn_create(scr);
lv_obj_add_event_cb(btn, event_handler, LV_EVENT_ALL, NULL);
lv_obj_align(btn, LV_ALIGN_BOTTOM_LEFT, 2 + i * 80, 3);
lv_obj_set_size(btn, 80 - 5, 25);
lv_obj_remove_flag(btn, LV_OBJ_FLAG_PRESS_LOCK);
lv_obj_t *label = lv_label_create(btn);
lv_label_set_text(label, btns[i]);
lv_obj_center(label);
}
// lv_obj_t *spinner = lv_spinner_create(scr);
// lv_obj_set_size(spinner, 100, 100);
// lv_obj_center(spinner);
// lv_spinner_set_anim_params(spinner, 10000, 200);
}
void app_main(void) {
ESP_LOGI(TAG, "Initialize LVGL library");
lv_init();
lv_display_t *disp = lv_display_create(LCD_H_RES, LCD_V_RES);
lv_color_t *buf1 = NULL;
lv_color_t *buf2 = NULL;
uint32_t malloc_flags = ESP_DMA_MALLOC_FLAG_PSRAM;
ESP_ERROR_CHECK(esp_dma_malloc(LCD_H_RES * LCD_V_RES * sizeof(lv_color_t), malloc_flags, (void *) &buf1, NULL));
ESP_ERROR_CHECK(esp_dma_malloc(LCD_H_RES * LCD_V_RES * sizeof(lv_color_t), malloc_flags, (void *) &buf2, NULL));
assert(buf1 && buf2);
ESP_LOGI(TAG, "buf1@%p, buf2@%p", buf1, buf2);
lv_display_set_buffers(disp, buf1, buf2, LCD_H_RES * LCD_V_RES * sizeof(lv_color_t), LV_DISPLAY_RENDER_MODE_FULL);
lv_display_set_flush_cb(disp, lvgl_flush_cb);
lv_indev_t *indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_BUTTON);
lv_indev_set_read_cb(indev, button_read);
static const lv_point_t button_points[] = {
{40, 230},
{120, 230},
{200, 230},
{280, 230}
};
lv_indev_set_button_points(indev, button_points);
ESP_LOGI(TAG, "Turn on LCD power");
gpio_config_t power_gpio_config = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = 1ULL << PIN_NUM_LCD_POWER
};
ESP_ERROR_CHECK(gpio_config(&power_gpio_config));
gpio_set_level(PIN_NUM_LCD_POWER, 1);
gpio_config_t lcd_rd_gpio_config = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = 1ULL << PIN_NUM_RD
};
ESP_ERROR_CHECK(gpio_config(&lcd_rd_gpio_config));
gpio_set_level(PIN_NUM_RD, 1);
ESP_LOGI(TAG, "Turn off LCD backlight");
gpio_config_t bk_gpio_config = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = 1ULL << PIN_NUM_BK_LIGHT
};
ESP_ERROR_CHECK(gpio_config(&bk_gpio_config));
gpio_set_level(PIN_NUM_BK_LIGHT, 0);
esp_lcd_panel_io_handle_t io_handle = NULL;
init_i80_bus(&io_handle, disp);
esp_lcd_panel_handle_t panel_handle = NULL;
init_lcd_panel(io_handle, &panel_handle);
// Stub: user can flush pre-defined pattern to the screen before we turn on the screen or backlight
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
ESP_LOGI(TAG, "Turn on LCD backlight");
gpio_set_level(PIN_NUM_BK_LIGHT, 1);
ESP_LOGI(TAG, "Prepare Button GPIOs");
gpio_config_t button_gpio_config = {
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_ENABLE,
.pin_bit_mask = (1ULL << PIN_NUM_BUTTON1) | (1ULL << PIN_NUM_BUTTON2) | (1ULL << PIN_NUM_BUTTON3) | (1ULL << PIN_NUM_BUTTON4)
};
ESP_ERROR_CHECK(gpio_config(&button_gpio_config));
ESP_LOGI(TAG, "Set LVGL display user data");
lv_display_set_user_data(disp, panel_handle);
ESP_LOGI(TAG, "Install LVGL tick timer");
// Tick interface for LVGL (using esp_timer to generate 2ms periodic event)
const esp_timer_create_args_t lvgl_tick_timer_args = {
.callback = &increase_lvgl_tick,
.name = "lvgl_tick"
};
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_TICK_PERIOD_MS * 1000));
lvgl_mux = xSemaphoreCreateRecursiveMutex();
assert(lvgl_mux);
ESP_LOGI(TAG, "Create LVGL task");
xTaskCreate(lvgl_port_task, "LVGL", LVGL_TASK_STACK_SIZE, NULL, LVGL_TASK_PRIORITY, NULL);
ESP_LOGI(TAG, "Display LVGL animation");
// Lock the mutex due to the LVGL APIs are not thread-safe
if (lvgl_lock(-1)) {
lvgl_demo_ui(disp);
// Release the mutex
lvgl_unlock();
}
}