ALXV Labs Byte90

Byte90

The Byte90 is a development board based on the ESP32-S3 module, featuring a USB-C connector, a color 128x128 OLED, a button, and an ADXL345 accelerometer, all in a little housing that makes it look like a retro computer (e.g. C64, ZX Spectrum).

See the Byte90 product page for more information.

The espp::Byte90 component provides a singleton hardware abstraction for initializing the various subsystems.

API Reference

Header File

Classes

class Byte90 : public espp::BaseComponent

The Byte90 class provides an interface to the Byte90 ESP32-S3 development board.

The class provides access to the following features:

The class is a singleton and can be accessed using the get() method.

For more information, see https://github.com/alxv2016/Byte90-alxvlabs

**Pin Reference Table:**

| XIAO Pin | GPIO | Function | Component | |———-|——|———-|———–| | D0 | GPIO1 | RST | Display Reset | | D1 | GPIO2 | INT | ADXL345 Interrupt | | A3 | GPIO4 | INPUT | Button (with pull-up) | | D4 | GPIO5 | SDA | ADXL345 I2C Data | | D5 | GPIO6 | SCL | ADXL345 I2C Clock | | D8 | GPIO7 | SCK | Display SPI Clock | | D10 | GPIO9 | MOSI | Display SPI Data | | D6 | GPIO43 | DC | Display Data/Command | | D7 | GPIO44 | CS | Display Chip Select |

Example

  espp::Byte90 &byte90 = espp::Byte90::get();
  byte90.set_log_level(espp::Logger::Verbosity::INFO);

  // initialize the accelerometer
  if (!byte90.initialize_accelerometer()) {
    logger.error("Failed to initialize accelerometer!");
  }
  // initialize the LCD
  if (!byte90.initialize_lcd()) {
    logger.error("Failed to initialize LCD!");
    return;
  }
  // set the pixel buffer to be 50 lines high
  static constexpr size_t pixel_buffer_size = byte90.lcd_width() * 50;
  // initialize the LVGL display for the Byte90
  if (!byte90.initialize_display(pixel_buffer_size)) {
    logger.error("Failed to initialize display!");
    return;
  }
  // 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%
      auto brightness = byte90.brightness();
      brightness = std::fmod(brightness + 10.0f, 100.0f);
      logger.info("Setting brightness to {:.0f}%", brightness);
      byte90.brightness(brightness);
      // lock the display mutex
      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_display_get_default();
      lv_disp_set_rotation(disp, rotation);
      update_layout();
    }
  };
  byte90.initialize_button(on_button_pressed);

  // set the background color to black
  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
  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 {
                        {
                          std::lock_guard<std::recursive_mutex> lock(lvgl_mutex);
                          lv_task_handler();
                        }
                        std::unique_lock<std::mutex> lock(m);
                        cv.wait_for(lock, 16ms);
                        return false;
                      },
                      .task_config = {
                          .name = "lv_task",
                      }});
  lv_task.start();

  // set the display brightness to be 75%
  byte90.brightness(75.0f);

  while (true) {
    auto start = esp_timer_get_time();
    // if there are 10 circles on the screen, clear them
    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)
      int middle_x = byte90.rotated_display_width() / 2;
      int middle_y = byte90.rotated_display_height() / 2;
      static constexpr int radius = 30;
      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
      std::lock_guard<std::recursive_mutex> lock(lvgl_mutex);
      draw_circle(x, y, 5);
    }
    auto end = esp_timer_get_time();
    auto elapsed = end - start;
    std::this_thread::sleep_for(100ms - std::chrono::microseconds(elapsed));
  }

Public Types

using button_callback_t = espp::Interrupt::event_callback_fn

Alias for the button callback function.

using Accelerometer = espp::Adxl345

Alias for the Accelerometer type.

using AccelerometerData = Accelerometer::Data

Alias for the Accelerometer data type.

using Pixel = lv_color16_t

Alias for the pixel type used by the Byte90 display.

using DisplayDriver = espp::Ssd1351

Alias for the display driver used by the Byte90 display.

using accel_callback_t = std::function<void(const std::chrono::high_resolution_clock::time_point &timestamp, const std::vector<AccelerometerData> &data)>

Alias for an accelerometer interrupt callback.

Public Functions

inline I2c &internal_i2c()

Get a reference to the internal I2C bus

Note

The internal I2C bus is used for the accelerometer

Returns:

A reference to the internal I2C bus

inline espp::Interrupt &interrupts()

Get a reference to the interrupts

Returns:

A reference to the interrupts

bool initialize_button(const button_callback_t &callback = nullptr)

Initialize the button

Parameters:

callback – The callback function to call when the button is pressed

Returns:

true if the button was successfully initialized, false otherwise

bool button_state() const

Get the button state

Returns:

The button state (true = button pressed, false = button released)

bool initialize_accelerometer(const accel_callback_t &callback = nullptr)

Initialize the accelerometer

Note

This will configure the accelerometer interrupt pin which will automatically call the accel callback function when the accel interrupt occurs

Parameters:

callback – The callback function to call when the accelerometer interrupt occurs

Returns:

true if the accelerometer was successfully initialized, false otherwise

inline std::shared_ptr<Accelerometer> accelerometer() const

Get the Accelerometer shared pointer

Returns:

A shared pointer to the Accelerometer instance

inline std::vector<AccelerometerData> accelerometer_data()

Get the most recent accelerometer data

Returns:

The most recent accelerometer data

bool initialize_lcd()

Initialize the LCD (low level display driver)

Returns:

true if the LCD was successfully initialized, false otherwise

bool initialize_display(size_t pixel_buffer_size)

Initialize the display (lvgl display driver)

Note

This will also allocate two full frame buffers in the SPIRAM

Parameters:

pixel_buffer_size – The size of the pixel buffer

Returns:

true if the display was successfully initialized, false otherwise

size_t rotated_display_width() const

Get the display width in pixels, according to the current orientation

Returns:

The display width in pixels, according to the current orientation

size_t rotated_display_height() const

Get the display height in pixels, according to the current orientation

Returns:

The display height in pixels, according to the current orientation

std::shared_ptr<Display<Pixel>> display() const

Get a shared pointer to the display

Returns:

A shared pointer to the display

inline const std::shared_ptr<DisplayDriver> &display_driver() const

Get a shared pointer to the low-level display driver

Returns:

A shared pointer to the display driver

void brightness(float brightness)

Set the brightness of the backlight

Parameters:

brightness – The brightness of the backlight as a percentage (0 - 100)

float brightness() const

Get the brightness of the backlight

Returns:

The brightness of the backlight as a percentage (0 - 100)

Pixel *vram0() const

Get the VRAM 0 pointer (DMA memory used by LVGL)

Note

This is the memory used by LVGL for rendering

Note

This is null unless initialize_display() has been called

Returns:

The VRAM 0 pointer

Pixel *vram1() const

Get the VRAM 1 pointer (DMA memory used by LVGL)

Note

This is the memory used by LVGL for rendering

Note

This is null unless initialize_display() has been called

Returns:

The VRAM 1 pointer

uint8_t *frame_buffer0() const

Get the frame buffer 0 pointer

Note

This memory is designed to be used by the application developer and is provided as a convenience. It is not used by the display driver.

Note

This is null unless initialize_display() has been called

Returns:

The frame buffer 0 pointer

uint8_t *frame_buffer1() const

Get the frame buffer 1 pointer

Note

This memory is designed to be used by the application developer and is provided as a convenience. It is not used by the display driver.

Note

This is null unless initialize_display() has been called

Returns:

The frame buffer 1 pointer

void write_lcd_frame(const uint16_t x, const uint16_t y, const uint16_t width, const uint16_t height, uint8_t *data)

Write a frame to the LCD

Note

This method queues the data to be written to the LCD, only blocking if there is an ongoing SPI transaction

Parameters:
  • x – The x coordinate

  • y – The y coordinate

  • width – The width of the frame, in pixels

  • height – The height of the frame, in pixels

  • data – The data to write

void write_lcd_lines(int xs, int ys, int xe, int ye, const uint8_t *data, uint32_t user_data)

Write lines to the LCD

Note

This method queues the panel transfer asynchronously and may return before the write has completed.

Parameters:
  • xs – The x start coordinate

  • ys – The y start coordinate

  • xe – The x end coordinate

  • ye – The y end coordinate

  • data – The data to write

  • user_data – User data to pass to the SPI transaction callback

inline const std::string &get_name() const

Get the name of the component

Note

This is the tag of the logger

Returns:

A const reference to the name of the component

inline void set_log_tag(const std::string_view &tag)

Set the tag for the logger

Parameters:

tag – The tag to use for the logger

inline espp::Logger::Verbosity get_log_level() const

Get the log level for the logger

Returns:

The verbosity level of the logger

inline void set_log_level(espp::Logger::Verbosity level)

Set the log level for the logger

Parameters:

level – The verbosity level to use for the logger

inline void set_log_verbosity(espp::Logger::Verbosity level)

Set the log verbosity for the logger

See also

set_log_level

Note

This is a convenience method that calls set_log_level

Parameters:

level – The verbosity level to use for the logger

inline espp::Logger::Verbosity get_log_verbosity() const

Get the log verbosity for the logger

See also

get_log_level

Note

This is a convenience method that calls get_log_level

Returns:

The verbosity level of the logger

inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)

Set the rate limit for the logger

Note

Only calls to the logger that have _rate_limit suffix will be rate limited

Parameters:

rate_limit – The rate limit to use for the logger

Public Static Functions

static inline Byte90 &get()

Access the singleton instance of the Byte90 class.

Returns:

Reference to the singleton instance of the Byte90 class

static inline constexpr size_t lcd_width()

Get the width of the LCD in pixels

Returns:

The width of the LCD in pixels

static inline constexpr size_t lcd_height()

Get the height of the LCD in pixels

Returns:

The height of the LCD in pixels

static inline constexpr auto get_lcd_dc_gpio()

Get the GPIO pin for the LCD data/command signal

Returns:

The GPIO pin for the LCD data/command signal

Public Static Attributes

static constexpr size_t SPI_MAX_TRANSFER_BYTES = SPI_LL_DMA_MAX_BIT_LEN / 8

Maximum number of bytes that can be transferred in a single SPI transaction to the Display. 32k on the ESP32-S3.