Skip to content

Commit 17f60bb

Browse files
authored
feat(display_drivers): Update display drivers to share code by refactoring into base class. Add SpiPanelIo helper class as well (#620)
* feat(display_drivers): Update display drivers to share code by refactoring into base class. Add SpiPanelIo helper class as well * update docs * make bsp components examples more consistent so they all handle rotation more gracefully * fix sa in display drivers example * address comments * revert changes * only apply flags to struct if idf >= v6.0
1 parent 694cc02 commit 17f60bb

95 files changed

Lines changed: 4477 additions & 5287 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

components/byte90/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
idf_component_register(
33
INCLUDE_DIRS "include"
44
SRC_DIRS "src"
5-
REQUIRES driver adxl345 base_component display display_drivers i2c interrupt task
5+
REQUIRES driver adxl345 base_component display display_drivers i2c interrupt spi task
66
REQUIRED_IDF_TARGETS "esp32s3"
77
)

components/byte90/example/main/byte90_example.cpp

Lines changed: 135 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,27 @@
1+
#include <array>
12
#include <chrono>
2-
#include <deque>
33
#include <stdlib.h>
44

55
#include "byte90.hpp"
66

77
using namespace std::chrono_literals;
88

9-
static constexpr size_t MAX_CIRCLES = 100;
10-
static std::deque<lv_obj_t *> circles;
9+
static constexpr size_t MAX_CIRCLES = 10;
10+
struct Circle {
11+
int x{0};
12+
int y{0};
13+
int radius{0};
14+
bool visible{false};
15+
};
16+
static std::array<Circle, MAX_CIRCLES> circles;
17+
static size_t next_circle_index = 0;
18+
static size_t visible_circle_count = 0;
19+
static lv_obj_t *circle_layer = nullptr;
1120

1221
static std::recursive_mutex lvgl_mutex;
22+
static bool initialize_circle_layer(int width, int height);
23+
static void draw_circle_layer(lv_event_t *event);
24+
static void invalidate_circle_area(const Circle &circle);
1325
static void draw_circle(int x0, int y0, int radius);
1426
static void clear_circles();
1527

@@ -39,6 +51,20 @@ extern "C" void app_main(void) {
3951
}
4052
// initialize the button, which we'll use to cycle the rotation of the display
4153
logger.info("Initializing the button");
54+
lv_obj_t *bg = nullptr;
55+
lv_obj_t *label = nullptr;
56+
static auto update_layout = [&]() {
57+
int width = byte90.rotated_display_width();
58+
int height = byte90.rotated_display_height();
59+
lv_obj_set_size(bg, width, height);
60+
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
61+
if (circle_layer) {
62+
lv_obj_set_size(circle_layer, width, height);
63+
lv_obj_align(circle_layer, LV_ALIGN_CENTER, 0, 0);
64+
lv_obj_move_foreground(circle_layer);
65+
lv_obj_invalidate(circle_layer);
66+
}
67+
};
4268
auto on_button_pressed = [&](const auto &event) {
4369
if (event.active) {
4470
// increment the brightness by 10%, looping back to 0% after 100%
@@ -50,22 +76,30 @@ extern "C" void app_main(void) {
5076
std::lock_guard<std::recursive_mutex> lock(lvgl_mutex);
5177
static auto rotation = LV_DISPLAY_ROTATION_0;
5278
rotation = static_cast<lv_display_rotation_t>((static_cast<int>(rotation) + 1) % 4);
53-
lv_display_t *disp = lv_disp_get_default();
79+
lv_display_t *disp = lv_display_get_default();
5480
lv_disp_set_rotation(disp, rotation);
81+
update_layout();
5582
}
5683
};
5784
byte90.initialize_button(on_button_pressed);
5885

5986
// set the background color to black
60-
lv_obj_t *bg = lv_obj_create(lv_screen_active());
61-
lv_obj_set_size(bg, byte90.lcd_width(), byte90.lcd_height());
87+
bg = lv_obj_create(lv_screen_active());
88+
lv_obj_set_size(bg, byte90.rotated_display_width(), byte90.rotated_display_height());
6289
lv_obj_set_style_bg_color(bg, lv_color_make(0, 0, 0), 0);
90+
if (!initialize_circle_layer(byte90.rotated_display_width(), byte90.rotated_display_height())) {
91+
logger.error("Failed to initialize circle layer!");
92+
return;
93+
}
6394

6495
// add text in the center of the screen
65-
lv_obj_t *label = lv_label_create(lv_screen_active());
96+
label = lv_label_create(lv_screen_active());
6697
lv_label_set_text(label, "Drawing circles\nto the screen.");
6798
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
6899
lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0);
100+
update_layout();
101+
102+
lv_obj_move_foreground(circle_layer);
69103

70104
// start a simple thread to do the lv_task_handler every 16ms
71105
espp::Task lv_task({.callback = [](std::mutex &m, std::condition_variable &cv) -> bool {
@@ -88,17 +122,16 @@ extern "C" void app_main(void) {
88122
while (true) {
89123
auto start = esp_timer_get_time();
90124
// if there are 10 circles on the screen, clear them
91-
static constexpr int max_circles = 10;
92-
if (circles.size() >= max_circles) {
125+
if (visible_circle_count >= MAX_CIRCLES) {
93126
// lock the lvgl mutex
94127
std::lock_guard<std::recursive_mutex> lock(lvgl_mutex);
95128
clear_circles();
96129
} else {
97130
// draw a circle of circles on the screen (just draw the next circle)
98-
static constexpr int middle_x = byte90.lcd_width() / 2;
99-
static constexpr int middle_y = byte90.lcd_height() / 2;
131+
int middle_x = byte90.rotated_display_width() / 2;
132+
int middle_y = byte90.rotated_display_height() / 2;
100133
static constexpr int radius = 30;
101-
float angle = circles.size() * 2.0f * M_PI / max_circles;
134+
float angle = visible_circle_count * 2.0f * M_PI / MAX_CIRCLES;
102135
int x = middle_x + radius * cos(angle);
103136
int y = middle_y + radius * sin(angle);
104137
// lock the lvgl mutex
@@ -112,28 +145,100 @@ extern "C" void app_main(void) {
112145
//! [byte90 example]
113146
}
114147

148+
static bool initialize_circle_layer(int width, int height) {
149+
if (circle_layer) {
150+
return true;
151+
}
152+
circle_layer = lv_obj_create(lv_screen_active());
153+
if (!circle_layer) {
154+
return false;
155+
}
156+
lv_obj_remove_style_all(circle_layer);
157+
lv_obj_set_size(circle_layer, width, height);
158+
lv_obj_align(circle_layer, LV_ALIGN_CENTER, 0, 0);
159+
lv_obj_clear_flag(circle_layer, LV_OBJ_FLAG_CLICKABLE);
160+
lv_obj_clear_flag(circle_layer, LV_OBJ_FLAG_SCROLLABLE);
161+
lv_obj_set_style_bg_opa(circle_layer, LV_OPA_TRANSP, 0);
162+
lv_obj_set_style_border_width(circle_layer, 0, 0);
163+
lv_obj_set_style_outline_width(circle_layer, 0, 0);
164+
lv_obj_set_style_shadow_width(circle_layer, 0, 0);
165+
lv_obj_add_event_cb(circle_layer, draw_circle_layer, LV_EVENT_DRAW_MAIN, nullptr);
166+
return true;
167+
}
168+
169+
static void draw_circle_layer(lv_event_t *event) {
170+
if (visible_circle_count == 0) {
171+
return;
172+
}
173+
auto *obj = static_cast<lv_obj_t *>(lv_event_get_current_target(event));
174+
auto *layer = lv_event_get_layer(event);
175+
lv_area_t obj_coords;
176+
lv_obj_get_coords(obj, &obj_coords);
177+
178+
lv_draw_rect_dsc_t rect_dsc;
179+
lv_draw_rect_dsc_init(&rect_dsc);
180+
rect_dsc.base.layer = layer;
181+
rect_dsc.radius = LV_RADIUS_CIRCLE;
182+
rect_dsc.bg_opa = LV_OPA_70;
183+
rect_dsc.bg_color = lv_color_make(0, 255, 255);
184+
rect_dsc.border_width = 0;
185+
rect_dsc.outline_width = 0;
186+
rect_dsc.shadow_width = 0;
187+
188+
for (const auto &circle : circles) {
189+
if (!circle.visible) {
190+
continue;
191+
}
192+
lv_area_t coords = {
193+
.x1 = static_cast<lv_coord_t>(obj_coords.x1 + circle.x - circle.radius),
194+
.y1 = static_cast<lv_coord_t>(obj_coords.y1 + circle.y - circle.radius),
195+
.x2 = static_cast<lv_coord_t>(obj_coords.x1 + circle.x + circle.radius - 1),
196+
.y2 = static_cast<lv_coord_t>(obj_coords.y1 + circle.y + circle.radius - 1),
197+
};
198+
lv_draw_rect(layer, &rect_dsc, &coords);
199+
}
200+
}
201+
202+
static void invalidate_circle_area(const Circle &circle) {
203+
if (!circle_layer || circle.radius <= 0) {
204+
return;
205+
}
206+
207+
lv_area_t obj_coords;
208+
lv_obj_get_coords(circle_layer, &obj_coords);
209+
lv_area_t coords = {
210+
.x1 = static_cast<lv_coord_t>(obj_coords.x1 + circle.x - circle.radius),
211+
.y1 = static_cast<lv_coord_t>(obj_coords.y1 + circle.y - circle.radius),
212+
.x2 = static_cast<lv_coord_t>(obj_coords.x1 + circle.x + circle.radius - 1),
213+
.y2 = static_cast<lv_coord_t>(obj_coords.y1 + circle.y + circle.radius - 1),
214+
};
215+
lv_obj_invalidate_area(circle_layer, &coords);
216+
}
217+
115218
static void draw_circle(int x0, int y0, int radius) {
116-
// if we have too many circles, remove the oldest one
117-
if (circles.size() >= MAX_CIRCLES) {
118-
lv_obj_delete(circles.front());
119-
circles.pop_front();
219+
if (!circle_layer) {
220+
return;
120221
}
121-
lv_obj_t *my_Cir = lv_obj_create(lv_screen_active());
122-
lv_obj_set_scrollbar_mode(my_Cir, LV_SCROLLBAR_MODE_OFF);
123-
lv_obj_set_size(my_Cir, radius * 2, radius * 2);
124-
lv_obj_set_pos(my_Cir, x0 - radius, y0 - radius);
125-
lv_obj_set_style_radius(my_Cir, LV_RADIUS_CIRCLE, 0);
126-
// ensure the circle ignores touch events (so things behind it can still be
127-
// interacted with)
128-
lv_obj_clear_flag(my_Cir, LV_OBJ_FLAG_CLICKABLE);
129-
circles.push_back(my_Cir);
222+
lv_obj_move_foreground(circle_layer);
223+
Circle previous_circle = circles[next_circle_index];
224+
circles[next_circle_index] = {.x = x0, .y = y0, .radius = radius, .visible = true};
225+
next_circle_index = (next_circle_index + 1) % circles.size();
226+
if (visible_circle_count < circles.size()) {
227+
visible_circle_count++;
228+
}
229+
if (previous_circle.visible) {
230+
invalidate_circle_area(previous_circle);
231+
}
232+
invalidate_circle_area(circles[(next_circle_index + circles.size() - 1) % circles.size()]);
130233
}
131234

132235
static void clear_circles() {
133-
// remove the circles from lvgl
134-
for (auto circle : circles) {
135-
lv_obj_delete(circle);
236+
for (auto &circle : circles) {
237+
if (circle.visible) {
238+
invalidate_circle_area(circle);
239+
}
240+
circle.visible = false;
136241
}
137-
// clear the vector
138-
circles.clear();
242+
next_circle_index = 0;
243+
visible_circle_count = 0;
139244
}

components/byte90/idf_component.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ dependencies:
2323
espp/display_drivers: '>=1.0'
2424
espp/i2c: '>=1.0'
2525
espp/interrupt: '>=1.0'
26+
espp/spi: '>=1.0'
2627
espp/task: '>=1.0'
2728
targets:
2829
- esp32s3

components/byte90/include/byte90.hpp

Lines changed: 12 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55
#include <vector>
66

77
#include <driver/gpio.h>
8-
#include <driver/spi_master.h>
98
#include <hal/spi_ll.h>
109
#include <hal/spi_types.h>
1110

1211
#include "adxl345.hpp"
1312
#include "base_component.hpp"
1413
#include "i2c.hpp"
1514
#include "interrupt.hpp"
15+
#include "spi.hpp"
1616
#include "ssd1351.hpp"
1717

1818
namespace espp {
@@ -154,6 +154,14 @@ class Byte90 : public BaseComponent {
154154
/// \return The height of the LCD in pixels
155155
static constexpr size_t lcd_height() { return lcd_height_; }
156156

157+
/// Get the display width in pixels, according to the current orientation
158+
/// \return The display width in pixels, according to the current orientation
159+
size_t rotated_display_width() const;
160+
161+
/// Get the display height in pixels, according to the current orientation
162+
/// \return The display height in pixels, according to the current orientation
163+
size_t rotated_display_height() const;
164+
157165
/// Get the GPIO pin for the LCD data/command signal
158166
/// \return The GPIO pin for the LCD data/command signal
159167
static constexpr auto get_lcd_dc_gpio() { return lcd_dc_io; }
@@ -196,15 +204,6 @@ class Byte90 : public BaseComponent {
196204
/// \note This is null unless initialize_display() has been called
197205
uint8_t *frame_buffer1() const;
198206

199-
/// Write command and optional parameters to the LCD
200-
/// \param command The command to write
201-
/// \param parameters The command parameters to write
202-
/// \param user_data User data to pass to the spi transaction callback
203-
/// \note This method is designed to be used by the display driver
204-
/// \note This method queues the data to be written to the LCD, only blocking
205-
/// if there is an ongoing SPI transaction
206-
void write_command(uint8_t command, std::span<const uint8_t> parameters, uint32_t user_data);
207-
208207
/// Write a frame to the LCD
209208
/// \param x The x coordinate
210209
/// \param y The y coordinate
@@ -216,21 +215,8 @@ class Byte90 : public BaseComponent {
216215
void write_lcd_frame(const uint16_t x, const uint16_t y, const uint16_t width,
217216
const uint16_t height, uint8_t *data);
218217

219-
/// Write lines to the LCD
220-
/// \param xs The x start coordinate
221-
/// \param ys The y start coordinate
222-
/// \param xe The x end coordinate
223-
/// \param ye The y end coordinate
224-
/// \param data The data to write
225-
/// \param user_data User data to pass to the spi transaction callback
226-
/// \note This method queues the data to be written to the LCD, only blocking
227-
/// if there is an ongoing SPI transaction
228-
void write_lcd_lines(int xs, int ys, int xe, int ye, const uint8_t *data, uint32_t user_data);
229-
230218
protected:
231219
Byte90();
232-
bool init_spi_bus();
233-
void lcd_wait_lines();
234220

235221
// common:
236222
// internal i2c (adxl345)
@@ -275,9 +261,6 @@ class Byte90 : public BaseComponent {
275261
.sda_pullup_en = GPIO_PULLUP_ENABLE,
276262
.scl_pullup_en = GPIO_PULLUP_ENABLE}};
277263

278-
// spi bus shared between sdcard and lcd
279-
std::atomic<bool> spi_bus_initialized_{false};
280-
281264
espp::Interrupt::PinConfig button_interrupt_pin_{
282265
.gpio_num = button_io,
283266
.callback =
@@ -346,12 +329,10 @@ class Byte90 : public BaseComponent {
346329

347330
// display
348331
std::shared_ptr<Display<Pixel>> display_;
349-
/// SPI bus for communication with the LCD
350-
spi_device_interface_config_t lcd_config_;
351-
spi_device_handle_t lcd_handle_{nullptr};
332+
std::unique_ptr<DisplayDriver> display_driver_;
352333
static constexpr int spi_queue_size = 6;
353-
spi_transaction_t trans[spi_queue_size];
354-
std::atomic<int> num_queued_trans = 0;
334+
std::unique_ptr<Spi> lcd_spi_;
335+
std::unique_ptr<SpiPanelIo> lcd_;
355336
uint8_t *frame_buffer0_{nullptr};
356337
uint8_t *frame_buffer1_{nullptr};
357338
}; // class Byte90

components/byte90/src/byte90.cpp

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,3 @@ using namespace espp;
44

55
Byte90::Byte90()
66
: BaseComponent("Byte90") {}
7-
8-
////////////////////////
9-
// SPI Functions //
10-
////////////////////////
11-
12-
bool Byte90::init_spi_bus() {
13-
if (spi_bus_initialized_) {
14-
return true;
15-
}
16-
17-
spi_bus_config_t bus_cfg;
18-
memset(&bus_cfg, 0, sizeof(bus_cfg));
19-
bus_cfg.mosi_io_num = spi_mosi_io;
20-
bus_cfg.miso_io_num = -1;
21-
bus_cfg.sclk_io_num = spi_sclk_io;
22-
bus_cfg.quadwp_io_num = -1;
23-
bus_cfg.quadhd_io_num = -1;
24-
bus_cfg.max_transfer_sz = SPI_MAX_TRANSFER_BYTES;
25-
auto ret = spi_bus_initialize(spi_num, &bus_cfg, SPI_DMA_CH_AUTO);
26-
if (ret != ESP_OK) {
27-
logger_.error("Failed to initialize bus.");
28-
return false;
29-
}
30-
31-
logger_.info("SPI bus initialized");
32-
spi_bus_initialized_ = true;
33-
34-
return true;
35-
}

0 commit comments

Comments
 (0)