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");
  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_disp_get_default();
      lv_disp_set_rotation(disp, rotation);
    }
  };
  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());
  lv_obj_set_style_bg_color(bg, lv_color_make(0, 0, 0), 0);

  // add text in the center of the screen
  lv_obj_t *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);

  // 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
    static constexpr int max_circles = 10;
    if (circles.size() >= 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;
      static constexpr int radius = 30;
      float angle = circles.size() * 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

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

Get a shared pointer to the display

Returns:

A shared pointer to the display

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_command(uint8_t command, std::span<const uint8_t> parameters, uint32_t user_data)

Write command and optional parameters to the LCD

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

Parameters:
  • command – The command to write

  • parameters – The command parameters to write

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

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 data to be written to the LCD, only blocking if there is an ongoing SPI transaction

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