From 00ea5dae205a87ab42f17233fe5603e5c6742121 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 1 Mar 2025 10:34:24 -0500 Subject: [PATCH] Refactor ownership of draw buffer data. --- esp/cpp/06_i2c-scanner/main/idf_component.yml | 2 +- esp/cpp/06_i2c-scanner/sdkconfig | 9 ++ esp/cpp/07_lcd-panel-i2c/README.md | 2 - esp/cpp/07_lcd-panel-i2c/main/CMakeLists.txt | 4 +- esp/cpp/07_lcd-panel-i2c/main/display.cpp | 124 +--------------- esp/cpp/07_lcd-panel-i2c/main/display.h | 66 ++------- esp/cpp/07_lcd-panel-i2c/main/i2c.h | 2 +- esp/cpp/07_lcd-panel-i2c/main/panel.h | 13 +- .../07_lcd-panel-i2c/main/panel_device.cpp | 135 ++++++++++++++++++ esp/cpp/07_lcd-panel-i2c/main/panel_device.h | 110 ++++++++++++-- esp/cpp/07_lcd-panel-i2c/main/scoped_lock.cpp | 5 + esp/cpp/07_lcd-panel-i2c/main/scoped_lock.h | 21 +++ esp/cpp/07_lcd-panel-i2c/main/ssd1306.cpp | 6 - esp/cpp/07_lcd-panel-i2c/main/ssd1306.h | 17 +-- esp/cpp/07_lcd-panel-i2c/main/time_keeper.h | 17 +-- esp/cpp/07_lcd-panel-i2c/sdkconfig | 8 ++ 16 files changed, 322 insertions(+), 219 deletions(-) create mode 100644 esp/cpp/07_lcd-panel-i2c/main/panel_device.cpp create mode 100644 esp/cpp/07_lcd-panel-i2c/main/scoped_lock.cpp create mode 100644 esp/cpp/07_lcd-panel-i2c/main/scoped_lock.h delete mode 100644 esp/cpp/07_lcd-panel-i2c/main/ssd1306.cpp diff --git a/esp/cpp/06_i2c-scanner/main/idf_component.yml b/esp/cpp/06_i2c-scanner/main/idf_component.yml index 8de976e..0d10ce3 100644 --- a/esp/cpp/06_i2c-scanner/main/idf_component.yml +++ b/esp/cpp/06_i2c-scanner/main/idf_component.yml @@ -2,7 +2,7 @@ dependencies: ## Required IDF version idf: - version: '>=4.1.0' + version: '>=5.3.0' # # Put list of dependencies here # # For components maintained by Espressif: # component: "~1.0.0" diff --git a/esp/cpp/06_i2c-scanner/sdkconfig b/esp/cpp/06_i2c-scanner/sdkconfig index bbf2fda..308bf2d 100644 --- a/esp/cpp/06_i2c-scanner/sdkconfig +++ b/esp/cpp/06_i2c-scanner/sdkconfig @@ -2091,6 +2091,15 @@ CONFIG_MDNS_TASK_STACK_SIZE=4096 CONFIG_MDNS_TASK_AFFINITY_CPU0=y # CONFIG_MDNS_TASK_AFFINITY_CPU1 is not set CONFIG_MDNS_TASK_AFFINITY=0x0 + +# +# MDNS Memory Configuration +# +CONFIG_MDNS_TASK_CREATE_FROM_INTERNAL=y +CONFIG_MDNS_MEMORY_ALLOC_INTERNAL=y +# CONFIG_MDNS_MEMORY_CUSTOM_IMPL is not set +# end of MDNS Memory Configuration + CONFIG_MDNS_SERVICE_ADD_TIMEOUT_MS=2000 CONFIG_MDNS_TIMER_PERIOD_MS=100 # CONFIG_MDNS_NETWORKING_SOCKET is not set diff --git a/esp/cpp/07_lcd-panel-i2c/README.md b/esp/cpp/07_lcd-panel-i2c/README.md index be21e9a..c98e8cc 100644 --- a/esp/cpp/07_lcd-panel-i2c/README.md +++ b/esp/cpp/07_lcd-panel-i2c/README.md @@ -12,8 +12,6 @@ For instructions on setting up the ESP-IDF see [04_-esp-idf-arduino](./../04_esp ![schematic](./schematic.png) -Temperature and humidity sensor served on a web page within the local network. - ![example](./example.gif) To build this example run the following commands. diff --git a/esp/cpp/07_lcd-panel-i2c/main/CMakeLists.txt b/esp/cpp/07_lcd-panel-i2c/main/CMakeLists.txt index a29f894..6986668 100644 --- a/esp/cpp/07_lcd-panel-i2c/main/CMakeLists.txt +++ b/esp/cpp/07_lcd-panel-i2c/main/CMakeLists.txt @@ -1,5 +1,7 @@ idf_component_register( - SRCS main.cpp display.cpp ssd1306.cpp i2c.h time_keeper.h panel.h panel_device.h + SRCS + main.cpp display.cpp panel_device.cpp scoped_lock.cpp + i2c.h time_keeper.h panel.h ssd1306.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 b41e1ca..96e25dc 100644 --- a/esp/cpp/07_lcd-panel-i2c/main/display.cpp +++ b/esp/cpp/07_lcd-panel-i2c/main/display.cpp @@ -10,19 +10,11 @@ // TODO: Remove this dependency by relocating SSD1306::oledb_buffer_ #include "ssd1306.h" -// LVGL library is not thread-safe, this example calls LVGL APIs from tasks. -// We must use a mutex to protect it. -_lock_t Display::ScopedLock::lv_lock_; - // Static TimeKeeper for managing ESP timers across all displays. TimeKeeper Display::timers_; -/// Tag used for ESP logging. -const char * TAG = "Display"; - Display::Display(IPanelDevice &device) : - panel_(device), - lv_buf_(nullptr) + panel_(device) { if (!lv_is_initialized()) { ESP_LOGI(TAG, "Initialize LVGL"); @@ -34,12 +26,7 @@ Display::Display(IPanelDevice &device) : // associate the i2c panel handle to the display lv_display_set_user_data(lv_display_, panel_.esp_panel_); - register_draw_buffer(); - register_lvgl_tick_timer(); - - ESP_LOGI(TAG, "Create LVGL FreeRTOS task"); - xTaskCreate(Display::lvgl_port_task, "LVGL", LVGL_TASK_STACK_SIZE, - nullptr, LVGL_TASK_PRIORITY, nullptr); + panel_.register_display_callbacks(lv_display_); } void Display::set_text(const char *text, @@ -68,110 +55,3 @@ void Display::set_text(const char *text, lv_obj_set_width(obj, lv_display_get_horizontal_resolution(lv_display_)); lv_obj_align(obj, align, 0, 0); } - -bool Display::lvgl_flush_ready_cb(esp_lcd_panel_io_handle_t, - esp_lcd_panel_io_event_data_t *, - void *user_ctx) -{ - auto *disp = (lv_display_t *) user_ctx; - lv_display_flush_ready(disp); - return false; -} - -void Display::lvgl_flush_cb(lv_display_t *display, const lv_area_t *area, - uint8_t *px_map) // NOLINT(*-non-const-parameter) -{ - auto panel_handle = - (esp_lcd_panel_handle_t) lv_display_get_user_data(display); - - // 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); - int32_t x1 = area->x1; - int32_t x2 = area->x2; - int32_t y1 = area->y1; - int32_t y2 = area->y2; - - for (int32_t y = y1; y <= y2; y++) { - for (int32_t x = x1; x <= x2; x++) { - /* 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 - Left Right - */ - 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. - uint8_t *buf = SSD1306::oled_buffer_ + hor_res * (y >> 3) + (x); - if (chroma_color) { - (*buf) &= ~(1 << (y % 8)); - } else { - (*buf) |= (1 << (y % 8)); - } - } - } - // Pass the draw buffer to the driver. - ESP_ERROR_CHECK( - esp_lcd_panel_draw_bitmap(panel_handle, x1, y1, x2 + 1, y2 + 1, - SSD1306::oled_buffer_)); -} - -void Display::lvgl_increase_tick_cb(void *) -{ - // Tell LVGL how many milliseconds has elapsed - lv_tick_inc(LVGL_TICK_PERIOD_MS); -} - -[[noreturn]] void Display::lvgl_port_task(void *) -{ - ESP_LOGI(TAG, "Starting LVGL task"); - for (uint32_t time_to_next_ms = 0; true; usleep(1000 * time_to_next_ms)) { - ScopedLock lock; - time_to_next_ms = lv_timer_handler(); - } -} - -void Display::register_draw_buffer() -{ - // Create draw buffer. - ESP_LOGI(TAG, "Allocate separate LVGL draw buffers"); - lv_buf_ = heap_caps_calloc(1, panel_.device_->lv_buf_size_, - MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); - assert(lv_buf_); - - ESP_LOGI(TAG, "Set LVGL draw buffers"); - // Color format must be set first, LVGL9 suooprt new monochromatic format. - lv_display_set_color_format(lv_display_, LV_COLOR_FORMAT_I1); - lv_display_set_buffers(lv_display_, lv_buf_, nullptr, - panel_.device_->lv_buf_size_, - LV_DISPLAY_RENDER_MODE_FULL); - lv_display_set_rotation(lv_display_, LV_DISPLAY_ROTATION_0); - - ESP_LOGI(TAG, "Set LVGL callback for flushing to the display"); - lv_display_set_flush_cb(lv_display_, Display::lvgl_flush_cb); - - ESP_LOGI(TAG, "Register io panel callback for LVGL flush ready notification"); - const esp_lcd_panel_io_callbacks_t cbs = { - .on_color_trans_done = Display::lvgl_flush_ready_cb, - }; - ESP_ERROR_CHECK( - esp_lcd_panel_io_register_event_callbacks(panel_.esp_io_, &cbs, - lv_display_)); -} - -void Display::register_lvgl_tick_timer() -{ - ESP_LOGI(TAG, "Use esp_timer to increase LVGL tick"); - const esp_timer_create_args_t esp_timer_args = { - .callback = &Display::lvgl_increase_tick_cb, - .name = "lvgl_tick" - }; - timers_.start_new_timer_periodic(esp_timer_args, LVGL_TICK_PERIOD_MS * 1000); -} diff --git a/esp/cpp/07_lcd-panel-i2c/main/display.h b/esp/cpp/07_lcd-panel-i2c/main/display.h index dfe0ddb..ecf7db4 100644 --- a/esp/cpp/07_lcd-panel-i2c/main/display.h +++ b/esp/cpp/07_lcd-panel-i2c/main/display.h @@ -1,17 +1,13 @@ #ifndef DISPLAY_H #define DISPLAY_H - #include #include #include "time_keeper.h" #include "panel.h" - -#define LVGL_TICK_PERIOD_MS 5 -#define LVGL_TASK_STACK_SIZE (4 * 1024) -#define LVGL_TASK_PRIORITY 2 +#include "scoped_lock.h" /** * Encapsulates lv_display handle and related LVGL operations. @@ -37,6 +33,8 @@ public: Display &operator=(Display &) = delete; + using lv_display_handle_t = lv_display_t *; + // // GETTERS @@ -45,20 +43,20 @@ public: * * @sa ScopedLock for calling custom LVGL API's not implemented by Display. */ - [[nodiscard]] inline const lv_display_t *get() const { return lv_display_; } + [[nodiscard]] inline lv_display_handle_t get() const { return lv_display_; } /** * Getter for accessing LVGL display handle. * * @sa ScopedLock for calling custom LVGL API's not implemented by Display. */ - [[nodiscard]] inline lv_display_t *get() { return lv_display_; } + [[nodiscard]] inline lv_display_handle_t get() { return lv_display_; } /// Dereference operator for accessing LVGL display handle. - [[nodiscard]] inline const lv_display_t *operator*() const { return get(); } + [[nodiscard]] inline lv_display_handle_t operator*() const { return get(); } /// Dereference operator for accessing LVGL display handle. - [[nodiscard]] inline lv_display_t *operator*() { return get(); } + [[nodiscard]] inline lv_display_handle_t operator*() { return get(); } // // LVGL OPERATIONS @@ -77,24 +75,6 @@ public: lv_label_long_mode_t long_mode = LV_LABEL_LONG_SCROLL_CIRCULAR, lv_align_t align = LV_ALIGN_TOP_MID); - // - // TYPE DEFINITIONS - - /** - * Obtains LVGL API mutex lock for the duration of local scope. - * - * LVGL library is not thread-safe, this lock should be held when making calls - * to the LVGL API, and released as soon as possible when finished. - */ - struct ScopedLock { - explicit ScopedLock() { _lock_acquire(&lv_lock_); } - - ~ScopedLock() { _lock_release(&lv_lock_); } - - /// Mutex used to protect LVGL API calls. - static _lock_t lv_lock_; - }; - // // PUBLIC STATIC MEMBERS @@ -103,30 +83,6 @@ public: private: - // - // PRIVATE METHODS - - /// Registers LVGL draw buffers for this display. - void register_draw_buffer(); - - // - // PRIVATE STATIC METHODS - - /// Registers LVGL ticker timer callback for rendering this display. - static void register_lvgl_tick_timer(); - - static bool lvgl_flush_ready_cb(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_cb(void *arg); - - [[noreturn]] static void lvgl_port_task(void *arg); - // // PRIVATE MEMBERS @@ -134,10 +90,7 @@ private: Panel panel_; /// LVGL display handle. - lv_display_t *lv_display_; - - /// LVGL draw buffer associated with this Display's lv_display_t. - void *lv_buf_; + lv_display_handle_t lv_display_; /** * LVGL object handles stored in the LVGL screen associated with this Display. @@ -146,6 +99,9 @@ private: * @sa lv_display_get_screen_active */ std::unordered_map lv_objects_; + + /// Tag used for ESP logging. + constexpr static const char *TAG = "Display"; }; #endif // DISPLAY_H diff --git a/esp/cpp/07_lcd-panel-i2c/main/i2c.h b/esp/cpp/07_lcd-panel-i2c/main/i2c.h index 61c6167..c6f786b 100644 --- a/esp/cpp/07_lcd-panel-i2c/main/i2c.h +++ b/esp/cpp/07_lcd-panel-i2c/main/i2c.h @@ -78,7 +78,7 @@ private: // PRIVATE MEMBERS /// Tag used for ESP logging. - const char * TAG = "I2C"; + constexpr static const char *TAG = "I2C"; }; #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 df87ed9..d289aa7 100644 --- a/esp/cpp/07_lcd-panel-i2c/main/panel.h +++ b/esp/cpp/07_lcd-panel-i2c/main/panel.h @@ -57,13 +57,24 @@ struct Panel { /// ESP LCD panel configuration structure. esp_lcd_panel_dev_config_t esp_panel_config_; + /** + * Registers LVGL draw buffers and callbacks for rendering the display. + * + * @param display_handle Pointer to the LVGL display to use for rendering. + */ + inline void register_display_callbacks(lv_display_t *display_handle) const + { + device_->register_draw_buffer(display_handle, esp_io_); + device_->register_lvgl_tick_timer(); + } + private: // // PRIVATE MEMBERS /// Tag used for ESP logging. - const char * TAG = "Panel"; + constexpr static const char *TAG = "Panel"; }; #endif //PANEL_H diff --git a/esp/cpp/07_lcd-panel-i2c/main/panel_device.cpp b/esp/cpp/07_lcd-panel-i2c/main/panel_device.cpp new file mode 100644 index 0000000..f2c97c6 --- /dev/null +++ b/esp/cpp/07_lcd-panel-i2c/main/panel_device.cpp @@ -0,0 +1,135 @@ + +#include "panel_device.h" +#include "display.h" +#include "scoped_lock.h" +#include "time_keeper.h" + +// To use LV_COLOR_FORMAT_I1 we need an extra buffer to hold the converted data. +uint8_t IPanelDevice::oled_buffer_[LCD_H_RES * LCD_V_RES / 8]; + +bool IPanelDevice::lvgl_flush_ready_cb(esp_lcd_panel_io_handle_t, + esp_lcd_panel_io_event_data_t *, + void *user_ctx) +{ + auto *disp = (lv_display_t *) user_ctx; + lv_display_flush_ready(disp); + return false; +} + +void IPanelDevice::lvgl_flush_cb(lv_display_t *display, const lv_area_t *area, + uint8_t *px_map) // NOLINT(*-non-const-parameter) +{ + auto panel_handle = + (esp_lcd_panel_handle_t) lv_display_get_user_data(display); + + // 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); + int32_t x1 = area->x1; + int32_t x2 = area->x2; + int32_t y1 = area->y1; + int32_t y2 = area->y2; + + for (int32_t y = y1; y <= y2; y++) { + for (int32_t x = x1; x <= x2; x++) { + /* 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 + Left Right + */ + 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. + uint8_t *buf = IPanelDevice::oled_buffer_ + hor_res * (y >> 3) + (x); + if (chroma_color) { + (*buf) &= ~(1 << (y % 8)); + } else { + (*buf) |= (1 << (y % 8)); + } + } + } + // Pass the draw buffer to the driver. + ESP_ERROR_CHECK( + esp_lcd_panel_draw_bitmap(panel_handle, x1, y1, x2 + 1, y2 + 1, + IPanelDevice::oled_buffer_)); +} + +void IPanelDevice::lvgl_increase_tick_cb(void *) +{ + // Tell LVGL how many milliseconds has elapsed + lv_tick_inc(LVGL_TICK_PERIOD_MS); +} + +[[noreturn]] void IPanelDevice::lvgl_port_task(void *) +{ + // Optionally initialize some LVGL objects here before entering loop below. + + ESP_LOGI(TAG, "Starting LVGL task"); + for (uint32_t time_to_next_ms = 0; true; usleep(1000 * time_to_next_ms)) { + // Obtain LVGL API lock before calling any LVGL methods. + ScopedLock lock; + + // Optionally handle LVGL input or event logic here. + + // Update LVGL periodic timers. + time_to_next_ms = lv_timer_handler(); + } +} + +void IPanelDevice::register_draw_buffer(lv_display_t *display_handle, + esp_lcd_panel_io_handle_t io_handle) +{ + // Create draw buffer. + ESP_LOGI(TAG, "Allocate separate LVGL draw buffers"); + lv_buf_ = heap_caps_calloc(1, lv_buf_size_, + MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + assert(lv_buf_); + + ESP_LOGI(TAG, "Set LVGL draw buffers"); + // Color format must be set first, LVGL9 suooprt new monochromatic format. + lv_display_set_color_format(display_handle, LV_COLOR_FORMAT_I1); + lv_display_set_buffers(display_handle, lv_buf_, nullptr, + lv_buf_size_, + LV_DISPLAY_RENDER_MODE_FULL); + lv_display_set_rotation(display_handle, LV_DISPLAY_ROTATION_0); + + ESP_LOGI(TAG, "Set LVGL callback for flushing to the display"); + lv_display_set_flush_cb(display_handle, IPanelDevice::lvgl_flush_cb); + + ESP_LOGI(TAG, "Register io panel callback for LVGL flush ready notification"); + const esp_lcd_panel_io_callbacks_t cbs = { + .on_color_trans_done = IPanelDevice::lvgl_flush_ready_cb, + }; + ESP_ERROR_CHECK( + esp_lcd_panel_io_register_event_callbacks(io_handle, &cbs, + display_handle)); +} + +void IPanelDevice::register_lvgl_tick_timer() +{ + ESP_LOGI(TAG, "Use esp_timer to increase LVGL tick"); + const esp_timer_create_args_t esp_timer_args = { + .callback = &IPanelDevice::lvgl_increase_tick_cb, + // Data to pass to the IPanelDevice::lvgl_port_task callback. + .arg = nullptr, + .name = "lvgl_tick", + }; + Display::timers_.start_new_timer_periodic(esp_timer_args, + LVGL_TICK_PERIOD_MS * 1000); + + // LVGL requires a FreeRTOS task for running it's event loop. + // The lvgl_port_task callback can update the UI or handle input logic. + // For this basic example we don't do either of these things. + ESP_LOGI(TAG, "Create LVGL FreeRTOS task"); + // Optionally set user data to pass to LVGL's FreeRTOS task callback here. + void *user_data = nullptr; + xTaskCreate(lvgl_port_task, "LVGL", LVGL_TASK_STACK_SIZE, + user_data, LVGL_TASK_PRIORITY, nullptr); +} 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 1e0de82..cf85bcf 100644 --- a/esp/cpp/07_lcd-panel-i2c/main/panel_device.h +++ b/esp/cpp/07_lcd-panel-i2c/main/panel_device.h @@ -13,6 +13,12 @@ // 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 +#define LVGL_TICK_PERIOD_MS 5 +#define LVGL_TASK_STACK_SIZE (4 * 1024) +#define LVGL_TASK_PRIORITY 2 + +#define LCD_H_RES 128 +#define LCD_V_RES 64 /** * Encapsulates vendor specific ESP LCD panel initialization logic. @@ -139,28 +145,116 @@ public: /// ESP LCD panel IO configuration. esp_lcd_panel_io_i2c_config_t esp_io_config_; + /** + * Registers LVGL draw buffers for this display. + * + * An implementation of the interface can optionally override this method to + * provide custom LVGL callbacks and display configurations. + * + * @param display_handle LVGL display handle to use for rendering. + * @param io_handle IO handle for the ESP LCD panel. + */ + virtual void register_draw_buffer(lv_display_t *display_handle, + esp_lcd_panel_io_handle_t io_handle); + + /** + * Registers LVGL ticker timer callback for rendering this display. + * + * An implementation of the interface can optionally override this method to + * provide custom LVGL callbacks and tick configurations. + */ + virtual void register_lvgl_tick_timer(); + private: // // PRIVATE METHODS /** - * 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. - */ + * 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; + // + // PRIVATE STATIC METHODS + +/** + * The callback invoked when panel IO finishes transferring color data. + * This signals that the panel is ready to flush image data to the display. + * + * @param panel LCD panel IO handles. + * @param data Panel IO event data, fed by driver. + * @param user_ctx User data, passed from `esp_lcd_panel_io_xxx_config_t`. + * @return Whether a high priority task has been waken up by this function. + * @sa SSD1306::SSD1306 for setting user_ctx data passed to the callback. + * @sa register_rendering_data for overriding this callback. + */ + static bool lvgl_flush_ready_cb(esp_lcd_panel_io_handle_t panel, + esp_lcd_panel_io_event_data_t *data, + void *user_ctx); + + /** + * The callback invoked for flushing the rendered image to the display. + * + * `px_map` contains the rendered image as raw pixel map and it should be + * copied to `area` on the display. + * + * @param display LVGL display handle to use for rendering. + * @param area Area of the display being flushed. + * @param px_map Rendered image data for writing to the display area. + * @sa register_rendering_data for overriding this callback. + */ + static void lvgl_flush_cb(lv_display_t *display, + const lv_area_t *area, + uint8_t *px_map); + + /** + * Callback invoked for every period of the timer. + * + * This callback _must_ call lv_tick_inc to inform LVGL how much time has + * elapsed since the last period of the timer. + * + * @param data User data passed to the callback. + * @sa register_lvgl_tick_timer for setting user data and the tick period of + * the timer, or overriding this callback entirely. + */ + static void lvgl_increase_tick_cb(void *data); + + /** + * FreeRTOS task callback invoked for handling LVGL events or updating the UI. + * + * This function is intentionally an endless loop and should never return. + * LVGL initialization logic can optionally be added before entering the loop. + * Input logic can optionally be handled within the loop. + * + * This callback _must_ call lv_timer_handler to handle LVGL periodic timers. + * + * @param data User data passed to the callback. + * @sa register_lvgl_tick_timer for overriding this callback. + */ + [[noreturn]] static void lvgl_port_task(void *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 MEMBERS + /// LVGL draw buffer associated with this Display's lv_display_t. + void *lv_buf_; + /// Tag used for ESP logging. - const char * TAG = "IPanelDevice"; + constexpr static const char *TAG = "IPanelDevice"; }; #endif // PANEL_DEVICE_H diff --git a/esp/cpp/07_lcd-panel-i2c/main/scoped_lock.cpp b/esp/cpp/07_lcd-panel-i2c/main/scoped_lock.cpp new file mode 100644 index 0000000..ecdd8c7 --- /dev/null +++ b/esp/cpp/07_lcd-panel-i2c/main/scoped_lock.cpp @@ -0,0 +1,5 @@ +#include "scoped_lock.h" + +// LVGL library is not thread-safe, this example calls LVGL APIs from tasks. +// We must use a mutex to protect it. +_lock_t ScopedLock::lv_lock_; diff --git a/esp/cpp/07_lcd-panel-i2c/main/scoped_lock.h b/esp/cpp/07_lcd-panel-i2c/main/scoped_lock.h new file mode 100644 index 0000000..32dd202 --- /dev/null +++ b/esp/cpp/07_lcd-panel-i2c/main/scoped_lock.h @@ -0,0 +1,21 @@ +#ifndef SCOPED_LOCK_H +#define SCOPED_LOCK_H + +#include + +/** + * Obtains LVGL API mutex lock for the duration of local scope. + * + * LVGL library is not thread-safe, this lock should be held when making calls + * to the LVGL API, and released as soon as possible when finished. + */ +struct ScopedLock { + explicit ScopedLock() { _lock_acquire(&lv_lock_); } + + ~ScopedLock() { _lock_release(&lv_lock_); } + + /// Mutex used to protect LVGL API calls. + static _lock_t lv_lock_; +}; + +#endif // SCOPED_LOCK_H diff --git a/esp/cpp/07_lcd-panel-i2c/main/ssd1306.cpp b/esp/cpp/07_lcd-panel-i2c/main/ssd1306.cpp deleted file mode 100644 index 9429023..0000000 --- a/esp/cpp/07_lcd-panel-i2c/main/ssd1306.cpp +++ /dev/null @@ -1,6 +0,0 @@ - -#include "ssd1306.h" - -// To use LV_COLOR_FORMAT_I1 we need an extra buffer to hold the converted data. -// TODO: Remove this and SSD1306 can be header only. -uint8_t SSD1306::oled_buffer_[LCD_H_RES * LCD_V_RES / 8]; diff --git a/esp/cpp/07_lcd-panel-i2c/main/ssd1306.h b/esp/cpp/07_lcd-panel-i2c/main/ssd1306.h index c01e034..bb13bfb 100644 --- a/esp/cpp/07_lcd-panel-i2c/main/ssd1306.h +++ b/esp/cpp/07_lcd-panel-i2c/main/ssd1306.h @@ -25,8 +25,6 @@ // According to SSD1306 datasheet. // https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf -#define LCD_H_RES 128 -#define LCD_V_RES 64 #define I2C_HW_ADDR 0x3C #define LCD_PIXEL_CLOCK_HZ (400 * 1000) // Bit number used to represent command and parameter @@ -62,6 +60,9 @@ public: IPanelDevice(i2c, (esp_lcd_panel_io_i2c_config_t) { .dev_addr = I2C_HW_ADDR, + // User data to pass to the LVGL flush_ready callback. + // See IPanelDevice::lvgl_flush_ready_cb + .user_ctx = nullptr, .control_phase_bytes = 1, .dc_bit_offset = 6, .lcd_cmd_bits = LCD_CMD_BITS, @@ -95,12 +96,6 @@ public: /// SSD1306 configuration structure. esp_lcd_panel_ssd1306_config_t ssd1306_config_; - /** - * 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: // @@ -113,12 +108,6 @@ private: { ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(io, &config, &panel)); } - - // - // PRIVATE MEMBERS - - /// Tag used for ESP logging. - const char * TAG = "SSD1306"; }; #endif // SSD1306_H 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 5645ea2..371a8d5 100644 --- a/esp/cpp/07_lcd-panel-i2c/main/time_keeper.h +++ b/esp/cpp/07_lcd-panel-i2c/main/time_keeper.h @@ -1,4 +1,3 @@ - #ifndef TIME_KEEPER_H #define TIME_KEEPER_H @@ -20,13 +19,13 @@ struct Timer { explicit Timer(esp_timer_create_args_t args) : args_(args), esp_timer_(nullptr) { - ESP_LOGI(TAG, "Creating esp_timer with name: %s", args_.name); + ESP_LOGI(TAG, "Creating esp_timer with name: '%s'", args_.name); ESP_ERROR_CHECK(esp_timer_create(&args, &esp_timer_)); } ~Timer() { - ESP_LOGI(TAG, "Destroying esp_timer with name: %s", args_.name); + ESP_LOGI(TAG, "Destroying esp_timer with name: '%s'", args_.name); ESP_ERROR_CHECK(esp_timer_delete(esp_timer_)); } @@ -51,7 +50,7 @@ private: // PRIVATE MEMBERS /// Tag used for ESP logging. - const char * TAG = "Timer"; + constexpr static const char *TAG = "Timer"; }; /** @@ -86,11 +85,11 @@ struct TimeKeeper { * @sa get_handle * @sa operator[](const char*) */ - TimerHandle create_timer(esp_timer_create_args_t args) + [[maybe_unused]] TimerHandle create_timer(esp_timer_create_args_t args) { auto rt = managed_timers_.emplace(args.name, args); if (!rt.second) { - ESP_LOGE(TAG, "Display::Timer already exists with name %s", args.name); + ESP_LOGE(TAG, "Timer already exists with name '%s'", args.name); return nullptr; } return &rt.first->second; @@ -105,7 +104,9 @@ struct TimeKeeper { /// Delete a Timer with the given name. [[maybe_unused]] void delete_timer(const char *name) { - managed_timers_.erase(name); + if (managed_timers_.erase(name) == 0) { + ESP_LOGE(TAG, "Attempt to delete timer that does not exist: '%s'", name); + } } /// Create a Timer with the ESP args and call esp_timer_start_periodic. @@ -149,7 +150,7 @@ private: std::unordered_map managed_timers_; /// Tag used for ESP logging. - const char * TAG = "TimeKeeper"; + constexpr static const char *TAG = "TimeKeeper"; }; #endif // TIME_KEEPER_H diff --git a/esp/cpp/07_lcd-panel-i2c/sdkconfig b/esp/cpp/07_lcd-panel-i2c/sdkconfig index 7feaadd..c0a794a 100644 --- a/esp/cpp/07_lcd-panel-i2c/sdkconfig +++ b/esp/cpp/07_lcd-panel-i2c/sdkconfig @@ -2091,7 +2091,15 @@ CONFIG_MDNS_TASK_STACK_SIZE=4096 CONFIG_MDNS_TASK_AFFINITY_CPU0=y # CONFIG_MDNS_TASK_AFFINITY_CPU1 is not set CONFIG_MDNS_TASK_AFFINITY=0x0 + +# +# MDNS Memory Configuration +# CONFIG_MDNS_TASK_CREATE_FROM_INTERNAL=y +CONFIG_MDNS_MEMORY_ALLOC_INTERNAL=y +# CONFIG_MDNS_MEMORY_CUSTOM_IMPL is not set +# end of MDNS Memory Configuration + CONFIG_MDNS_SERVICE_ADD_TIMEOUT_MS=2000 CONFIG_MDNS_TIMER_PERIOD_MS=100 # CONFIG_MDNS_NETWORKING_SOCKET is not set