#ifndef DISPLAY_H #define DISPLAY_H #include #include #include #include #include #include #include "misc/lv_types.h" #include "misc/lv_area.h" #include "display/lv_display.h" #include "widgets/label/lv_label.h" #define I2C_BUS_PORT 0 #define LVGL_TICK_PERIOD_MS 5 #define LVGL_TASK_STACK_SIZE (4 * 1024) #define LVGL_TASK_PRIORITY 2 #define LVGL_PALETTE_SIZE 8 static const char *TAG = "lcd-panel"; // According to SSD1306 datasheet // https://www.digikey.com/en/products/detail/winstar-display/WEA012864DWPP3N00003/20533255 // Bit number used to represent command and parameter #define SCREEN_WIDTH 128 // OLED display width, in pixels. #define SCREEN_HEIGHT 64 // OLED display height, in pixels. #define LCD_H_RES SCREEN_WIDTH #define LCD_V_RES SCREEN_HEIGHT #define I2C_HW_ADDR 0x3C #define LCD_PIXEL_CLOCK_HZ (400 * 1000) #define LCD_CMD_BITS 8 #define LCD_PARAM_BITS 8 // Pin may vary based on your schematic. #define PIN_SDA GPIO_NUM_21 #define PIN_SCL GPIO_NUM_22 #define PIN_RST -1 struct I2C { I2C(); ~I2C() = default; i2c_master_bus_handle_t i2c_bus_; }; struct ScopedLock { explicit ScopedLock() { _lock_acquire(&lock_); } ~ScopedLock() { _lock_release(&lock_); } // LVGL library is not thread-safe, this example calls LVGL APIs from tasks. // We must use a mutex to protect it. static _lock_t lock_; }; template class PanelDevice { public: PanelDevice(size_t width, size_t height) : width_(width), height_(height) { } ~PanelDevice() = default; [[nodiscard]] size_t get_width() const { return width_; } [[nodiscard]] size_t get_height() const { return height_; } [[nodiscard]] size_t get_area() const { return width_ * height_; } [[nodiscard]] size_t get_draw_buffer_size() const { // LVGL reserves 2x4 bytes in the buffer to be used as a palette. return width_ * height_ / 8 + LVGL_PALETTE_SIZE; } [[nodiscard]] lv_display_t *create_display() const { return lv_display_create(width_, height_); } [[nodiscard]] T &get_vendor_config() const { return vendor_config_; } void create_panel(esp_lcd_panel_dev_config_t &panel_config, esp_lcd_panel_io_handle_t io_handle, esp_lcd_panel_handle_t &panel_handle); private: size_t width_; size_t height_; protected: T vendor_config_; }; template void PanelDevice::create_panel(esp_lcd_panel_dev_config_t &config, esp_lcd_panel_io_handle_t io, esp_lcd_panel_handle_t &panel) { // If the passed handle is already allocated, delete it. if (panel != nullptr) { esp_lcd_panel_del(panel); } // Set the vendor config and allocate a new panel handle. config.vendor_config = &vendor_config_; ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(io, &config, &panel)); } class SSD1306 : public PanelDevice { public: explicit SSD1306(esp_lcd_panel_ssd1306_config_t ssd_config, int width = SCREEN_WIDTH, int height = SCREEN_HEIGHT); ~SSD1306() = default; }; class Panel { public: explicit Panel(i2c_master_bus_handle_t i2c); explicit Panel(const I2C &i2c) : Panel(i2c.i2c_bus_) { } ~Panel() = default; [[nodiscard]] inline const esp_lcd_panel_t *get() const { return panel_; } [[nodiscard]] inline esp_lcd_panel_t *get() { return panel_; } [[nodiscard]] inline const esp_lcd_panel_t * operator*() const { return get(); } [[nodiscard]] inline esp_lcd_panel_t *operator*() { return get(); } esp_lcd_panel_io_handle_t io_handle_; esp_lcd_panel_handle_t panel_; private: esp_lcd_panel_dev_config_t panel_config_; }; class Display { public: explicit Display(const I2C &i2c); ~Display() = default; [[nodiscard]] inline const lv_display_t *get() const { return display_; } [[nodiscard]] inline lv_display_t *get() { return display_; } [[nodiscard]] inline const lv_display_t *operator*() const { return get(); } [[nodiscard]] inline lv_display_t *operator*() { return get(); } void set_text(const char *text, const char *name, lv_label_long_mode_t long_mode = LV_LABEL_LONG_SCROLL_CIRCULAR, lv_align_t align = LV_ALIGN_TOP_MID); static bool lvgl_flush_ready(esp_lcd_panel_io_handle_t panel, esp_lcd_panel_io_event_data_t *data, void *user_ctx); static void lvgl_flush_cb(lv_display_t *display, const lv_area_t *area, uint8_t *px_map); static void lvgl_increase_tick(void *arg); static void lvgl_port_task(void *arg); // For LV_COLOR_FORMAT_I1 we need an extra buffer to hold the converted data. static uint8_t oled_buffer_[LCD_H_RES * LCD_V_RES / 8]; private: Panel panel_; lv_display_t *display_; void *buf_; std::unordered_map objects_; }; #endif // DISPLAY_H