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: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ jobs:
target: esp32
- path: 'components/button/example'
target: esp32
- path: 'components/byte90/example'
target: esp32s3
- path: 'components/chsc6x/example'
target: esp32s3
- path: 'components/cli/example'
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/upload_components.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ jobs:
components/ble_gatt_server
components/bm8563
components/button
components/byte90
components/chsc6x
components/cli
components/codec
Expand Down
7 changes: 7 additions & 0 deletions components/byte90/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# only register the component if the target is esp32s3
idf_component_register(
INCLUDE_DIRS "include"
SRC_DIRS "src"
REQUIRES driver adxl345 base_component display display_drivers i2c interrupt task
REQUIRED_IDF_TARGETS "esp32s3"
)
8 changes: 8 additions & 0 deletions components/byte90/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
menu "Byte90 Configuration"
config BYTE90_INTERRUPT_STACK_SIZE
int "Interrupt stack size"
default 4096
help
Size of the stack used for the interrupt handler. Shared by the
keyboard callback and the touch callback.
endmenu
16 changes: 16 additions & 0 deletions components/byte90/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# ALXV Labs Byte90 Board Support Package (BSP) Component

[![Badge](https://components.espressif.com/components/espp/byte90/badge.svg)](https://components.espressif.com/components/espp/byte90)

The ALXV Labs Byte90 is a cute little retro computer styled ESP32-S3 system. It
features a nice OLED display, integrated battery + charging circuit, ADXL345
3-axis I2C accelerometer, and button.

The `espp::Byte90` component provides a singleton hardware abstraction for
initializing the accelerometer, interrupts, and display subsystems.

## Example

The [example](./example) shows how to use the `espp::Byte90` hardware abstraction
component initialize the components on the ALXV Labs Byte90.

21 changes: 21 additions & 0 deletions components/byte90/example/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.20)

include($ENV{IDF_PATH}/tools/cmake/project.cmake)

# add the component directories that we want to use
set(EXTRA_COMPONENT_DIRS
"../../../components/"
)

set(
COMPONENTS
"main esptool_py byte90"
CACHE STRING
"List of components to include"
)

project(byte90_example)

set(CMAKE_CXX_STANDARD 20)
39 changes: 39 additions & 0 deletions components/byte90/example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Byte90 Example

This example shows how to use the `espp::Byte90` hardware abstraction component
initialize the components on the [ALXV Labs
Byte90](https://github.com/alxv2016/Byte90-alxvlabs).

It initializes the accelerometer, button, and display subsystems.

![image](https://github.com/user-attachments/assets/2bf9fc0e-f577-408f-95ba-109806004cbc)

## How to use example

### Hardware Required

This example is designed to run on the ALXV Labs Byte90.

### Build and Flash

Build the project and flash it to the board, then run monitor tool to view
serial output:

```
idf.py -p PORT flash monitor
```

(Replace PORT with the name of the serial port to use.)

(To exit the serial monitor, type ``Ctrl-]``.)

See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.

## Example Output

![CleanShot 2025-06-05 at 15 33 16](https://github.com/user-attachments/assets/538f9744-f5b0-4b56-b6f7-7924b669307b)

![image](https://github.com/user-attachments/assets/2bf9fc0e-f577-408f-95ba-109806004cbc)
![image](https://github.com/user-attachments/assets/41b30c8a-1a73-47a0-ab3a-9c96ed0a2a3f)
![image](https://github.com/user-attachments/assets/29b4c473-cd81-4799-a67f-95af3a97e744)
![image](https://github.com/user-attachments/assets/4632ea35-53a3-4357-b324-d46927218436)
2 changes: 2 additions & 0 deletions components/byte90/example/main/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
idf_component_register(SRC_DIRS "."
INCLUDE_DIRS ".")
139 changes: 139 additions & 0 deletions components/byte90/example/main/byte90_example.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#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 std::recursive_mutex lvgl_mutex;
static void draw_circle(int x0, int y0, int radius);
static void clear_circles();

extern "C" void app_main(void) {
espp::Logger logger({.tag = "Byte90 Example", .level = espp::Logger::Verbosity::INFO});
logger.info("Starting example!");

//! [byte90 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));
}
//! [byte90 example]
}

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();
}
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);
}

static void clear_circles() {
// remove the circles from lvgl
for (auto circle : circles) {
lv_obj_delete(circle);
}
// clear the vector
circles.clear();
}
47 changes: 47 additions & 0 deletions components/byte90/example/sdkconfig.defaults
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
CONFIG_IDF_TARGET="esp32s3"

CONFIG_FREERTOS_HZ=1000

# set compiler optimization level to -O2 (compile for performance)
CONFIG_COMPILER_OPTIMIZATION_PERF=y

CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y
CONFIG_ESPTOOLPY_FLASHSIZE="8MB"
# over twice as fast as DIO
CONFIG_ESPTOOLPY_FLASHMODE_QIO=y

# ESP32-specific
#
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=240

# Common ESP-related
#
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=4096
CONFIG_ESP_MAIN_TASK_STACK_SIZE=16384

# Set esp-timer task stack size to 6KB
CONFIG_ESP_TIMER_TASK_STACK_SIZE=6144

# set the functions into IRAM
CONFIG_SPI_MASTER_IN_IRAM=y

#
# LVGL configuration - # Color settings
#
# CONFIG_LV_COLOR_DEPTH_32 is not set
CONFIG_LV_COLOR_DEPTH_16=y
# CONFIG_LV_COLOR_DEPTH_8 is not set
# CONFIG_LV_COLOR_DEPTH_1 is not set
CONFIG_LV_COLOR_DEPTH=16
CONFIG_LV_COLOR_16_SWAP=y
CONFIG_LV_COLOR_MIX_ROUND_OFS=128
CONFIG_LV_COLOR_CHROMA_KEY_HEX=0x00FF00

#
# LVGL configuration - # Themes
#
CONFIG_LV_USE_THEME_DEFAULT=y
CONFIG_LV_THEME_DEFAULT_DARK=y
CONFIG_LV_THEME_DEFAULT_GROW=y
CONFIG_LV_THEME_DEFAULT_TRANSITION_TIME=80
26 changes: 26 additions & 0 deletions components/byte90/idf_component.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
## IDF Component Manager Manifest File
license: "MIT"
description: "ALXV Labs Byte90 Board Support Package (BSP) component for ESPP"
url: "https://github.com/esp-cpp/espp/tree/main/components/byte90"
repository: "git://github.com/esp-cpp/espp.git"
maintainers:
- William Emfinger <waemfinger@gmail.com>
documentation: "https://esp-cpp.github.io/espp/byte90.html"
examples:
- path: example
tags:
- cpp
- Component
- ALXV-Labs
- Byte90
- BSP
dependencies:
idf:
version: '>=5.0'
espp/adxl345: '>=1.0'
espp/base_component: '>=1.0'
espp/display: '>=1.0'
espp/display_drivers: '>=1.0'
espp/i2c: '>=1.0'
espp/interrupt: '>=1.0'
espp/task: '>=1.0'
Loading