Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion components/byte90/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
idf_component_register(
INCLUDE_DIRS "include"
SRC_DIRS "src"
REQUIRES driver adxl345 base_component display display_drivers i2c interrupt task
REQUIRES driver adxl345 base_component display display_drivers i2c interrupt spi task
REQUIRED_IDF_TARGETS "esp32s3"
)
165 changes: 135 additions & 30 deletions components/byte90/example/main/byte90_example.cpp
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
#include <array>
#include <chrono>
#include <deque>
#include <stdlib.h>

#include "byte90.hpp"

using namespace std::chrono_literals;

static constexpr size_t MAX_CIRCLES = 100;
static std::deque<lv_obj_t *> circles;
static constexpr size_t MAX_CIRCLES = 10;
struct Circle {
int x{0};
int y{0};
int radius{0};
bool visible{false};
};
static std::array<Circle, MAX_CIRCLES> circles;
static size_t next_circle_index = 0;
static size_t visible_circle_count = 0;
static lv_obj_t *circle_layer = nullptr;

static std::recursive_mutex lvgl_mutex;
static bool initialize_circle_layer(int width, int height);
static void draw_circle_layer(lv_event_t *event);
static void invalidate_circle_area(const Circle &circle);
static void draw_circle(int x0, int y0, int radius);
static void clear_circles();

Expand Down Expand Up @@ -39,6 +51,20 @@ extern "C" void app_main(void) {
}
// initialize the button, which we'll use to cycle the rotation of the display
logger.info("Initializing the button");
lv_obj_t *bg = nullptr;
lv_obj_t *label = nullptr;
static auto update_layout = [&]() {
int width = byte90.rotated_display_width();
int height = byte90.rotated_display_height();
lv_obj_set_size(bg, width, height);
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
if (circle_layer) {
lv_obj_set_size(circle_layer, width, height);
lv_obj_align(circle_layer, LV_ALIGN_CENTER, 0, 0);
lv_obj_move_foreground(circle_layer);
lv_obj_invalidate(circle_layer);
}
};
auto on_button_pressed = [&](const auto &event) {
if (event.active) {
// increment the brightness by 10%, looping back to 0% after 100%
Expand All @@ -50,22 +76,30 @@ extern "C" void app_main(void) {
std::lock_guard<std::recursive_mutex> lock(lvgl_mutex);
static auto rotation = LV_DISPLAY_ROTATION_0;
rotation = static_cast<lv_display_rotation_t>((static_cast<int>(rotation) + 1) % 4);
lv_display_t *disp = lv_disp_get_default();
lv_display_t *disp = lv_display_get_default();
lv_disp_set_rotation(disp, rotation);
update_layout();
}
};
byte90.initialize_button(on_button_pressed);

// set the background color to black
lv_obj_t *bg = lv_obj_create(lv_screen_active());
lv_obj_set_size(bg, byte90.lcd_width(), byte90.lcd_height());
bg = lv_obj_create(lv_screen_active());
lv_obj_set_size(bg, byte90.rotated_display_width(), byte90.rotated_display_height());
lv_obj_set_style_bg_color(bg, lv_color_make(0, 0, 0), 0);
if (!initialize_circle_layer(byte90.rotated_display_width(), byte90.rotated_display_height())) {
logger.error("Failed to initialize circle layer!");
return;
}

// add text in the center of the screen
lv_obj_t *label = lv_label_create(lv_screen_active());
label = lv_label_create(lv_screen_active());
lv_label_set_text(label, "Drawing circles\nto the screen.");
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0);
update_layout();

lv_obj_move_foreground(circle_layer);

// start a simple thread to do the lv_task_handler every 16ms
espp::Task lv_task({.callback = [](std::mutex &m, std::condition_variable &cv) -> bool {
Expand All @@ -88,17 +122,16 @@ extern "C" void app_main(void) {
while (true) {
auto start = esp_timer_get_time();
// if there are 10 circles on the screen, clear them
static constexpr int max_circles = 10;
if (circles.size() >= max_circles) {
if (visible_circle_count >= MAX_CIRCLES) {
// lock the lvgl mutex
std::lock_guard<std::recursive_mutex> lock(lvgl_mutex);
clear_circles();
} else {
// draw a circle of circles on the screen (just draw the next circle)
static constexpr int middle_x = byte90.lcd_width() / 2;
static constexpr int middle_y = byte90.lcd_height() / 2;
int middle_x = byte90.rotated_display_width() / 2;
int middle_y = byte90.rotated_display_height() / 2;
static constexpr int radius = 30;
float angle = circles.size() * 2.0f * M_PI / max_circles;
float angle = visible_circle_count * 2.0f * M_PI / MAX_CIRCLES;
int x = middle_x + radius * cos(angle);
int y = middle_y + radius * sin(angle);
// lock the lvgl mutex
Expand All @@ -112,28 +145,100 @@ extern "C" void app_main(void) {
//! [byte90 example]
}

static bool initialize_circle_layer(int width, int height) {
if (circle_layer) {
return true;
}
circle_layer = lv_obj_create(lv_screen_active());
if (!circle_layer) {
return false;
}
lv_obj_remove_style_all(circle_layer);
lv_obj_set_size(circle_layer, width, height);
lv_obj_align(circle_layer, LV_ALIGN_CENTER, 0, 0);
lv_obj_clear_flag(circle_layer, LV_OBJ_FLAG_CLICKABLE);
lv_obj_clear_flag(circle_layer, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_set_style_bg_opa(circle_layer, LV_OPA_TRANSP, 0);
lv_obj_set_style_border_width(circle_layer, 0, 0);
lv_obj_set_style_outline_width(circle_layer, 0, 0);
lv_obj_set_style_shadow_width(circle_layer, 0, 0);
lv_obj_add_event_cb(circle_layer, draw_circle_layer, LV_EVENT_DRAW_MAIN, nullptr);
return true;
}

static void draw_circle_layer(lv_event_t *event) {
if (visible_circle_count == 0) {
return;
}
auto *obj = static_cast<lv_obj_t *>(lv_event_get_current_target(event));
auto *layer = lv_event_get_layer(event);
lv_area_t obj_coords;
lv_obj_get_coords(obj, &obj_coords);

lv_draw_rect_dsc_t rect_dsc;
lv_draw_rect_dsc_init(&rect_dsc);
rect_dsc.base.layer = layer;
rect_dsc.radius = LV_RADIUS_CIRCLE;
rect_dsc.bg_opa = LV_OPA_70;
rect_dsc.bg_color = lv_color_make(0, 255, 255);
rect_dsc.border_width = 0;
rect_dsc.outline_width = 0;
rect_dsc.shadow_width = 0;

for (const auto &circle : circles) {
if (!circle.visible) {
continue;
}
lv_area_t coords = {
.x1 = static_cast<lv_coord_t>(obj_coords.x1 + circle.x - circle.radius),
.y1 = static_cast<lv_coord_t>(obj_coords.y1 + circle.y - circle.radius),
.x2 = static_cast<lv_coord_t>(obj_coords.x1 + circle.x + circle.radius - 1),
.y2 = static_cast<lv_coord_t>(obj_coords.y1 + circle.y + circle.radius - 1),
};
lv_draw_rect(layer, &rect_dsc, &coords);
}
}

static void invalidate_circle_area(const Circle &circle) {
if (!circle_layer || circle.radius <= 0) {
return;
}

lv_area_t obj_coords;
lv_obj_get_coords(circle_layer, &obj_coords);
lv_area_t coords = {
.x1 = static_cast<lv_coord_t>(obj_coords.x1 + circle.x - circle.radius),
.y1 = static_cast<lv_coord_t>(obj_coords.y1 + circle.y - circle.radius),
.x2 = static_cast<lv_coord_t>(obj_coords.x1 + circle.x + circle.radius - 1),
.y2 = static_cast<lv_coord_t>(obj_coords.y1 + circle.y + circle.radius - 1),
};
lv_obj_invalidate_area(circle_layer, &coords);
}

static void draw_circle(int x0, int y0, int radius) {
// if we have too many circles, remove the oldest one
if (circles.size() >= MAX_CIRCLES) {
lv_obj_delete(circles.front());
circles.pop_front();
if (!circle_layer) {
return;
}
lv_obj_t *my_Cir = lv_obj_create(lv_screen_active());
lv_obj_set_scrollbar_mode(my_Cir, LV_SCROLLBAR_MODE_OFF);
lv_obj_set_size(my_Cir, radius * 2, radius * 2);
lv_obj_set_pos(my_Cir, x0 - radius, y0 - radius);
lv_obj_set_style_radius(my_Cir, LV_RADIUS_CIRCLE, 0);
// ensure the circle ignores touch events (so things behind it can still be
// interacted with)
lv_obj_clear_flag(my_Cir, LV_OBJ_FLAG_CLICKABLE);
circles.push_back(my_Cir);
lv_obj_move_foreground(circle_layer);
Circle previous_circle = circles[next_circle_index];
circles[next_circle_index] = {.x = x0, .y = y0, .radius = radius, .visible = true};
next_circle_index = (next_circle_index + 1) % circles.size();
if (visible_circle_count < circles.size()) {
visible_circle_count++;
}
if (previous_circle.visible) {
invalidate_circle_area(previous_circle);
}
invalidate_circle_area(circles[(next_circle_index + circles.size() - 1) % circles.size()]);
}

static void clear_circles() {
// remove the circles from lvgl
for (auto circle : circles) {
lv_obj_delete(circle);
for (auto &circle : circles) {
if (circle.visible) {
invalidate_circle_area(circle);
}
circle.visible = false;
}
// clear the vector
circles.clear();
next_circle_index = 0;
visible_circle_count = 0;
}
1 change: 1 addition & 0 deletions components/byte90/idf_component.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ dependencies:
espp/display_drivers: '>=1.0'
espp/i2c: '>=1.0'
espp/interrupt: '>=1.0'
espp/spi: '>=1.0'
espp/task: '>=1.0'
targets:
- esp32s3
43 changes: 12 additions & 31 deletions components/byte90/include/byte90.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
#include <vector>

#include <driver/gpio.h>
#include <driver/spi_master.h>
#include <hal/spi_ll.h>
#include <hal/spi_types.h>

#include "adxl345.hpp"
#include "base_component.hpp"
#include "i2c.hpp"
#include "interrupt.hpp"
#include "spi.hpp"
#include "ssd1351.hpp"

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

/// Get the display width in pixels, according to the current orientation
/// \return The display width in pixels, according to the current orientation
size_t rotated_display_width() const;

/// Get the display height in pixels, according to the current orientation
/// \return The display height in pixels, according to the current orientation
size_t rotated_display_height() const;

/// Get the GPIO pin for the LCD data/command signal
/// \return The GPIO pin for the LCD data/command signal
static constexpr auto get_lcd_dc_gpio() { return lcd_dc_io; }
Expand Down Expand Up @@ -196,15 +204,6 @@ class Byte90 : public BaseComponent {
/// \note This is null unless initialize_display() has been called
uint8_t *frame_buffer1() const;

/// Write command and optional parameters to the LCD
/// \param command The command to write
/// \param parameters The command parameters to write
/// \param user_data User data to pass to the spi transaction callback
/// \note This method is designed to be used by the display driver
/// \note This method queues the data to be written to the LCD, only blocking
/// if there is an ongoing SPI transaction
void write_command(uint8_t command, std::span<const uint8_t> parameters, uint32_t user_data);

/// Write a frame to the LCD
/// \param x The x coordinate
/// \param y The y coordinate
Expand All @@ -216,21 +215,8 @@ class Byte90 : public BaseComponent {
void write_lcd_frame(const uint16_t x, const uint16_t y, const uint16_t width,
const uint16_t height, uint8_t *data);

/// Write lines to the LCD
/// \param xs The x start coordinate
/// \param ys The y start coordinate
/// \param xe The x end coordinate
/// \param ye The y end coordinate
/// \param data The data to write
/// \param user_data User data to pass to the spi transaction callback
/// \note This method queues the data to be written to the LCD, only blocking
/// if there is an ongoing SPI transaction
void write_lcd_lines(int xs, int ys, int xe, int ye, const uint8_t *data, uint32_t user_data);

protected:
Byte90();
bool init_spi_bus();
void lcd_wait_lines();

// common:
// internal i2c (adxl345)
Expand Down Expand Up @@ -275,9 +261,6 @@ class Byte90 : public BaseComponent {
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE}};

// spi bus shared between sdcard and lcd
std::atomic<bool> spi_bus_initialized_{false};

espp::Interrupt::PinConfig button_interrupt_pin_{
.gpio_num = button_io,
.callback =
Expand Down Expand Up @@ -346,12 +329,10 @@ class Byte90 : public BaseComponent {

// display
std::shared_ptr<Display<Pixel>> display_;
/// SPI bus for communication with the LCD
spi_device_interface_config_t lcd_config_;
spi_device_handle_t lcd_handle_{nullptr};
std::unique_ptr<DisplayDriver> display_driver_;
static constexpr int spi_queue_size = 6;
spi_transaction_t trans[spi_queue_size];
std::atomic<int> num_queued_trans = 0;
std::unique_ptr<Spi> lcd_spi_;
std::unique_ptr<SpiPanelIo> lcd_;
uint8_t *frame_buffer0_{nullptr};
uint8_t *frame_buffer1_{nullptr};
}; // class Byte90
Expand Down
29 changes: 0 additions & 29 deletions components/byte90/src/byte90.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,3 @@ using namespace espp;

Byte90::Byte90()
: BaseComponent("Byte90") {}

////////////////////////
// SPI Functions //
////////////////////////

bool Byte90::init_spi_bus() {
if (spi_bus_initialized_) {
return true;
}

spi_bus_config_t bus_cfg;
memset(&bus_cfg, 0, sizeof(bus_cfg));
bus_cfg.mosi_io_num = spi_mosi_io;
bus_cfg.miso_io_num = -1;
bus_cfg.sclk_io_num = spi_sclk_io;
bus_cfg.quadwp_io_num = -1;
bus_cfg.quadhd_io_num = -1;
bus_cfg.max_transfer_sz = SPI_MAX_TRANSFER_BYTES;
auto ret = spi_bus_initialize(spi_num, &bus_cfg, SPI_DMA_CH_AUTO);
if (ret != ESP_OK) {
logger_.error("Failed to initialize bus.");
return false;
}

logger_.info("SPI bus initialized");
spi_bus_initialized_ = true;

return true;
}
Loading
Loading