From e2bb4061393e5a951e5b05711c9d4e290b1515d4 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 16 Feb 2025 14:31:50 -0500 Subject: [PATCH] Finish cleanup. --- esp/cpp/07_lcd-panel-i2c/main/CMakeLists.txt | 6 +- esp/cpp/07_lcd-panel-i2c/main/display.cpp | 8 +-- esp/cpp/07_lcd-panel-i2c/main/display.h | 24 ++++--- esp/cpp/07_lcd-panel-i2c/main/i2c.h | 55 +++++++++++---- esp/cpp/07_lcd-panel-i2c/main/panel.h | 20 +++++- esp/cpp/07_lcd-panel-i2c/main/panel_device.h | 72 ++++++++++++++++++-- esp/cpp/07_lcd-panel-i2c/main/ssd1306.h | 30 +++++++- esp/cpp/07_lcd-panel-i2c/main/time_keeper.h | 12 ++-- esp/cpp/README.md | 1 + 9 files changed, 183 insertions(+), 45 deletions(-) diff --git a/esp/cpp/07_lcd-panel-i2c/main/CMakeLists.txt b/esp/cpp/07_lcd-panel-i2c/main/CMakeLists.txt index 7726d57..a29f894 100644 --- a/esp/cpp/07_lcd-panel-i2c/main/CMakeLists.txt +++ b/esp/cpp/07_lcd-panel-i2c/main/CMakeLists.txt @@ -1 +1,5 @@ -idf_component_register(SRC_DIRS . INCLUDE_DIRS .) +idf_component_register( + SRCS main.cpp display.cpp ssd1306.cpp i2c.h time_keeper.h panel.h panel_device.h + INCLUDE_DIRS . + REQUIRES driver +) diff --git a/esp/cpp/07_lcd-panel-i2c/main/display.cpp b/esp/cpp/07_lcd-panel-i2c/main/display.cpp index 45956ea..1908328 100644 --- a/esp/cpp/07_lcd-panel-i2c/main/display.cpp +++ b/esp/cpp/07_lcd-panel-i2c/main/display.cpp @@ -129,11 +129,9 @@ void Display::lvgl_increase_tick_cb(void *) [[noreturn]] void Display::lvgl_port_task(void *) { ESP_LOGI(TAG, "Starting LVGL task"); - for (uint32_t time_till_next_ms = 0; true;) { - _lock_acquire(&ScopedLock::lv_lock_); - time_till_next_ms = lv_timer_handler(); - _lock_release(&ScopedLock::lv_lock_); - usleep(1000 * time_till_next_ms); + for (uint32_t time_to_next_ms = 0; true; usleep(1000 * time_to_next_ms)) { + ScopedLock lock; + time_to_next_ms = lv_timer_handler(); } } diff --git a/esp/cpp/07_lcd-panel-i2c/main/display.h b/esp/cpp/07_lcd-panel-i2c/main/display.h index 6606a60..0879459 100644 --- a/esp/cpp/07_lcd-panel-i2c/main/display.h +++ b/esp/cpp/07_lcd-panel-i2c/main/display.h @@ -13,19 +13,17 @@ #define LVGL_TASK_STACK_SIZE (4 * 1024) #define LVGL_TASK_PRIORITY 2 +/** + * Encapsulates lv_display handle and related LVGL operations. + * Contains helper methods that wrap basic LVGL operations such as drawing text. + * The underlying lv_display can be obtained for manual LVGL operations. + * @sa ScopedLock + * @sa Display::get() + */ class Display { public: - // - // CONSTRUCTORS - - Display(const Display &) = delete; - - Display(Display &) = delete; - - Display &operator=(Display &) = delete; - /** - * Construct a new Display using an IPanelDevice. + * Construct a new Display using an object that implements IPanelDevice. * * @param device An object that implements the IPanelDevice interface. */ @@ -33,6 +31,12 @@ public: ~Display() = default; + Display(const Display &) = delete; + + Display(Display &) = delete; + + Display &operator=(Display &) = delete; + // // GETTERS diff --git a/esp/cpp/07_lcd-panel-i2c/main/i2c.h b/esp/cpp/07_lcd-panel-i2c/main/i2c.h index 9c7143c..92c08e0 100644 --- a/esp/cpp/07_lcd-panel-i2c/main/i2c.h +++ b/esp/cpp/07_lcd-panel-i2c/main/i2c.h @@ -5,14 +5,23 @@ #include +// TODO: Refactor tags for-each class. static const char *TAG = "lcd-panel"; +/** + * Encapsulates ESP I2C creation and usage. + */ struct I2C { + /** + * Construct and initialize an ESP I2C master bus. + * An I2C constructor may only be called one time in any application. + * + * @param sda GPIO pin number for SDA + * @param scl GPIO pin number for SCL + * @param rst GPIO pin number for RST + */ I2C(gpio_num_t sda, gpio_num_t scl, int rst = -1) : - esp_i2c_bus_(nullptr), - rst_num_(rst), - esp_bus_config_( - (i2c_master_bus_config_t) { + I2C((i2c_master_bus_config_t) { .i2c_port = I2C_BUS_PORT, .sda_io_num = sda, .scl_io_num = scl, @@ -21,22 +30,44 @@ struct I2C { .flags { .enable_internal_pullup = true, }, - } - ) + }, + rst + ) { } + + /** + * Construct an ESP I2C master bus given a specific ESP I2C configuration. + * An I2C constructor may only be called one time in any application. + * + * @param config ESP I2C master bus configuration. + * @param rst GPIO pin number for RST + */ + explicit I2C(i2c_master_bus_config_t config, int rst = -1) : + esp_bus_config_(config), + rst_num_(rst) { + i2c_master_bus_handle_t i2c; ESP_LOGI(TAG, "Initializing new master I2C bus"); - ESP_ERROR_CHECK(i2c_new_master_bus(&esp_bus_config_, &esp_i2c_bus_)); + ESP_ERROR_CHECK(i2c_new_master_bus(&esp_bus_config_, &i2c)); } ~I2C() = default; - // TODO: Can you use the I2C get_master_bus API in a static method? - i2c_master_bus_handle_t esp_i2c_bus_; + /** + * ESP I2C master bus handle getter. + * This will fail if an I2C instance was never constructed. + */ + static i2c_master_bus_handle_t get() + { + i2c_master_bus_handle_t i2c = nullptr; + ESP_ERROR_CHECK(i2c_master_get_bus_handle(0, &i2c)); + return i2c; + } - int rst_num_; - -private: + /// ESP I2C master bus configuration used during initialization. i2c_master_bus_config_t esp_bus_config_; + + /// RST GPIO pin number. + int rst_num_; }; #endif //I2C_H diff --git a/esp/cpp/07_lcd-panel-i2c/main/panel.h b/esp/cpp/07_lcd-panel-i2c/main/panel.h index ae9ef52..792483e 100644 --- a/esp/cpp/07_lcd-panel-i2c/main/panel.h +++ b/esp/cpp/07_lcd-panel-i2c/main/panel.h @@ -3,8 +3,19 @@ #include "panel_device.h" -class Panel { -public: +/** + * Encapsulates esp_lcd_panel handles and operations. + * The only exception is esp_lcd_panel_io_i2c_config_t owned by IPanelDevice. + * This structure requires details specific to the implementing device. + * + * Panel is an implementation detail of Display, not meant to be used directly. + */ +struct Panel { + /** + * Construct a new Panel using an object that implements IPanelDevice. + * + * @param device An object that implements the IPanelDevice interface. + */ explicit Panel(IPanelDevice &device) : device_(&device), esp_io_(nullptr), @@ -31,13 +42,16 @@ public: ~Panel() = default; + /// Pointer to object using known interface for IPanelDevice. IPanelDevice *device_; + /// ESP LCD panel IO handle. esp_lcd_panel_io_handle_t esp_io_; + /// ESP LCD panel handle. esp_lcd_panel_handle_t esp_panel_; -private: + /// ESP LCD panel configuration structure. esp_lcd_panel_dev_config_t esp_panel_config_; }; diff --git a/esp/cpp/07_lcd-panel-i2c/main/panel_device.h b/esp/cpp/07_lcd-panel-i2c/main/panel_device.h index e3533b2..f74145d 100644 --- a/esp/cpp/07_lcd-panel-i2c/main/panel_device.h +++ b/esp/cpp/07_lcd-panel-i2c/main/panel_device.h @@ -10,10 +10,28 @@ #include "i2c.h" +// LVGL reserves 2x4 bytes in the buffer to be used as a palette. +// This additional space must be added to the IPanelDevice::buf_size_. #define LVGL_PALETTE_SIZE 8 +/** + * Encapsulates vendor specific ESP LCD pabel initialization logic. + * This pure virtual interface can be inherited from for using new LCD devices. + * See the SSD1306 example for implementing a PanelDevice for NT35510 or ST7789. + * + * At this time only I2C is supported. + * Classes that inherit from this interface should likely be marked final. + */ class IPanelDevice { public: + /** + * Construct an IPanelDevice. + * + * @param i2c I2C object. Eventually this will mature to IProtocol or similar. + * @param config I2C configuration for this device. + * @param height Height of the device screen in pixels. + * @param width Width of the device screen in pixels. + */ explicit IPanelDevice(I2C &i2c, esp_lcd_panel_io_i2c_config_t config, int width, @@ -21,6 +39,15 @@ public: IPanelDevice(i2c, config, width, height, width * height / 8 + LVGL_PALETTE_SIZE) { } + /** + * Construct an IPanelDevice. + * + * @param i2c I2C object. Eventually this will mature to IProtocol or similar. + * @param config I2C configuration for this device. + * @param height Height of the device screen in pixels. + * @param width Width of the device screen in pixels. + * @param draw_buf_size Size of the draw buffer for this device. + */ explicit IPanelDevice(I2C &i2c, esp_lcd_panel_io_i2c_config_t io_config, int width, @@ -30,11 +57,15 @@ public: height_(height), rst_num_(i2c.rst_num_), lv_buf_size_(draw_buf_size), - esp_i2c_bus_(i2c.esp_i2c_bus_), esp_io_config_(io_config) { } virtual ~IPanelDevice() = default; + /** + * Create an LVGL display using the width and height of this device. + * + * @return Handle to the created LVGL display. + */ [[nodiscard]] lv_display_t *create_display() const { auto display = lv_display_create(width_, height_); @@ -42,15 +73,28 @@ public: return display; } + /** + * Create an ESP LCD panel IO handle. + * + * @return The created ESP LCD panel IO handle. + */ [[nodiscard]] esp_lcd_panel_io_handle_t create_io_handle() { ESP_LOGI(TAG, "Creating panel IO handle"); esp_lcd_panel_io_handle_t handle = nullptr; ESP_ERROR_CHECK( - esp_lcd_new_panel_io_i2c(esp_i2c_bus_, &esp_io_config_, &handle)); + esp_lcd_new_panel_io_i2c(I2C::get(), &esp_io_config_, &handle)); return handle; } + /** + * Create and initialize an ESP panel handle. + * IPanelDevice implementors must initialize the panel within init_panel. + * + * @param config ESP LCD panel configuration. + * @param io ESP LCD panel IO handle. + * @param [out] panel ESP LCD panel handle output pointer location. + */ void create_panel(esp_lcd_panel_dev_config_t &config, esp_lcd_panel_io_handle_t io, esp_lcd_panel_handle_t &panel) @@ -62,25 +106,43 @@ public: } ESP_LOGI(TAG, "Install SSD1306 panel driver"); + // Call pure virtual method responsible for initializing the panel handle. init_panel(config, io, panel); + } + /** + * Retrieve the device specific vendor configuration structure. + * + * @return Address of vendor configuration structure. + * @sa SSD1306::vendor_config + */ virtual void *vendor_config() = 0; + /// Width of the device screen in pixels. int32_t width_; + /// Height of the device screen in pixels. int32_t height_; + /// RST GPIO pin number. int rst_num_; - // LVGL reserves 2x4 bytes in the buffer to be used as a palette. + /// LVGL draw buffer size for the device. size_t lv_buf_size_; - i2c_master_bus_handle_t esp_i2c_bus_; - + /// ESP LCD panel IO configuration. esp_lcd_panel_io_i2c_config_t esp_io_config_; private: + /** + * Initializes the ESP panel using vendor specific APIs and configurations. + * This method should implement any setup logic specific to the device. + * + * @param config ESP LCD panel configuration. + * @param io ESP LCD panel IO handle. + * @param [out] panel ESP LCD panel handle output pointer location. + */ virtual void init_panel(esp_lcd_panel_dev_config_t &config, esp_lcd_panel_io_handle_t io, esp_lcd_panel_handle_t &panel) = 0; diff --git a/esp/cpp/07_lcd-panel-i2c/main/ssd1306.h b/esp/cpp/07_lcd-panel-i2c/main/ssd1306.h index 5998c83..da12180 100644 --- a/esp/cpp/07_lcd-panel-i2c/main/ssd1306.h +++ b/esp/cpp/07_lcd-panel-i2c/main/ssd1306.h @@ -33,12 +33,27 @@ #define LCD_CMD_BITS 8 #define LCD_PARAM_BITS 8 +/** + * Example of implementing the IPanelDevice interface for SSD1306 LCD device. + */ class SSD1306 final : public IPanelDevice { public: - // Constructors allow overriding ssd1306 config. + /** + * Construct a new SSD1306 device. + * + * @param i2c I2C master bus to manage this device. + */ explicit SSD1306(I2C &i2c) : SSD1306(i2c, {.height = SCREEN_HEIGHT}) { } + /** + * Construct a new SSD1306 device given a specific SSD1306 configuration. + * + * @param i2c I2C master bus to manage this device. + * @param config SSD1306 vendor configuration. + * @param width Width of the device screen in pixels. + * @param height Height of the device screen in pixels. + */ SSD1306(I2C &i2c, esp_lcd_panel_ssd1306_config_t config, int width = SCREEN_WIDTH, @@ -60,18 +75,27 @@ public: ~SSD1306() final = default; + /** + * Provides the SSD1306 vendor configuration to IPanelDevice consumers. + * + * @return Address of the SSD1306 vendor configuration structure. + */ void *vendor_config() override { return &ssd1306_config_; } - // The configuration structure specific to the SSD1306. + /// SSD1306 configuration structure. esp_lcd_panel_ssd1306_config_t ssd1306_config_; - // For LV_COLOR_FORMAT_I1 we need an extra buffer to hold the converted data. + /** + * Draw buffer for this panel device. + * 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: + /// Initializes the ESP LCD panel handle for the SSD1306 device. void init_panel(esp_lcd_panel_dev_config_t &config, esp_lcd_panel_io_handle_t io, esp_lcd_panel_handle_t &panel) override diff --git a/esp/cpp/07_lcd-panel-i2c/main/time_keeper.h b/esp/cpp/07_lcd-panel-i2c/main/time_keeper.h index b869009..3cf02b3 100644 --- a/esp/cpp/07_lcd-panel-i2c/main/time_keeper.h +++ b/esp/cpp/07_lcd-panel-i2c/main/time_keeper.h @@ -17,12 +17,6 @@ * The ESP timer will be deleted when this class desctructor is called. */ struct Timer { - Timer(const Timer &) = delete; - - Timer(Timer &) = delete; - - Timer &operator=(Timer &) = delete; - explicit Timer(esp_timer_create_args_t args) : args_(args), esp_timer_(nullptr) { @@ -36,6 +30,12 @@ struct Timer { ESP_ERROR_CHECK(esp_timer_delete(esp_timer_)); } + Timer(const Timer &) = delete; + + Timer(Timer &) = delete; + + Timer &operator=(Timer &) = delete; + /// Arguments passed to ESP API during timer creation. esp_timer_create_args_t args_; diff --git a/esp/cpp/README.md b/esp/cpp/README.md index b613246..b31f094 100644 --- a/esp/cpp/README.md +++ b/esp/cpp/README.md @@ -8,6 +8,7 @@ shaunrd0/klips/esp/ ├── 04_esp-idf-arduino # CMake example instead of Arduino IDE for ESP development. ├── 05_temp-humidity-web # Temperature and humidity sensor within a web browser. ├── 06_i2c-scanner # Simple I2C device scanner. +├── 07_lcd-panel-i2c # Drawing to a LCD display with LVGL over I2C. ├── ESP32-basic-starter-kit.pdf # PDF for tutorials in ESP32 starter kit. ├── ESP32-dev-module.png └── README.md