From 9e912048ab44d876c52ea770d411184b6d398dcf Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 15 Feb 2025 09:40:02 -0500 Subject: [PATCH] Checkpoint adding SSD1306 and PanelDevice. --- esp/cpp/07_lcd-panel/main/display.cpp | 58 +++++++++---------- esp/cpp/07_lcd-panel/main/display.h | 83 +++++++++++++++++++++------ esp/cpp/07_lcd-panel/main/main.cpp | 1 + 3 files changed, 96 insertions(+), 46 deletions(-) diff --git a/esp/cpp/07_lcd-panel/main/display.cpp b/esp/cpp/07_lcd-panel/main/display.cpp index 010faf1..64f0a90 100644 --- a/esp/cpp/07_lcd-panel/main/display.cpp +++ b/esp/cpp/07_lcd-panel/main/display.cpp @@ -11,7 +11,7 @@ #include #include -// To use LV_COLOR_FORMAT_I1, we need an extra buffer to hold the converted data +// To use LV_COLOR_FORMAT_I1 we need an extra buffer to hold the converted data. uint8_t Display::oled_buffer_[LCD_H_RES * LCD_V_RES / 8]; // LVGL library is not thread-safe, this example calls LVGL APIs from tasks. @@ -26,13 +26,14 @@ Display::Display(const I2C &i2c) : lv_init(); } - // create a lvgl display + // Create a lvgl display. + // TODO: Use PanelDevice functions display_ = lv_display_create(LCD_H_RES, LCD_V_RES); // associate the i2c panel handle to the display lv_display_set_user_data(display_, panel_.get()); - // create draw buffer + // Create draw buffer. ESP_LOGI(TAG, "Allocate separate LVGL draw buffers"); - // LVGL reserves 2 x 4 bytes in the buffer, as these are assumed to be used as a palette. + // TODO: Use PanelDevice functions size_t draw_buffer_sz = LCD_H_RES * LCD_V_RES / 8 + LVGL_PALETTE_SIZE; buf_ = heap_caps_calloc(1, draw_buffer_sz, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); @@ -40,11 +41,11 @@ Display::Display(const I2C &i2c) : // LVGL9 suooprt new monochromatic format. lv_display_set_color_format(display_, LV_COLOR_FORMAT_I1); - // initialize LVGL draw buffers + // Initialize LVGL draw buffers. lv_display_set_buffers(display_, buf_, nullptr, draw_buffer_sz, LV_DISPLAY_RENDER_MODE_FULL); lv_display_set_rotation(display_, LV_DISPLAY_ROTATION_0); - // set the callback which can copy the rendered image to an area of the display + // Set callback which can copy the rendered image to an area of the display. lv_display_set_flush_cb(display_, Display::lvgl_flush_cb); ESP_LOGI(TAG, @@ -57,6 +58,7 @@ Display::Display(const I2C &i2c) : esp_lcd_panel_io_register_event_callbacks(panel_.io_handle_, &cbs, display_)); + // TODO: What is this ESP_LOGI(TAG, "Use esp_timer as LVGL tick timer"); const esp_timer_create_args_t lvgl_tick_timer_args = { .callback = &Display::lvgl_increase_tick, @@ -109,8 +111,10 @@ void Display::lvgl_flush_cb(lv_display_t *display, const lv_area_t *area, auto panel_handle = (esp_lcd_panel_handle_t) lv_display_get_user_data(display); - // This is necessary because LVGL reserves 2 x 4 bytes in the buffer, as these are assumed to be used as a palette. Skip the palette here - // More information about the monochrome, please refer to https://docs.lvgl.io/9.2/porting/display.html#monochrome-displays + // Necessary because LVGL reserves 2x4 bytes in the buffer for a palette. + // For more information about the monochrome, please refer to: + // https://docs.lvgl.io/9.2/porting/display.html#monochrome-displays + // Skip the palette here. px_map += LVGL_PALETTE_SIZE; uint16_t hor_res = lv_display_get_physical_horizontal_resolution(display); @@ -121,7 +125,7 @@ void Display::lvgl_flush_cb(lv_display_t *display, const lv_area_t *area, for (int32_t y = y1; y <= y2; y++) { for (int32_t x = x1; x <= x2; x++) { - /* The order of bits is MSB first + /* The order of bits is MSB first. MSB LSB bits 7 6 5 4 3 2 1 0 pixels 0 1 2 3 4 5 6 7 @@ -130,8 +134,8 @@ void Display::lvgl_flush_cb(lv_display_t *display, const lv_area_t *area, bool chroma_color = (px_map[(hor_res >> 3) * y + (x >> 3)] & 1 << (7 - x % 8)); - /* Write to the buffer as required for the display. - * It writes only 1-bit for monochrome displays mapped vertically.*/ + // Write to the buffer as required for the display. + // It writes only 1-bit for monochrome displays mapped vertically. uint8_t *buf = oled_buffer_ + hor_res * (y >> 3) + (x); if (chroma_color) { (*buf) &= ~(1 << (y % 8)); @@ -140,19 +144,19 @@ void Display::lvgl_flush_cb(lv_display_t *display, const lv_area_t *area, } } } - // pass the draw buffer to the driver + // Pass the draw buffer to the driver. ESP_ERROR_CHECK( esp_lcd_panel_draw_bitmap(panel_handle, x1, y1, x2 + 1, y2 + 1, oled_buffer_)); } -void Display::lvgl_increase_tick(void *arg) +void Display::lvgl_increase_tick(void *) { - /* Tell LVGL how many milliseconds has elapsed */ + // Tell LVGL how many milliseconds has elapsed lv_tick_inc(LVGL_TICK_PERIOD_MS); } -void Display::lvgl_port_task(void *arg) +void Display::lvgl_port_task(void *) { ESP_LOGI(TAG, "Starting LVGL task"); uint32_t time_till_next_ms = 0; @@ -164,14 +168,6 @@ void Display::lvgl_port_task(void *arg) } } -//SSD1306::SSD1306() { -// -//} -//void *SSD1306::vendor_config() -//{ -// return &ssd1306_config_; -//} - I2C::I2C() : i2c_bus_(nullptr) { ESP_LOGI(TAG, "Initialize I2C bus"); @@ -203,18 +199,22 @@ Panel::Panel(i2c_master_bus_handle_t i2c) : io_handle_(nullptr), panel_(nullptr) esp_lcd_new_panel_io_i2c(i2c, &io_config, &io_handle_)); ESP_LOGI(TAG, "Install SSD1306 panel driver"); - ssd1306_config_ = { - .height = LCD_V_RES, - }; + SSD1306 ssd1306({.height = LCD_V_RES}); panel_config_ = { .reset_gpio_num = PIN_RST, .bits_per_pixel = 1, - .vendor_config = &ssd1306_config_ }; - ESP_ERROR_CHECK( - esp_lcd_new_panel_ssd1306(io_handle_, &panel_config_, &panel_)); + ssd1306.create_panel(panel_config_, io_handle_, panel_); ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); ESP_ERROR_CHECK(esp_lcd_panel_init(panel_)); ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); } + +SSD1306::SSD1306(esp_lcd_panel_ssd1306_config_t ssd_config, + int width, + int height) : + PanelDevice(width, height) +{ + vendor_config_ = ssd_config; +} diff --git a/esp/cpp/07_lcd-panel/main/display.h b/esp/cpp/07_lcd-panel/main/display.h index 1045ad1..3f14fc1 100644 --- a/esp/cpp/07_lcd-panel/main/display.h +++ b/esp/cpp/07_lcd-panel/main/display.h @@ -6,6 +6,7 @@ #include #include #include +#include #include "misc/lv_types.h" #include "misc/lv_area.h" #include "display/lv_display.h" @@ -19,11 +20,11 @@ static const char *TAG = "lcd-panel"; -// https://www.digikey.com/en/products/detail/winstar-display/WEA012864DWPP3N00003/20533255 // 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 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 @@ -31,7 +32,7 @@ static const char *TAG = "lcd-panel"; #define LCD_CMD_BITS 8 #define LCD_PARAM_BITS 8 -// Pin may vary based on your schematic +// Pin may vary based on your schematic. #define PIN_SDA GPIO_NUM_21 #define PIN_SCL GPIO_NUM_22 #define PIN_RST -1 @@ -54,6 +55,67 @@ struct ScopedLock { 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); @@ -77,8 +139,6 @@ public: private: esp_lcd_panel_dev_config_t panel_config_; - - esp_lcd_panel_ssd1306_config_t ssd1306_config_; }; class Display { @@ -125,15 +185,4 @@ private: std::unordered_map objects_; }; -//class SSD1306 : public Display { -//public: -// SSD1306(); -// -// void * vendor_config() override; -// -//private: -// esp_lcd_panel_dev_config_t panel_config_; -// esp_lcd_panel_ssd1306_config_t ssd1306_config_; -//}; - #endif // DISPLAY_H diff --git a/esp/cpp/07_lcd-panel/main/main.cpp b/esp/cpp/07_lcd-panel/main/main.cpp index 55f7ec1..b0a3528 100644 --- a/esp/cpp/07_lcd-panel/main/main.cpp +++ b/esp/cpp/07_lcd-panel/main/main.cpp @@ -1,5 +1,6 @@ #include "display.h" +// TODO: Can this be static since there can only be one initialization? I2C i2c; void setup()