diff --git a/esp/cpp/07_lcd-panel/CMakeLists.txt b/esp/cpp/07_lcd-panel/CMakeLists.txt index b2a289e..d70f297 100644 --- a/esp/cpp/07_lcd-panel/CMakeLists.txt +++ b/esp/cpp/07_lcd-panel/CMakeLists.txt @@ -9,7 +9,7 @@ include($ENV{IDF_PATH}/tools/cmake/project.cmake) project( #[[NAME]] lcd-panel VERSION 0.1 - DESCRIPTION "Example of using the SSDLCD" + DESCRIPTION "Example of using the SSD1306 LCD display with ESP-IDF and LVGL" LANGUAGES CXX ) # For writing pure cmake components, see the documentation diff --git a/esp/cpp/07_lcd-panel/main/CMakeLists.txt b/esp/cpp/07_lcd-panel/main/CMakeLists.txt index 096aef1..300bcbb 100644 --- a/esp/cpp/07_lcd-panel/main/CMakeLists.txt +++ b/esp/cpp/07_lcd-panel/main/CMakeLists.txt @@ -1,5 +1,5 @@ idf_component_register( - SRCS main.cpp display.cpp - INCLUDE_DIRS "." + SRCS main.cpp display.cpp panel_device.cpp ssd1306.cpp + INCLUDE_DIRS . REQUIRES driver ) diff --git a/esp/cpp/07_lcd-panel/main/display.cpp b/esp/cpp/07_lcd-panel/main/display.cpp index 64f0a90..1392934 100644 --- a/esp/cpp/07_lcd-panel/main/display.cpp +++ b/esp/cpp/07_lcd-panel/main/display.cpp @@ -1,6 +1,8 @@ -#include "display.h" -#include "widgets/label/lv_label.h" +#include "display.h" +#include "ssd1306.h" + +#include "widgets/label/lv_label.h" #include #include #include @@ -11,9 +13,6 @@ #include #include -// 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. // We must use a mutex to protect it. _lock_t ScopedLock::lock_; @@ -136,7 +135,7 @@ void Display::lvgl_flush_cb(lv_display_t *display, const lv_area_t *area, // 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); + uint8_t *buf = SSD1306::oled_buffer_ + hor_res * (y >> 3) + (x); if (chroma_color) { (*buf) &= ~(1 << (y % 8)); } else { @@ -147,7 +146,7 @@ void Display::lvgl_flush_cb(lv_display_t *display, const lv_area_t *area, // 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_)); + SSD1306::oled_buffer_)); } void Display::lvgl_increase_tick(void *) @@ -156,11 +155,10 @@ void Display::lvgl_increase_tick(void *) lv_tick_inc(LVGL_TICK_PERIOD_MS); } -void Display::lvgl_port_task(void *) +[[noreturn]] void Display::lvgl_port_task(void *) { ESP_LOGI(TAG, "Starting LVGL task"); - uint32_t time_till_next_ms = 0; - while (1) { + for (uint32_t time_till_next_ms = 0; true;) { _lock_acquire(&ScopedLock::lock_); time_till_next_ms = lv_timer_handler(); _lock_release(&ScopedLock::lock_); @@ -184,7 +182,15 @@ I2C::I2C() : i2c_bus_(nullptr) ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &i2c_bus_)); } -Panel::Panel(i2c_master_bus_handle_t i2c) : io_handle_(nullptr), panel_(nullptr) +Panel::Panel(i2c_master_bus_handle_t i2c) : + io_handle_(nullptr), + panel_(nullptr), + panel_config_( + { + .reset_gpio_num = PIN_RST, + .bits_per_pixel = 1, + } + ) { ESP_LOGI(TAG, "Install panel IO"); esp_lcd_panel_io_i2c_config_t io_config = { @@ -200,21 +206,9 @@ Panel::Panel(i2c_master_bus_handle_t i2c) : io_handle_(nullptr), panel_(nullptr) ESP_LOGI(TAG, "Install SSD1306 panel driver"); SSD1306 ssd1306({.height = LCD_V_RES}); - panel_config_ = { - .reset_gpio_num = PIN_RST, - .bits_per_pixel = 1, - }; 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 3f14fc1..51ec9f9 100644 --- a/esp/cpp/07_lcd-panel/main/display.h +++ b/esp/cpp/07_lcd-panel/main/display.h @@ -12,31 +12,15 @@ #include "display/lv_display.h" #include "widgets/label/lv_label.h" +#include "panel_device.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(); @@ -55,67 +39,6 @@ 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); @@ -170,10 +93,7 @@ public: 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]; + [[noreturn]] static void lvgl_port_task(void *arg); private: Panel panel_; diff --git a/esp/cpp/07_lcd-panel/main/panel_device.cpp b/esp/cpp/07_lcd-panel/main/panel_device.cpp new file mode 100644 index 0000000..a4a4d96 --- /dev/null +++ b/esp/cpp/07_lcd-panel/main/panel_device.cpp @@ -0,0 +1,2 @@ + +#include "panel_device.h" diff --git a/esp/cpp/07_lcd-panel/main/panel_device.h b/esp/cpp/07_lcd-panel/main/panel_device.h new file mode 100644 index 0000000..b7220f0 --- /dev/null +++ b/esp/cpp/07_lcd-panel/main/panel_device.h @@ -0,0 +1,65 @@ +#ifndef PANEL_DEVICE_H +#define PANEL_DEVICE_H + +#include +#include +#include +#include "display/lv_display.h" + +#define LVGL_PALETTE_SIZE 8 + +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_; + // TODO: This needs moved to SSD1306 + ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(io, &config, &panel)); +} + +#endif // PANEL_DEVICE_H diff --git a/esp/cpp/07_lcd-panel/main/ssd1306.cpp b/esp/cpp/07_lcd-panel/main/ssd1306.cpp new file mode 100644 index 0000000..2a114b2 --- /dev/null +++ b/esp/cpp/07_lcd-panel/main/ssd1306.cpp @@ -0,0 +1,13 @@ + +#include "ssd1306.h" + +// To use LV_COLOR_FORMAT_I1 we need an extra buffer to hold the converted data. +uint8_t SSD1306::oled_buffer_[LCD_H_RES * LCD_V_RES / 8]; + +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/ssd1306.h b/esp/cpp/07_lcd-panel/main/ssd1306.h new file mode 100644 index 0000000..85d4895 --- /dev/null +++ b/esp/cpp/07_lcd-panel/main/ssd1306.h @@ -0,0 +1,36 @@ +#ifndef SSD1306_H +#define SSD1306_H + +#include + +#include "panel_device.h" + +// 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 + +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; + + // 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]; +}; + +#endif // SSD1306_H