#include "display.h" #include "board.h" #include "sdkconfig.h" #include "driver/spi_master.h" #include "driver/ledc.h" #include "esp_lcd_panel_io.h" #include "esp_lcd_panel_vendor.h" #include "esp_lcd_panel_ops.h" #include "esp_lvgl_port.h" #include "esp_log.h" static const char *TAG = "display"; #define LCD_SPI_HOST SPI2_HOST #define BL_TIMER LEDC_TIMER_0 #define BL_CHANNEL LEDC_CHANNEL_0 static esp_lcd_panel_handle_t panel; static void backlight_init(void) { ledc_timer_config_t timer = { .speed_mode = LEDC_LOW_SPEED_MODE, .duty_resolution = LEDC_TIMER_10_BIT, .timer_num = BL_TIMER, .freq_hz = 5000, .clk_cfg = LEDC_AUTO_CLK, }; ESP_ERROR_CHECK(ledc_timer_config(&timer)); ledc_channel_config_t ch = { .gpio_num = board->lcd_bl, .speed_mode = LEDC_LOW_SPEED_MODE, .channel = BL_CHANNEL, .timer_sel = BL_TIMER, .duty = 0, .hpoint = 0, }; ESP_ERROR_CHECK(ledc_channel_config(&ch)); } void display_set_backlight(uint8_t percent) { if (percent > 100) percent = 100; // perceptual-ish curve so 50% doesn't look like 90% uint32_t duty = (1023u * percent * percent) / 10000u; ledc_set_duty(LEDC_LOW_SPEED_MODE, BL_CHANNEL, duty); ledc_update_duty(LEDC_LOW_SPEED_MODE, BL_CHANNEL); } void display_init(void) { backlight_init(); spi_bus_config_t bus = { .mosi_io_num = board->lcd_mosi, .miso_io_num = board->lcd_miso, .sclk_io_num = board->lcd_sclk, .quadwp_io_num = -1, .quadhd_io_num = -1, .max_transfer_sz = LCD_H_RES * 48 * sizeof(uint16_t), }; ESP_ERROR_CHECK(spi_bus_initialize(LCD_SPI_HOST, &bus, SPI_DMA_CH_AUTO)); esp_lcd_panel_io_handle_t io = NULL; esp_lcd_panel_io_spi_config_t io_cfg = { .cs_gpio_num = board->lcd_cs, .dc_gpio_num = board->lcd_dc, .spi_mode = 0, .pclk_hz = CONFIG_WIDGET_LCD_PCLK_MHZ * 1000 * 1000, .trans_queue_depth = 10, .lcd_cmd_bits = 8, .lcd_param_bits = 8, }; ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_SPI_HOST, &io_cfg, &io)); esp_lcd_panel_dev_config_t panel_cfg = { .reset_gpio_num = board->lcd_rst, .rgb_ele_order = board->lcd_bgr ? LCD_RGB_ELEMENT_ORDER_BGR : LCD_RGB_ELEMENT_ORDER_RGB, .bits_per_pixel = 16, }; ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io, &panel_cfg, &panel)); ESP_ERROR_CHECK(esp_lcd_panel_reset(panel)); ESP_ERROR_CHECK(esp_lcd_panel_init(panel)); ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true)); // IPS panel, TFT_INVERSION_ON // TFT_eSPI setRotation(3) == MADCTL MV|MY; the 170 px axis sits at RAM column 35. ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, true)); ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, false, true)); ESP_ERROR_CHECK(esp_lcd_panel_set_gap(panel, 0, 35)); ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true)); lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG(); port_cfg.task_stack = 10240; port_cfg.task_affinity = 1; ESP_ERROR_CHECK(lvgl_port_init(&port_cfg)); lvgl_port_display_cfg_t disp_cfg = { .io_handle = io, .panel_handle = panel, .buffer_size = LCD_H_RES * 40, .double_buffer = true, .hres = LCD_H_RES, .vres = LCD_V_RES, .monochrome = false, .rotation = {.swap_xy = true, .mirror_x = false, .mirror_y = true}, .color_format = LV_COLOR_FORMAT_RGB565, .flags = {.buff_dma = true, .buff_spiram = false, .swap_bytes = true}, }; lv_display_t *disp = lvgl_port_add_disp(&disp_cfg); if (!disp) { ESP_LOGE(TAG, "lvgl_port_add_disp failed"); abort(); } ESP_LOGI(TAG, "ST7789 %dx%d up at %d MHz", LCD_H_RES, LCD_V_RES, CONFIG_WIDGET_LCD_PCLK_MHZ); } void display_off(void) { display_set_backlight(0); if (panel) esp_lcd_panel_disp_on_off(panel, false); } void display_set_flipped(bool flipped) { if (!panel) return; // Rotation 3 (MV|MY) is the stock orientation; rotation 1 (MV|MX) is its 180° twin. // The 170-px window is centred in the 240-column RAM, so the 35-px gap is the same both ways. lvgl_port_lock(0); esp_lcd_panel_mirror(panel, flipped, !flipped); lv_obj_invalidate(lv_screen_active()); lvgl_port_unlock(); }