Added Lilygo T-Deck support & more (#4)

* added lilygo t-deck

restructured boards
implemented HardwareConfig
implemented lilygo t-deck lcd and touch drivers
added sdkconfig defaults for supported boards

* cleanup

* added esp32s3 job

* build job names updated

* wip

* partial revert

* update readme and build.yml

* updated build.yaml with fix for quotes

* use esp-idf 5.1.2

* improvements and fixes

* fixes for display code

* made config const

* various improvements
This commit is contained in:
Ken Van Hoeylandt 2024-01-05 17:01:39 +01:00 committed by GitHub
parent eed990217f
commit 8336316133
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 747 additions and 551 deletions

View File

@ -1,16 +1,33 @@
name: Build
on: [push]
jobs:
build:
Build-Yellow-Board:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v2
with:
submodules: recursive
- name: esp-idf build
- name: Board select
run: cp sdkconfig.board.yellow_board sdkconfig
- name: esp32 build
uses: espressif/esp-idf-ci-action@main
with:
esp_idf_version: v5.1
esp_idf_version: v5.1.2
target: esp32
path: './'
Build-Lilygo-T-Deck:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v2
with:
submodules: recursive
- name: Board select
run: cp sdkconfig.board.lilygo_tdeck sdkconfig
- name: esp32s3 build
uses: espressif/esp-idf-ci-action@main
with:
esp_idf_version: v5.1.2
target: esp32s3
path: './'

View File

@ -3,6 +3,17 @@ cmake_minimum_required(VERSION 3.16)
add_definitions(-DFURI_DEBUG)
set(COMPONENTS main)
set(EXTRA_COMPONENT_DIRS "boards" "components")
# Yellow Board only runs on ESP32
if(NOT "${IDF_TARGET}" STREQUAL "esp32")
set(EXCLUDE_COMPONENTS "yellow_board_2432s024")
endif()
# T-Deck is an S3 platform
if(NOT "${IDF_TARGET}" STREQUAL "esp32s3")
set(EXCLUDE_COMPONENTS "lilygo_tdeck")
endif()
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(NanoBake)

View File

@ -1,6 +1,7 @@
## Overview
NanoBake is a front-end application platform for ESP32. It provides an application framework that is based on code from the [Flipper Zero](https://github.com/flipperdevices/flipperzero-firmware/) project.
NanoBake is a front-end application platform for ESP32.
It provides an application framework that is based on code from the [Flipper Zero](https://github.com/flipperdevices/flipperzero-firmware/) project.
Nanobake provides:
- A hardware abstraction layer
@ -16,11 +17,11 @@ Requirements:
## Technologies
UI is created with [lvgl](https://github.com/lvgl/lvgl) via [esp_lvgl_port](https://github.com/espressif/esp-bsp/tree/master/components/esp_lvgl_port).
LCD and input drivers are based on [esp_lcd](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/lcd.html)
and [esp_lcd_touch](https://components.espressif.com/components/espressif/esp_lcd_touch).
UI is created with [lvgl](https://github.com/lvgl/lvgl) via [esp_lvgl_port](https://github.com/espressif/esp-bsp/tree/master/components/esp_lvgl_port).
## Supported Hardware
**NOTE**: `sdkconfig.defaults` currently contains `CONFIG_LV_COLOR_16_SWAP=y`.
@ -28,33 +29,15 @@ You might have to remove this setting if you're not using the Yellow Board descr
### Devices
See below for the supported hardware.
Predefined configurations are available for:
- Yellow Board / 2432S024 (capacitive touch variant)
- Yellow Board: 2.4" with capacitive touch (2432S024) (sources: AliExpress [1](https://www.aliexpress.com/item/1005005902429049.html), [2](https://www.aliexpress.com/item/1005005865107357.html))
- LilyGo T-Deck
- (more will follow)
### Drivers
Other configurations can be supported, but they require you to set up the drivers yourself:
**Displays** (see [esp-bsp](https://github.com/espressif/esp-bsp/blob/master/LCD.md) and [Espressif Registry](https://components.espressif.com/components?q=esp_lcd)):
- GC9503
- GC9A01
- ILI9341
- RA8875
- RM68120
- SH1107
- SSD1306
- SSD1963
- ST7262E43
- ST7789
- ST7796
**Touch** (see [Espressif Registry](https://components.espressif.com/components?q=esp_lcd_touch)):
- CST8xx
- FT5X06
- GT1151
- GT911
- STMPE610
- TT2100
- Display drivers: [esp-bsp](https://github.com/espressif/esp-bsp/blob/master/LCD.md) and [Espressif Registry](https://components.espressif.com/components?q=esp_lcd).
- Touch drivers: [Espressif Registry](https://components.espressif.com/components?q=esp_lcd_touch).
## Guide
@ -63,6 +46,16 @@ Until there is proper documentation, here are some pointers:
- [NanoBake](./components/nanobake/inc)
- [Furi](./components/furi/src)
## Building Firmware
First we have to select the correct device:
1. If you use CLion, close it and delete the `cmake-build-debug` folder.
2. If you have a `build` folder, then delete it or run `idf.py fullclean`
3. Copy the `sdkconfig.board.YOUR_BOARD` into `sdkconfig`
Now you can run `idf.py flash monitor`
## License
[GNU General Public License Version 3](LICENSE.md)

View File

@ -0,0 +1,5 @@
idf_component_register(
SRC_DIRS "."
INCLUDE_DIRS "."
REQUIRES nanobake esp_lcd esp_lcd_touch_gt911
)

View File

@ -0,0 +1,26 @@
#include "esp_log.h"
#include "driver/gpio.h"
#include "kernel.h"
#define TAG "lilygo_tdeck_bootstrap"
#define TDECK_PERI_POWERON GPIO_NUM_10
static void tdeck_power_on() {
ESP_LOGI(TAG, "power on");
gpio_config_t device_power_signal_config = {
.pin_bit_mask = BIT64(TDECK_PERI_POWERON),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&device_power_signal_config);
gpio_set_level(TDECK_PERI_POWERON, 1);
}
void lilygo_tdeck_bootstrap() {
tdeck_power_on();
// Give keyboard's ESP time to boot
// It uses I2C and seems to interfere with the touch driver
furi_delay_ms(500);
}

View File

@ -0,0 +1,168 @@
#include "nanobake.h"
#include "esp_log.h"
#include "esp_err.h"
#include "esp_lcd_panel_ops.h"
#include "driver/spi_master.h"
#include "esp_lcd_panel_vendor.h"
#include "driver/ledc.h"
#define TAG "lilygo_tdeck_display"
#define LCD_SPI_HOST SPI2_HOST
#define LCD_PIN_SCLK GPIO_NUM_40
#define LCD_PIN_MOSI GPIO_NUM_41
#define LCD_PIN_MISO GPIO_NUM_38
#define LCD_PIN_CS GPIO_NUM_12
#define LCD_PIN_DC GPIO_NUM_11 // RS
#define LCD_PIN_BACKLIGHT GPIO_NUM_42
#define LCD_SPI_FREQUENCY 40000000
#define LCD_HORIZONTAL_RESOLUTION 320
#define LCD_VERTICAL_RESOLUTION 240
#define LCD_BITS_PER_PIXEL 16
#define LCD_DRAW_BUFFER_HEIGHT (LCD_VERTICAL_RESOLUTION / 10)
// Backlight PWM
#define LCD_BACKLIGHT_LEDC_TIMER LEDC_TIMER_0
#define LCD_BACKLIGHT_LEDC_MODE LEDC_LOW_SPEED_MODE
#define LCD_BACKLIGHT_LEDC_OUTPUT_IO LCD_PIN_BACKLIGHT
#define LCD_BACKLIGHT_LEDC_CHANNEL LEDC_CHANNEL_0
#define LCD_BACKLIGHT_LEDC_DUTY_RES LEDC_TIMER_8_BIT
#define LCD_BACKLIGHT_LEDC_DUTY (191)
#define LCD_BACKLIGHT_LEDC_FREQUENCY (1000)
static void tdeck_backlight() {
ESP_LOGI(TAG, "enable backlight");
ledc_timer_config_t ledc_timer = {
.speed_mode = LCD_BACKLIGHT_LEDC_MODE,
.timer_num = LCD_BACKLIGHT_LEDC_TIMER,
.duty_resolution = LCD_BACKLIGHT_LEDC_DUTY_RES,
.freq_hz = LCD_BACKLIGHT_LEDC_FREQUENCY,
.clk_cfg = LEDC_AUTO_CLK
};
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));
ledc_channel_config_t ledc_channel = {
.speed_mode = LCD_BACKLIGHT_LEDC_MODE,
.channel = LCD_BACKLIGHT_LEDC_CHANNEL,
.timer_sel = LCD_BACKLIGHT_LEDC_TIMER,
.intr_type = LEDC_INTR_DISABLE,
.gpio_num = LCD_BACKLIGHT_LEDC_OUTPUT_IO,
.duty = 0, // Set duty to 0%
.hpoint = 0
};
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
ESP_ERROR_CHECK(ledc_set_duty(LCD_BACKLIGHT_LEDC_MODE, LCD_BACKLIGHT_LEDC_CHANNEL, LCD_BACKLIGHT_LEDC_DUTY));
}
static bool create_display_device(DisplayDevice* display) {
ESP_LOGI(TAG, "creating display");
int draw_buffer_size = LCD_HORIZONTAL_RESOLUTION * LCD_DRAW_BUFFER_HEIGHT * (LCD_BITS_PER_PIXEL / 8);
spi_bus_config_t bus_config = {
.sclk_io_num = LCD_PIN_SCLK,
.mosi_io_num = LCD_PIN_MOSI,
.miso_io_num = LCD_PIN_MISO,
.quadwp_io_num = -1, // Quad SPI LCD driver is not yet supported
.quadhd_io_num = -1, // Quad SPI LCD driver is not yet supported
.max_transfer_sz = draw_buffer_size,
};
if (spi_bus_initialize(LCD_SPI_HOST, &bus_config, SPI_DMA_CH_AUTO) != ESP_OK) {
ESP_LOGD(TAG, "spi bus init failed");
return false;
}
const esp_lcd_panel_io_spi_config_t panel_io_config = {
.cs_gpio_num = LCD_PIN_CS,
.dc_gpio_num = LCD_PIN_DC,
.spi_mode = 0,
.pclk_hz = LCD_SPI_FREQUENCY,
.trans_queue_depth = 10,
.on_color_trans_done = NULL,
.user_ctx = NULL,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.flags = {
.dc_low_on_data = 0,
.octal_mode = 0,
.quad_mode = 0,
.sio_mode = 1,
.lsb_first = 0,
.cs_high_active = 0,
}
};
if (esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_SPI_HOST, &panel_io_config, &display->io_handle) != ESP_OK) {
ESP_LOGD(TAG, "failed to create panel IO");
return false;
}
ESP_LOGI(TAG, "install driver");
const esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = GPIO_NUM_NC,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR,
.data_endian = LCD_RGB_DATA_ENDIAN_BIG,
.bits_per_pixel = LCD_BITS_PER_PIXEL,
.flags = {
.reset_active_high = 0
},
.vendor_config = NULL
};
if (esp_lcd_new_panel_st7789(display->io_handle, &panel_config, &display->display_handle) != ESP_OK) {
ESP_LOGD(TAG, "failed to create panel");
return false;
}
if (esp_lcd_panel_reset(display->display_handle) != ESP_OK) {
ESP_LOGD(TAG, "failed to reset panel");
return false;
}
if (esp_lcd_panel_init(display->display_handle) != ESP_OK) {
ESP_LOGD(TAG, "failed to init panel");
return false;
}
if (esp_lcd_panel_invert_color(display->display_handle, true) != ESP_OK) {
ESP_LOGD(TAG, "failed to init panel");
return false;
}
if (esp_lcd_panel_swap_xy(display->display_handle, true) != ESP_OK) {
ESP_LOGD(TAG, "failed to init panel");
return false;
}
if (esp_lcd_panel_mirror(display->display_handle, true, false) != ESP_OK) {
ESP_LOGD(TAG, "failed to init panel");
return false;
}
if (esp_lcd_panel_disp_on_off(display->display_handle, true) != ESP_OK) {
ESP_LOGD(TAG, "failed to turn display on");
return false;
}
display->horizontal_resolution = LCD_HORIZONTAL_RESOLUTION;
display->vertical_resolution = LCD_VERTICAL_RESOLUTION;
display->draw_buffer_height = LCD_DRAW_BUFFER_HEIGHT;
display->bits_per_pixel = LCD_BITS_PER_PIXEL;
display->monochrome = false;
display->double_buffering = false;
tdeck_backlight();
return true;
}
DisplayDriver lilygo_tdeck_display_driver() {
return (DisplayDriver) {
.name = "lilygo_tdeck_display",
.create_display_device = &create_display_device
};
}

View File

@ -0,0 +1,11 @@
#include "lilygo_tdeck.h"
void lilygo_tdeck_bootstrap();
DisplayDriver lilygo_tdeck_display_driver();
TouchDriver lilygo_tdeck_touch_driver();
const HardwareConfig lilygo_tdeck = {
.bootstrap = &lilygo_tdeck_bootstrap,
.display_driver = &lilygo_tdeck_display_driver,
.touch_driver = &lilygo_tdeck_touch_driver
};

View File

@ -0,0 +1,5 @@
#pragma once
#include "nanobake.h"
extern const HardwareConfig lilygo_tdeck;

View File

@ -0,0 +1,74 @@
#include "nanobake.h"
#include "esp_lcd_touch_gt911.h"
#include "esp_log.h"
#include "esp_err.h"
#include "driver/i2c.h"
#define TOUCH_I2C_PORT 0
#define TAG "lilygo_tdeck_touch"
static bool create_touch_device(esp_lcd_panel_io_handle_t* io_handle, esp_lcd_touch_handle_t* touch_handle) {
ESP_LOGI(TAG, "creating touch");
const i2c_config_t i2c_conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = GPIO_NUM_18,
.sda_pullup_en = GPIO_PULLUP_DISABLE,
.scl_io_num = GPIO_NUM_8,
.scl_pullup_en = GPIO_PULLUP_DISABLE,
.master.clk_speed = 400000
};
if (i2c_param_config(TOUCH_I2C_PORT, &i2c_conf) != ESP_OK) {
ESP_LOGE(TAG, "i2c config failed");
return false;
}
if (i2c_driver_install(TOUCH_I2C_PORT, i2c_conf.mode, 0, 0, 0) != ESP_OK) {
ESP_LOGE(TAG, "i2c driver install failed");
return false;
}
const esp_lcd_panel_io_i2c_config_t touch_io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG();
if (esp_lcd_new_panel_io_i2c((esp_lcd_i2c_bus_handle_t)TOUCH_I2C_PORT, &touch_io_config, io_handle) != ESP_OK) {
ESP_LOGE(TAG, "touch io i2c creation failed");
return false;
}
ESP_LOGI(TAG, "create_touch");
esp_lcd_touch_config_t config = {
.x_max = 240,
.y_max = 320,
.rst_gpio_num = GPIO_NUM_NC,
.int_gpio_num = GPIO_NUM_16,
.levels = {
.reset = 0,
.interrupt = 0,
},
.flags = {
.swap_xy = 1,
.mirror_x = 1,
.mirror_y = 0,
},
.process_coordinates = NULL,
.interrupt_callback = NULL,
.user_data = NULL
};
if (esp_lcd_touch_new_i2c_gt911(*io_handle, &config, touch_handle) != ESP_OK) {
ESP_LOGE(TAG, "gt911 driver creation failed");
return false;
}
return true;
}
TouchDriver lilygo_tdeck_touch_driver() {
return (TouchDriver) {
.name = "lilygo_tdeck_touch",
.create_touch_device = &create_touch_device
};
}

View File

@ -1,5 +1,4 @@
#include "board_2432s024_display.h"
#include "nanobake.h"
#include "esp_lcd_ili9341.h"
#include "esp_log.h"
#include "esp_err.h"
@ -23,13 +22,7 @@ static SemaphoreHandle_t refresh_finish = NULL;
#define LCD_HORIZONTAL_RESOLUTION 240
#define LCD_VERTICAL_RESOLUTION 320
#define LCD_BITS_PER_PIXEL 16
#define LCD_DRAW_BUFFER_HEIGHT 80
IRAM_ATTR static bool prv_on_color_trans_done(esp_lcd_panel_io_handle_t io_handle, esp_lcd_panel_io_event_data_t* edata, void* user_ctx) {
BaseType_t need_yield = pdFALSE;
xSemaphoreGiveFromISR(refresh_finish, &need_yield);
return (need_yield == pdTRUE);
}
#define LCD_DRAW_BUFFER_HEIGHT (LCD_VERTICAL_RESOLUTION / 10)
static bool create_display_device(DisplayDevice* display) {
ESP_LOGI(TAG, "creating display");
@ -58,7 +51,7 @@ static bool create_display_device(DisplayDevice* display) {
const esp_lcd_panel_io_spi_config_t panel_io_config = ILI9341_PANEL_IO_SPI_CONFIG(
LCD_PIN_CS,
LCD_PIN_DC,
prv_on_color_trans_done,
NULL,
NULL
);
@ -118,6 +111,7 @@ static bool create_display_device(DisplayDevice* display) {
display->draw_buffer_height = LCD_DRAW_BUFFER_HEIGHT;
display->bits_per_pixel = LCD_BITS_PER_PIXEL;
display->monochrome = false;
display->double_buffering = true;
return true;
}

View File

@ -1,11 +1,10 @@
#include "board_2432s024_touch.h"
#include "nanobake.h"
#include "esp_lcd_touch_cst816s.h"
#include "esp_log.h"
#include "esp_err.h"
#include "driver/i2c.h"
#define CST816_I2C_PORT (0)
#define TOUCH_I2C_PORT 0
#define TAG "2432s024_cst816"
@ -21,20 +20,20 @@ static bool create_touch_device(esp_lcd_panel_io_handle_t* io_handle, esp_lcd_to
.master.clk_speed = 400000
};
if (i2c_param_config(CST816_I2C_PORT, &i2c_conf) != ESP_OK) {
if (i2c_param_config(TOUCH_I2C_PORT, &i2c_conf) != ESP_OK) {
ESP_LOGE(TAG, "i2c config failed");
return false;
}
if (i2c_driver_install(CST816_I2C_PORT, i2c_conf.mode, 0, 0, 0) != ESP_OK) {
if (i2c_driver_install(TOUCH_I2C_PORT, i2c_conf.mode, 0, 0, 0) != ESP_OK) {
ESP_LOGE(TAG, "i2c driver install failed");
return false;
}
const esp_lcd_panel_io_i2c_config_t touch_io_config = ESP_LCD_TOUCH_IO_I2C_CST816S_CONFIG();
if (esp_lcd_new_panel_io_i2c((esp_lcd_i2c_bus_handle_t)CST816_I2C_PORT, &touch_io_config, io_handle) != ESP_OK) {
ESP_LOGE(TAG, "esp_lcd_panel creation failed");
if (esp_lcd_new_panel_io_i2c((esp_lcd_i2c_bus_handle_t)TOUCH_I2C_PORT, &touch_io_config, io_handle) != ESP_OK) {
ESP_LOGE(TAG, "touch I2C IO init failed");
return false;
}
@ -53,7 +52,9 @@ static bool create_touch_device(esp_lcd_panel_io_handle_t* io_handle, esp_lcd_to
.mirror_x = 0,
.mirror_y = 0,
},
.process_coordinates = NULL,
.interrupt_callback = NULL,
.user_data = NULL
};
if (esp_lcd_touch_new_i2c_cst816s(*io_handle, &config, touch_handle) != ESP_OK) {

View File

@ -0,0 +1,10 @@
#include "yellow_board.h"
DisplayDriver board_2432s024_create_display_driver();
TouchDriver board_2432s024_create_touch_driver();
const HardwareConfig yellow_board_24inch_cap = {
.bootstrap = NULL,
.display_driver = &board_2432s024_create_display_driver,
.touch_driver = &board_2432s024_create_touch_driver
};

View File

@ -0,0 +1,6 @@
#pragma once
#include "nanobake.h"
// Capacitive touch version of the 2.4" yellow board
extern const HardwareConfig yellow_board_24inch_cap;

View File

@ -1,4 +0,0 @@
#pragma once
#include "board_2432s024_display.h"
#include "board_2432s024_touch.h"

View File

@ -1,13 +0,0 @@
#pragma once
#include "display.h"
#ifdef __cplusplus
extern "C" {
#endif
extern DisplayDriver board_2432s024_create_display_driver();
#ifdef __cplusplus
}
#endif

View File

@ -1,13 +0,0 @@
#pragma once
#include "touch.h"
#ifdef __cplusplus
extern "C" {
#endif
extern TouchDriver board_2432s024_create_touch_driver();
#ifdef __cplusplus
}
#endif

View File

@ -63,11 +63,6 @@ typedef struct {
* Non-blocking method to create the GUI
*/
const AppOnShow _Nullable on_show;
/**
* Callstack size. If you get a stackoverflow, then consider increasing this value.
*/
const AppStackSize stack_size;
} AppManifest;
#ifdef __cplusplus

View File

@ -65,7 +65,7 @@ FURI_NORETURN void __furi_halt_implementation();
do { \
if (!(__e)) { \
ESP_LOGE("check", "%s", #__e); \
__furi_crash(__m); \
__furi_crash(#__m); \
} \
} while (0)
@ -83,7 +83,7 @@ FURI_NORETURN void __furi_halt_implementation();
do { \
if (!(__e)) { \
ESP_LOGE("assert", "%s", #__e); \
__furi_crash(__m); \
__furi_crash(#__m); \
} \
} while (0)
#else

View File

@ -116,7 +116,11 @@ uint32_t furi_event_flag_wait(
}
rflags = xEventGroupWaitBits(
hEventGroup, (EventBits_t)flags, exit_clr, wait_all, (TickType_t)timeout
hEventGroup,
(EventBits_t)flags,
exit_clr,
wait_all,
(TickType_t)timeout
);
if (options & FuriFlagWaitAll) {

View File

@ -1,12 +1,8 @@
/**
* @file kernel.h
* Furi Kernel primitives
*/
#pragma once
#include "furi_core_types.h"
#define configTICK_RATE_HZ_RAW 1000
#define configTICK_RATE_HZ_RAW CONFIG_FREERTOS_HZ
#ifdef __cplusplus
extern "C" {

View File

@ -13,13 +13,14 @@ LIST_DEF(FuriPubSubSubscriptionList, FuriPubSubSubscription, M_POD_OPLIST);
struct FuriPubSub {
FuriPubSubSubscriptionList_t items;
// TODO: replace recursive mutex with semaphore
FuriMutex* mutex;
};
FuriPubSub* furi_pubsub_alloc() {
FuriPubSub* pubsub = malloc(sizeof(FuriPubSub));
pubsub->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
pubsub->mutex = furi_mutex_alloc(FuriMutexTypeRecursive);
furi_assert(pubsub->mutex);
FuriPubSubSubscriptionList_init(pubsub->items);

View File

@ -4,6 +4,9 @@
#include "mutex.h"
#include "m-dict.h"
#include "m_cstr_dup.h"
#include "log.h"
#define TAG "record"
#define FURI_RECORD_FLAG_READY (0x1)
@ -37,7 +40,7 @@ static void furi_record_erase(const char* name, FuriRecordData* record_data) {
void furi_record_init() {
furi_record = malloc(sizeof(FuriRecord));
furi_record->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
furi_record->mutex = furi_mutex_alloc(FuriMutexTypeRecursive);
furi_check(furi_record->mutex);
FuriRecordDataDict_init(furi_record->records);
}
@ -111,6 +114,7 @@ bool furi_record_destroy(const char* name) {
}
void* furi_record_open(const char* name) {
furi_assert(name);
furi_assert(furi_record);
furi_record_lock();
@ -134,6 +138,7 @@ void* furi_record_open(const char* name) {
}
void furi_record_close(const char* name) {
furi_assert(name);
furi_assert(furi_record);
furi_record_lock();

View File

@ -51,7 +51,7 @@ struct FuriThread {
// Keep all non-alignable byte types in one place,
// this ensures that the size of this structure is minimal
bool is_service;
bool is_static;
bool heap_trace_enabled;
configSTACK_DEPTH_TYPE stack_size;
@ -113,7 +113,7 @@ static void furi_thread_body(void* context) {
furi_assert(thread->state == FuriThreadStateRunning);
if (thread->is_service) {
if (thread->is_static) {
ESP_LOGI(
TAG,
"%s service thread TCB memory will not be reclaimed",
@ -135,7 +135,7 @@ FuriThread* furi_thread_alloc() {
// TODO: create default struct instead of using memset()
memset(thread, 0, sizeof(FuriThread));
thread->output.buffer = furi_string_alloc();
thread->is_service = false;
thread->is_static = false;
FuriThread* parent = NULL;
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) {
@ -207,8 +207,8 @@ void furi_thread_set_appid(FuriThread* thread, const char* appid) {
thread->appid = appid ? strdup(appid) : NULL;
}
void furi_thread_mark_as_service(FuriThread* thread) {
thread->is_service = true;
void furi_thread_mark_as_static(FuriThread* thread) {
thread->is_static = true;
}
bool furi_thread_mark_is_service(FuriThreadId thread_id) {
@ -216,7 +216,7 @@ bool furi_thread_mark_is_service(FuriThreadId thread_id) {
assert(!FURI_IS_IRQ_MODE() && (hTask != NULL));
FuriThread* thread = (FuriThread*)pvTaskGetThreadLocalStoragePointer(hTask, 0);
assert(thread != NULL);
return thread->is_service;
return thread->is_static;
}
void furi_thread_set_stack_size(FuriThread* thread, size_t stack_size) {
@ -281,7 +281,7 @@ void furi_thread_start(FuriThread* thread) {
uint32_t stack = thread->stack_size / sizeof(StackType_t);
UBaseType_t priority = thread->priority ? thread->priority : FuriThreadPriorityNormal;
if (thread->is_service) {
if (thread->is_static) {
thread->task_handle = xTaskCreateStatic(
furi_thread_body,
thread->name,

View File

@ -109,7 +109,7 @@ void furi_thread_set_appid(FuriThread* thread, const char* appid);
*
* @param thread
*/
void furi_thread_mark_as_service(FuriThread* thread);
void furi_thread_mark_as_static(FuriThread* thread);
/** Set FuriThread stack size
*

View File

@ -1,7 +1,6 @@
#include "desktop.h"
#include "lvgl.h"
#include "check.h"
#include "record.h"
#include "apps/services/loader/loader.h"
#include "apps/services/gui/gui.h"
#include "apps/services/gui/view_port.h"
@ -11,9 +10,7 @@ static void on_open_app(lv_event_t* e) {
lv_event_code_t code = lv_event_get_code(e);
if (code == LV_EVENT_CLICKED) {
const AppManifest* manifest = lv_event_get_user_data(e);
FURI_RECORD_TRANSACTION(RECORD_LOADER, Loader*, loader, {
loader_start_app_nonblocking(loader, manifest->id, NULL);
})
loader_start_app_nonblocking(manifest->id, NULL);
}
}
@ -38,9 +35,7 @@ static void desktop_show(lv_obj_t* parent, void* context) {
static void desktop_start() {
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, &desktop_show, NULL);
FURI_RECORD_TRANSACTION(RECORD_GUI, Gui*, gui, {
gui_add_view_port(gui, view_port, GuiLayerDesktop);
})
gui_add_view_port(view_port, GuiLayerDesktop);
}
static void desktop_stop() {
@ -54,6 +49,5 @@ const AppManifest desktop_app = {
.type = AppTypeDesktop,
.on_start = &desktop_start,
.on_stop = &desktop_stop,
.on_show = NULL,
.stack_size = AppStackSizeNormal
.on_show = NULL
};

View File

@ -4,192 +4,36 @@
#include "gui_i.h"
#include "log.h"
#include "record.h"
#include "kernel.h"
#define TAG "gui"
// Forward declarations from gui_draw.c
// Forward declarations
bool gui_redraw_fs(Gui*);
void gui_redraw(Gui*);
static int32_t gui_main(void*);
ViewPort* gui_view_port_find_enabled(ViewPortArray_t array) {
// Iterating backward
ViewPortArray_it_t it;
ViewPortArray_it_last(it, array);
while (!ViewPortArray_end_p(it)) {
ViewPort* view_port = *ViewPortArray_ref(it);
if (view_port_is_enabled(view_port)) {
ViewPort* view_port = *ViewPortArray_ref(it);
return view_port;
}
ViewPortArray_previous(it);
}
return NULL;
}
size_t gui_active_view_port_count(Gui* gui, GuiLayer layer) {
furi_assert(gui);
furi_check(layer < GuiLayerMAX);
size_t ret = 0;
gui_lock(gui);
ViewPortArray_it_t it;
ViewPortArray_it_last(it, gui->layers[layer]);
while (!ViewPortArray_end_p(it)) {
ViewPort* view_port = *ViewPortArray_ref(it);
if (view_port_is_enabled(view_port)) {
ret++;
}
ViewPortArray_previous(it);
}
gui_unlock(gui);
return ret;
}
void gui_update(Gui* gui) {
furi_assert(gui);
FuriThreadId thread_id = furi_thread_get_id(gui->thread);
furi_thread_flags_set(thread_id, GUI_THREAD_FLAG_DRAW);
}
void gui_lock(Gui* gui) {
furi_assert(gui);
furi_check(furi_mutex_acquire(gui->mutex, FuriWaitForever) == FuriStatusOk);
}
void gui_unlock(Gui* gui) {
furi_assert(gui);
furi_check(furi_mutex_release(gui->mutex) == FuriStatusOk);
}
void gui_add_view_port(Gui* gui, ViewPort* view_port, GuiLayer layer) {
furi_assert(gui);
furi_assert(view_port);
furi_check(layer < GuiLayerMAX);
gui_lock(gui);
// Verify that view port is not yet added
ViewPortArray_it_t it;
for (size_t i = 0; i < GuiLayerMAX; i++) {
ViewPortArray_it(it, gui->layers[i]);
while (!ViewPortArray_end_p(it)) {
furi_assert(*ViewPortArray_ref(it) != view_port);
ViewPortArray_next(it);
}
}
// Add view port and link with gui
ViewPortArray_push_back(gui->layers[layer], view_port);
view_port_gui_set(view_port, gui);
gui_unlock(gui);
// Request redraw
gui_update(gui);
}
void gui_remove_view_port(Gui* gui, ViewPort* view_port) {
furi_assert(gui);
furi_assert(view_port);
gui_lock(gui);
view_port_gui_set(view_port, NULL);
ViewPortArray_it_t it;
for (size_t i = 0; i < GuiLayerMAX; i++) {
ViewPortArray_it(it, gui->layers[i]);
while (!ViewPortArray_end_p(it)) {
if (*ViewPortArray_ref(it) == view_port) {
ViewPortArray_remove(gui->layers[i], it);
} else {
ViewPortArray_next(it);
}
}
}
/*
if(gui->ongoing_input_view_port == view_port) {
gui->ongoing_input_view_port = NULL;
}
*/
gui_unlock(gui);
// Request redraw
gui_update(gui);
}
void gui_view_port_send_to_front(Gui* gui, ViewPort* view_port) {
furi_assert(gui);
furi_assert(view_port);
gui_lock(gui);
// Remove
GuiLayer layer = GuiLayerMAX;
ViewPortArray_it_t it;
for (size_t i = 0; i < GuiLayerMAX; i++) {
ViewPortArray_it(it, gui->layers[i]);
while (!ViewPortArray_end_p(it)) {
if (*ViewPortArray_ref(it) == view_port) {
ViewPortArray_remove(gui->layers[i], it);
furi_assert(layer == GuiLayerMAX);
layer = i;
} else {
ViewPortArray_next(it);
}
}
}
furi_assert(layer != GuiLayerMAX);
// Return to the top
ViewPortArray_push_back(gui->layers[layer], view_port);
gui_unlock(gui);
// Request redraw
gui_update(gui);
}
void gui_view_port_send_to_back(Gui* gui, ViewPort* view_port) {
furi_assert(gui);
furi_assert(view_port);
gui_lock(gui);
// Remove
GuiLayer layer = GuiLayerMAX;
ViewPortArray_it_t it;
for (size_t i = 0; i < GuiLayerMAX; i++) {
ViewPortArray_it(it, gui->layers[i]);
while (!ViewPortArray_end_p(it)) {
if (*ViewPortArray_ref(it) == view_port) {
ViewPortArray_remove(gui->layers[i], it);
furi_assert(layer == GuiLayerMAX);
layer = i;
} else {
ViewPortArray_next(it);
}
}
}
furi_assert(layer != GuiLayerMAX);
// Return to the top
ViewPortArray_push_at(gui->layers[layer], 0, view_port);
gui_unlock(gui);
// Request redraw
gui_update(gui);
}
static Gui* gui = NULL;
Gui* gui_alloc() {
Gui* gui = malloc(sizeof(Gui));
gui->thread = furi_thread_alloc_ex(
Gui* instance = malloc(sizeof(Gui));
memset(instance, 0, sizeof(Gui));
furi_check(instance != NULL);
instance->thread = furi_thread_alloc_ex(
"gui",
2048,
AppStackSizeLarge, // Last known minimum was 2800 for launching desktop
&gui_main,
NULL
);
gui->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
instance->mutex = xSemaphoreCreateRecursiveMutex();
furi_check(lvgl_port_lock(100));
gui->lvgl_parent = lv_scr_act();
instance->lvgl_parent = lv_scr_act();
lvgl_port_unlock();
for (size_t i = 0; i < GuiLayerMAX; i++) {
ViewPortArray_init(gui->layers[i]);
instance->layers[i] = NULL;
}
/*
@ -200,25 +44,72 @@ Gui* gui_alloc() {
furi_check(gui->input_events);
furi_pubsub_subscribe(gui->input_events, gui_input_events_callback, gui);
*/
return gui;
return instance;
}
void gui_free(Gui* gui) {
furi_thread_free(gui->thread);
void gui_free(Gui* instance) {
furi_assert(instance != NULL);
furi_thread_free(instance->thread);
furi_mutex_free(instance->mutex);
free(instance);
}
if (gui->mutex) {
furi_mutex_free(gui->mutex);
}
void gui_lock() {
furi_assert(gui);
furi_assert(gui->mutex);
furi_check(xSemaphoreTakeRecursive(gui->mutex, portMAX_DELAY) == pdPASS);
}
void gui_unlock() {
furi_assert(gui);
furi_assert(gui->mutex);
furi_check(xSemaphoreGiveRecursive(gui->mutex) == pdPASS);
}
void gui_request_draw() {
furi_assert(gui);
FuriThreadId thread_id = furi_thread_get_id(gui->thread);
furi_thread_flags_set(thread_id, GUI_THREAD_FLAG_DRAW);
}
void gui_add_view_port(ViewPort* view_port, GuiLayer layer) {
furi_assert(gui);
furi_assert(view_port);
furi_check(layer < GuiLayerMAX);
gui_lock();
furi_check(gui->layers[layer] == NULL, "layer in use");
gui->layers[layer] = view_port;
view_port_gui_set(view_port, gui);
gui_unlock();
gui_request_draw();
}
void gui_remove_view_port(ViewPort* view_port) {
furi_assert(gui);
furi_assert(view_port);
gui_lock();
view_port_gui_set(view_port, NULL);
for (size_t i = 0; i < GuiLayerMAX; i++) {
ViewPortArray_clear(gui->layers[i]);
if (gui->layers[i] == view_port) {
gui->layers[i] = NULL;
break;
}
}
free(gui);
gui_unlock();
gui_request_draw();
}
static int32_t gui_main(void* parameter) {
UNUSED(parameter);
static int32_t gui_main(void* p) {
UNUSED(p);
furi_check(gui);
Gui* local_gui = gui;
while (1) {
uint32_t flags = furi_thread_flags_wait(
@ -236,11 +127,10 @@ static int32_t gui_main(void* parameter) {
}*/
// Process and dispatch draw call
if (flags & GUI_THREAD_FLAG_DRAW) {
FURI_LOG_D(TAG, "redraw requested");
furi_thread_flags_clear(GUI_THREAD_FLAG_DRAW);
FURI_RECORD_TRANSACTION(RECORD_GUI, Gui*, gui, {
gui_redraw(gui);
})
gui_lock();
gui_redraw(local_gui);
gui_unlock();
}
if (flags & GUI_THREAD_FLAG_EXIT) {
@ -252,29 +142,27 @@ static int32_t gui_main(void* parameter) {
return 0;
}
// region AppManifest
static void gui_start(void* parameter) {
UNUSED(parameter);
Gui* gui = gui_alloc();
furi_record_create(RECORD_GUI, gui);
furi_thread_set_priority(gui->thread, FuriThreadPriorityHigh);
gui = gui_alloc();
furi_thread_set_priority(gui->thread, FuriThreadPriorityNormal);
furi_thread_start(gui->thread);
}
static void gui_stop() {
FURI_RECORD_TRANSACTION(RECORD_GUI, Gui*, gui, {
gui_lock(gui);
gui_lock();
FuriThreadId thread_id = furi_thread_get_id(gui->thread);
furi_thread_flags_set(thread_id, GUI_THREAD_FLAG_EXIT);
furi_thread_join(gui->thread);
FuriThreadId thread_id = furi_thread_get_id(gui->thread);
furi_thread_flags_set(thread_id, GUI_THREAD_FLAG_EXIT);
furi_thread_join(gui->thread);
gui_unlock(gui);
gui_unlock();
gui_free(gui);
})
furi_record_destroy(RECORD_GUI);
gui_free(gui);
}
const AppManifest gui_app = {
@ -284,6 +172,7 @@ const AppManifest gui_app = {
.type = AppTypeService,
.on_start = &gui_start,
.on_stop = &gui_stop,
.on_show = NULL,
.stack_size = AppStackSizeNormal
.on_show = NULL
};
// endregion

View File

@ -12,26 +12,11 @@ extern const AppManifest gui_app;
/** Gui layers */
typedef enum {
GuiLayerDesktop, /**< Desktop layer for internal use. Like fullscreen but with status bar */
GuiLayerWindow, /**< Window layer, status bar is shown */
GuiLayerStatusBarLeft, /**< Status bar left-side layer, auto-layout */
GuiLayerStatusBarRight, /**< Status bar right-side layer, auto-layout */
GuiLayerFullscreen, /**< Fullscreen layer, no status bar */
GuiLayerMAX /**< Don't use or move, special value */
} GuiLayer;
/** Gui Canvas Commit Callback */
typedef void (*GuiCanvasCommitCallback)(
uint8_t* data,
size_t size,
void* context
);
#define RECORD_GUI "gui"
typedef struct Gui Gui;
/** Add view_port to view_port tree
@ -42,7 +27,7 @@ typedef struct Gui Gui;
* @param view_port ViewPort instance
* @param[in] layer GuiLayer where to place view_port
*/
void gui_add_view_port(Gui* gui, ViewPort* view_port, GuiLayer layer);
void gui_add_view_port(ViewPort* view_port, GuiLayer layer);
/** Remove view_port from rendering tree
*
@ -51,25 +36,7 @@ void gui_add_view_port(Gui* gui, ViewPort* view_port, GuiLayer layer);
* @param gui Gui instance
* @param view_port ViewPort instance
*/
void gui_remove_view_port(Gui* gui, ViewPort* view_port);
/** Send ViewPort to the front
*
* Places selected ViewPort to the top of the drawing stack
*
* @param gui Gui instance
* @param view_port ViewPort instance
*/
void gui_view_port_send_to_front(Gui* gui, ViewPort* view_port);
/** Send ViewPort to the back
*
* Places selected ViewPort to the bottom of the drawing stack
*
* @param gui Gui instance
* @param view_port ViewPort instance
*/
void gui_view_port_send_to_back(Gui* gui, ViewPort* view_port);
void gui_remove_view_port(ViewPort* view_port);
#ifdef __cplusplus
}

View File

@ -39,12 +39,10 @@ static lv_obj_t* screen_with_top_bar_and_toolbar(lv_obj_t* parent) {
top_bar(vertical_container);
FURI_RECORD_TRANSACTION(RECORD_LOADER, Loader*, loader, {
const AppManifest* manifest = loader_get_current_app(loader);
if (manifest != NULL) {
toolbar(vertical_container, TOP_BAR_HEIGHT, manifest);
}
})
const AppManifest* manifest = loader_get_current_app();
if (manifest != NULL) {
toolbar(vertical_container, TOP_BAR_HEIGHT, manifest);
}
lv_obj_t* spacer = lv_obj_create(vertical_container);
lv_obj_set_size(spacer, 2, 2);
@ -60,7 +58,7 @@ static lv_obj_t* screen_with_top_bar_and_toolbar(lv_obj_t* parent) {
}
static bool gui_redraw_window(Gui* gui) {
ViewPort* view_port = gui_view_port_find_enabled(gui->layers[GuiLayerWindow]);
ViewPort* view_port = gui->layers[GuiLayerWindow];
if (view_port) {
lv_obj_t* container = screen_with_top_bar_and_toolbar(gui->lvgl_parent);
view_port_draw(view_port, container);
@ -71,7 +69,7 @@ static bool gui_redraw_window(Gui* gui) {
}
static bool gui_redraw_desktop(Gui* gui) {
ViewPort* view_port = gui_view_port_find_enabled(gui->layers[GuiLayerDesktop]);
ViewPort* view_port = gui->layers[GuiLayerDesktop];
if (view_port) {
lv_obj_t* container = screen_with_top_bar(gui->lvgl_parent);
view_port_draw(view_port, container);
@ -84,7 +82,7 @@ static bool gui_redraw_desktop(Gui* gui) {
}
bool gui_redraw_fs(Gui* gui) {
ViewPort* view_port = gui_view_port_find_enabled(gui->layers[GuiLayerFullscreen]);
ViewPort* view_port = gui->layers[GuiLayerFullscreen];
if (view_port) {
view_port_draw(view_port, gui->lvgl_parent);
return true;
@ -95,7 +93,6 @@ bool gui_redraw_fs(Gui* gui) {
void gui_redraw(Gui* gui) {
furi_assert(gui);
gui_lock(gui);
furi_check(lvgl_port_lock(100));
lv_obj_clean(gui->lvgl_parent);
@ -107,6 +104,4 @@ void gui_redraw(Gui* gui) {
}
lvgl_port_unlock();
gui_unlock(gui);
}

View File

@ -1,57 +1,28 @@
#pragma once
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "gui.h"
#include <m-algo.h>
#include <m-array.h>
#include <stdio.h>
#include "message_queue.h"
#include "mutex.h"
#include "pubsub.h"
#include "view_port.h"
#include "view_port_i.h"
#define GUI_DISPLAY_WIDTH 128
#define GUI_DISPLAY_HEIGHT 64
#define GUI_STATUS_BAR_X 0
#define GUI_STATUS_BAR_Y 0
#define GUI_STATUS_BAR_WIDTH GUI_DISPLAY_WIDTH
/* 0-1 pixels for upper thin frame
* 2-9 pixels for icons (battery, sd card, etc)
* 10-12 pixels for lower bold line */
#define GUI_STATUS_BAR_HEIGHT 13
/* icon itself area (battery, sd card, etc) excluding frame.
* painted 2 pixels below GUI_STATUS_BAR_X.
*/
#define GUI_STATUS_BAR_WORKAREA_HEIGHT 8
#define GUI_WINDOW_X 0
#define GUI_WINDOW_Y GUI_STATUS_BAR_HEIGHT
#define GUI_WINDOW_WIDTH GUI_DISPLAY_WIDTH
#define GUI_WINDOW_HEIGHT (GUI_DISPLAY_HEIGHT - GUI_WINDOW_Y)
#include <stdio.h>
#define GUI_THREAD_FLAG_DRAW (1 << 0)
#define GUI_THREAD_FLAG_INPUT (1 << 1)
#define GUI_THREAD_FLAG_EXIT (1 << 2)
#define GUI_THREAD_FLAG_ALL (GUI_THREAD_FLAG_DRAW | GUI_THREAD_FLAG_INPUT | GUI_THREAD_FLAG_EXIT)
ARRAY_DEF(ViewPortArray, ViewPort*, M_PTR_OPLIST);
typedef struct {
GuiCanvasCommitCallback callback;
void* context;
} CanvasCallbackPair;
/** Gui structure */
struct Gui {
// Thread and lock
FuriThread* thread;
FuriMutex* mutex;
SemaphoreHandle_t mutex;
// Layers and Canvas
ViewPortArray_t layers[GuiLayerMAX];
ViewPort* layers[GuiLayerMAX];
lv_obj_t* lvgl_parent;
// Input
@ -63,19 +34,11 @@ struct Gui {
*/
};
/** Find enabled ViewPort in ViewPortArray
*
* @param[in] array The ViewPortArray instance
*
* @return ViewPort instance or NULL
*/
ViewPort* gui_view_port_find_enabled(ViewPortArray_t array);
/** Update GUI, request redraw
*
* @param gui Gui instance
*/
void gui_update(Gui* gui);
void gui_request_draw();
///** Input event callback
// *
@ -86,21 +49,14 @@ void gui_update(Gui* gui);
// */
//void gui_input_events_callback(const void* value, void* ctx);
/** Get count of view ports in layer
*
* @param gui The Gui instance
* @param[in] layer GuiLayer that we want to get count of view ports
*/
size_t gui_active_view_port_count(Gui* gui, GuiLayer layer);
/** Lock GUI
*
* @param gui The Gui instance
*/
void gui_lock(Gui* gui);
void gui_lock();
/** Unlock GUI
*
* @param gui The Gui instance
*/
void gui_unlock(Gui* gui);
void gui_unlock();

View File

@ -36,7 +36,7 @@ void view_port_enabled_set(ViewPort* view_port, bool enabled) {
furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk);
if (view_port->is_enabled != enabled) {
view_port->is_enabled = enabled;
if (view_port->gui) gui_update(view_port->gui);
if (view_port->gui) gui_request_draw();
}
furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk);
}
@ -66,7 +66,7 @@ void view_port_update(ViewPort* view_port) {
ESP_LOGW(TAG, "ViewPort lockup: see %s:%d", __FILE__, __LINE__ - 3);
}
if (view_port->gui && view_port->is_enabled) gui_update(view_port->gui);
if (view_port->gui && view_port->is_enabled) gui_request_draw();
furi_mutex_release(view_port->mutex);
}

View File

@ -4,9 +4,7 @@
#include "apps/services/loader/loader.h"
static void app_toolbar_close(lv_event_t* event) {
FURI_RECORD_TRANSACTION(RECORD_LOADER, Loader*, loader, {
loader_stop_app(loader);
})
loader_stop_app();
}
void toolbar(lv_obj_t* parent, lv_coord_t offset_y, const AppManifest* manifest) {

View File

@ -6,18 +6,55 @@
#include <sys/cdefs.h>
#include "esp_heap_caps.h"
#include "apps/services/gui/gui.h"
#include "freertos/semphr.h"
#define TAG "Loader"
// Forward declarations
static int32_t loader_main(void* p);
static LoaderStatus loader_do_start_by_id(
Loader* loader,
const char* id,
const char* args,
FuriString* error_message
);
LoaderStatus loader_start_app(Loader* loader, const char* id, const char* args, FuriString* error_message) {
static Loader* loader = NULL;
static Loader* loader_alloc() {
furi_check(loader == NULL);
loader = malloc(sizeof(Loader));
loader->pubsub = furi_pubsub_alloc();
loader->queue = furi_message_queue_alloc(1, sizeof(LoaderMessage));
loader->thread = furi_thread_alloc_ex(
"loader",
AppStackSizeLarge, // Last known minimum was 2400 for starting Hello World app
&loader_main,
NULL
);
loader->app_data.args = NULL;
loader->app_data.app = NULL;
loader->app_data.view_port = NULL;
loader->mutex = xSemaphoreCreateRecursiveMutex();
return loader;
}
static void loader_free() {
furi_check(loader != NULL);
furi_thread_free(loader->thread);
furi_pubsub_free(loader->pubsub);
furi_message_queue_free(loader->queue);
furi_mutex_free(loader->mutex);
free(loader);
}
void loader_lock() {
furi_assert(loader);
furi_assert(loader->mutex);
furi_check(xSemaphoreTakeRecursive(loader->mutex, portMAX_DELAY) == pdPASS);
}
void loader_unlock() {
furi_assert(loader);
furi_assert(loader->mutex);
furi_check(xSemaphoreGiveRecursive(loader->mutex) == pdPASS);
}
LoaderStatus loader_start_app(const char* id, const char* args, FuriString* error_message) {
LoaderMessage message;
LoaderMessageLoaderStatusResult result;
@ -32,7 +69,7 @@ LoaderStatus loader_start_app(Loader* loader, const char* id, const char* args,
return result.value;
}
void loader_start_app_nonblocking(Loader* loader, const char* id, const char* args) {
void loader_start_app_nonblocking(const char* id, const char* args) {
LoaderMessage message;
LoaderMessageLoaderStatusResult result;
@ -45,22 +82,22 @@ void loader_start_app_nonblocking(Loader* loader, const char* id, const char* ar
furi_message_queue_put(loader->queue, &message, FuriWaitForever);
}
void loader_stop_app(Loader* loader) {
void loader_stop_app() {
LoaderMessage message;
message.type = LoaderMessageTypeAppStop;
furi_message_queue_put(loader->queue, &message, FuriWaitForever);
}
const AppManifest* _Nullable loader_get_current_app(Loader* loader) {
App* app = loader->app_data.app;
if (app != NULL) {
return app->manifest;
} else {
return NULL;
}
const AppManifest* _Nullable loader_get_current_app() {
loader_lock();
const App* app = loader->app_data.app;
const AppManifest* manifest = app ? app->manifest : NULL;
loader_unlock();
return manifest;
}
FuriPubSub* loader_get_pubsub(Loader* loader) {
FuriPubSub* loader_get_pubsub() {
furi_assert(loader);
// it's safe to return pubsub without locking
// because it's never freed and loader is never exited
@ -68,30 +105,6 @@ FuriPubSub* loader_get_pubsub(Loader* loader) {
return loader->pubsub;
}
// implementation
static Loader* loader_alloc() {
Loader* loader = malloc(sizeof(Loader));
loader->pubsub = furi_pubsub_alloc();
loader->queue = furi_message_queue_alloc(1, sizeof(LoaderMessage));
loader->thread = furi_thread_alloc_ex(
"loader",
2048,
&loader_main,
NULL
);
loader->app_data.args = NULL;
loader->app_data.app = NULL;
return loader;
}
static void loader_free(Loader* loader) {
furi_pubsub_free(loader->pubsub);
furi_message_queue_free(loader->queue);
furi_thread_free(loader->thread);
free(loader);
}
static void loader_log_status_error(
LoaderStatus status,
FuriString* error_message,
@ -128,15 +141,19 @@ static LoaderStatus loader_make_success_status(FuriString* error_message) {
}
static void loader_start_app_with_manifest(
Loader* loader,
const AppManifest* _Nonnull manifest,
const char* args
) {
FURI_LOG_I(TAG, "start with manifest %s", manifest->id);
if (manifest->type != AppTypeUser && manifest->type != AppTypeSystem) {
furi_crash("app type not supported by loader");
furi_crash("App type not supported by loader");
}
App* _Nonnull app = furi_app_alloc(manifest);
loader_lock();
loader->app_data.app = app;
loader->app_data.args = (void*)args;
@ -149,23 +166,20 @@ static void loader_start_app_with_manifest(
loader->app_data.view_port = view_port;
view_port_draw_callback_set(view_port, manifest->on_show, NULL);
FURI_RECORD_TRANSACTION(RECORD_GUI, Gui*, gui, {
gui_add_view_port(gui, view_port, GuiLayerWindow);
})
gui_add_view_port(view_port, GuiLayerWindow);
} else {
loader->app_data.view_port = NULL;
}
loader_unlock();
}
// process messages
static LoaderStatus loader_do_start_by_id(
Loader* loader,
const char* id,
const char* args,
FuriString* _Nullable error_message
) {
FURI_LOG_I(TAG, "loader start by id %s", id);
FURI_LOG_I(TAG, "Start by id %s", id);
const AppManifest* manifest = app_manifest_registry_find_by_id(id);
if (manifest == NULL) {
@ -177,11 +191,13 @@ static LoaderStatus loader_do_start_by_id(
);
}
loader_start_app_with_manifest(loader, manifest, args);
loader_start_app_with_manifest(manifest, args);
return loader_make_success_status(error_message);
}
static void loader_do_stop_app(Loader* loader) {
static void loader_do_stop_app() {
loader_lock();
App* app = loader->app_data.app;
if (app == NULL) {
FURI_LOG_W(TAG, "Stop app: no app running");
@ -192,9 +208,7 @@ static void loader_do_stop_app(Loader* loader) {
ViewPort* view_port = loader->app_data.view_port;
if (view_port) {
FURI_RECORD_TRANSACTION(RECORD_GUI, Gui*, gui, {
gui_remove_view_port(gui, view_port);
})
gui_remove_view_port(view_port);
view_port_free(view_port);
loader->app_data.view_port = NULL;
}
@ -211,6 +225,8 @@ static void loader_do_stop_app(Loader* loader) {
furi_app_free(loader->app_data.app);
loader->app_data.app = NULL;
loader_unlock();
FURI_LOG_I(
TAG,
"Application stopped. Free heap: %zu",
@ -222,43 +238,38 @@ static void loader_do_stop_app(Loader* loader) {
furi_pubsub_publish(loader->pubsub, &event);
}
// app
bool loader_is_app_running() {
loader_lock();
bool is_running = loader->app_data.app != NULL;
loader_unlock();
return is_running;
}
static int32_t loader_main(void* p) {
UNUSED(p);
FuriMessageQueue* queue = NULL;
FURI_RECORD_TRANSACTION(RECORD_LOADER, Loader*, loader, {
queue = loader->queue;
})
furi_check(queue != NULL);
LoaderMessage message;
bool exit_requested = false;
while (!exit_requested) {
if (furi_message_queue_get(queue, &message, FuriWaitForever) == FuriStatusOk) {
FURI_LOG_I(TAG, "processing message of type %d", message.type);
furi_check(loader != NULL);
if (furi_message_queue_get(loader->queue, &message, FuriWaitForever) == FuriStatusOk) {
FURI_LOG_I(TAG, "Processing message of type %d", message.type);
switch (message.type) {
case LoaderMessageTypeStartByName:
FURI_RECORD_TRANSACTION(RECORD_LOADER, Loader*, loader, {
if (loader->app_data.app) {
loader_do_stop_app(loader);
}
message.status_value->value = loader_do_start_by_id(
loader,
message.start.id,
message.start.args,
message.start.error_message
);
if (message.api_lock) {
api_lock_unlock(message.api_lock);
}
})
if (loader_is_app_running()) {
loader_do_stop_app();
}
message.status_value->value = loader_do_start_by_id(
message.start.id,
message.start.args,
message.start.error_message
);
if (message.api_lock) {
api_lock_unlock(message.api_lock);
}
break;
case LoaderMessageTypeAppStop:
FURI_RECORD_TRANSACTION(RECORD_LOADER, Loader*, loader, {
loader_do_stop_app(loader);
})
loader_do_stop_app();
break;
case LoaderMessageTypeExit:
exit_requested = true;
@ -270,30 +281,30 @@ static int32_t loader_main(void* p) {
return 0;
}
static void loader_start(void* p) {
Loader* loader = loader_alloc();
furi_record_create(RECORD_LOADER, loader);
furi_thread_set_priority(loader->thread, FuriThreadPriorityHigh);
// region AppManifest
static void loader_start(void* parameter) {
UNUSED(parameter);
furi_check(loader == NULL);
loader = loader_alloc();
furi_thread_set_priority(loader->thread, FuriThreadPriorityNormal);
furi_thread_start(loader->thread);
}
static void loader_stop() {
furi_check(loader != NULL);
LoaderMessage message = {
.api_lock = NULL,
.type = LoaderMessageTypeExit
};
FURI_RECORD_TRANSACTION(RECORD_LOADER, Loader*, loader, {
furi_message_queue_put(loader->queue, &message, FuriWaitForever);
// Send stop signal to thread and wait for thread to finish
furi_message_queue_put(loader->queue, &message, FuriWaitForever);
furi_thread_join(loader->thread);
furi_thread_join(loader->thread);
furi_thread_free(loader->thread);
loader->thread = NULL;
loader_free(loader);
})
furi_record_destroy(RECORD_LOADER);
loader_free();
loader = NULL;
}
const AppManifest loader_app = {
@ -303,6 +314,7 @@ const AppManifest loader_app = {
.type = AppTypeService,
.on_start = &loader_start,
.on_stop = &loader_stop,
.on_show = NULL,
.stack_size = AppStackSizeNormal
.on_show = NULL
};
// endregion

View File

@ -8,8 +8,6 @@
extern "C" {
#endif
#define RECORD_LOADER "loader"
typedef struct Loader Loader;
typedef enum {
@ -36,7 +34,7 @@ typedef struct {
* @param[out] error_message detailed error message, can be NULL
* @return LoaderStatus
*/
LoaderStatus loader_start_app(Loader* loader, const char* id, const char* args, FuriString* error_message);
LoaderStatus loader_start_app(const char* id, const char* args, FuriString* error_message);
/**
* @brief Close any running app, then start new one. Non-blocking.
@ -44,11 +42,13 @@ LoaderStatus loader_start_app(Loader* loader, const char* id, const char* args,
* @param[in] id application name or id
* @param[in] args application arguments
*/
void loader_start_app_nonblocking(Loader* loader, const char* id, const char* args);
void loader_start_app_nonblocking(const char* id, const char* args);
void loader_stop_app(Loader* loader);
void loader_stop_app();
const AppManifest* _Nullable loader_get_current_app(Loader* loader);
bool loader_is_app_running();
const AppManifest* _Nullable loader_get_current_app();
/**
* @brief Start application with GUI error message
* @param[in] instance loader instance
@ -56,20 +56,20 @@ const AppManifest* _Nullable loader_get_current_app(Loader* loader);
* @param[in] args application arguments
* @return LoaderStatus
*/
//LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const char* args);
//LoaderStatus loader_start_with_gui_error(const char* name, const char* args);
/**
* @brief Show loader menu
* @param[in] instance loader instance
*/
void loader_show_menu(Loader* instance);
void loader_show_menu();
/**
* @brief Get loader pubsub
* @param[in] instance loader instance
* @return FuriPubSub*
*/
FuriPubSub* loader_get_pubsub(Loader* instance);
FuriPubSub* loader_get_pubsub();
#ifdef __cplusplus
}

View File

@ -1,11 +1,13 @@
#pragma once
#include "api_lock.h"
#include "app_manifest.h"
#include "apps/services/gui/view_port.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "loader.h"
#include "message_queue.h"
#include "pubsub.h"
#include "thread.h"
#include "apps/services/gui/view_port.h"
typedef struct {
char* args;
@ -18,6 +20,7 @@ struct Loader {
FuriPubSub* pubsub;
FuriMessageQueue* queue;
LoaderAppData app_data;
SemaphoreHandle_t mutex;
};
typedef enum {

View File

@ -38,6 +38,5 @@ AppManifest system_info_app = {
.type = AppTypeSystem,
.on_start = NULL,
.on_stop = NULL,
.on_show = app_show,
.stack_size = AppStackSizeNormal
.on_show = app_show
};

View File

@ -5,7 +5,12 @@
#define TAG "hardware"
Devices nb_devices_create(Config _Nonnull* config) {
Hardware nb_hardware_init(const HardwareConfig _Nonnull* config) {
if (config->bootstrap != NULL) {
ESP_LOGI(TAG, "Bootstrapping");
config->bootstrap();
}
furi_check(config->display_driver != NULL, "no display driver configured");
DisplayDriver display_driver = config->display_driver();
ESP_LOGI(TAG, "display with driver %s", display_driver.name);
@ -21,7 +26,7 @@ Devices nb_devices_create(Config _Nonnull* config) {
touch = NULL;
}
return (Devices) {
return (Hardware) {
.display = display,
.touch = touch
};

View File

@ -10,7 +10,7 @@ extern "C" {
typedef struct {
DisplayDevice* _Nonnull display;
TouchDevice* _Nullable touch;
} Devices;
} Hardware;
#ifdef __cplusplus
}

View File

@ -6,7 +6,7 @@
extern "C" {
#endif
Devices nb_devices_create(Config _Nonnull* config);
Hardware nb_hardware_init(const HardwareConfig _Nonnull* config);
#ifdef __cplusplus
}

View File

@ -13,6 +13,7 @@ typedef struct {
uint16_t vertical_resolution;
uint16_t draw_buffer_height;
uint16_t bits_per_pixel;
bool double_buffering;
bool mirror_x;
bool mirror_y;
bool swap_xy;

View File

@ -5,7 +5,7 @@
#define TAG "lvgl"
Lvgl nb_graphics_init(Devices _Nonnull* hardware) {
Lvgl nb_graphics_init(Hardware _Nonnull* hardware) {
const lvgl_port_cfg_t lvgl_cfg = {
.task_priority = 4,
.task_stack = 4096,
@ -23,7 +23,7 @@ Lvgl nb_graphics_init(Devices _Nonnull* hardware) {
.io_handle = display->io_handle,
.panel_handle = display->display_handle,
.buffer_size = display->horizontal_resolution * display->draw_buffer_height * (display->bits_per_pixel / 8),
.double_buffer = 0,
.double_buffer = display->double_buffering,
.hres = display->horizontal_resolution,
.vres = display->vertical_resolution,
.monochrome = display->monochrome,

View File

@ -7,7 +7,7 @@
extern "C" {
#endif
Lvgl nb_graphics_init(Devices _Nonnull* hardware);
Lvgl nb_graphics_init(Hardware _Nonnull* hardware);
#ifdef __cplusplus
}

View File

@ -4,9 +4,11 @@
#include "furi.h"
#include "graphics_i.h"
#include "partitions.h"
#include "apps/services/gui/gui.h"
#define TAG "nanobake"
Gui* gui_alloc();
// System services
extern const AppManifest gui_app;
extern const AppManifest loader_app;
@ -50,12 +52,12 @@ static void start_desktop() {
FURI_LOG_I(TAG, "Startup complete");
}
__attribute__((unused)) extern void nanobake_start(Config* _Nonnull config) {
__attribute__((unused)) extern void nanobake_start(const Config* _Nonnull config) {
furi_init();
nb_partitions_init();
Devices hardware = nb_devices_create(config);
Hardware hardware = nb_hardware_init(config->hardware);
/*NbLvgl lvgl =*/nb_graphics_init(&hardware);
register_apps(config);

View File

@ -10,20 +10,27 @@ extern "C" {
// Forward declarations
typedef void* FuriThreadId;
typedef void (*Bootstrap)();
typedef TouchDriver (*CreateTouchDriver)();
typedef DisplayDriver (*CreateDisplayDriver)();
typedef struct {
// Optional bootstrapping method
const Bootstrap _Nullable bootstrap;
// Required driver for display
const CreateDisplayDriver _Nonnull display_driver;
// Optional driver for touch input
const CreateTouchDriver _Nullable touch_driver;
} HardwareConfig;
typedef struct {
const HardwareConfig* hardware;
// List of user applications
const size_t apps_count;
const AppManifest* const apps[];
} Config;
__attribute__((unused)) extern void nanobake_start(Config _Nonnull* config);
__attribute__((unused)) extern void nanobake_start(const Config _Nonnull* config);
FuriThreadId nanobake_get_app_thread_id(size_t index);
size_t nanobake_get_app_thread_count();

View File

@ -1,5 +1,17 @@
# Yellow Board only runs on ESP32
set(project_components nanobake)
if("${IDF_TARGET}" STREQUAL "esp32")
list(APPEND project_components yellow_board)
endif()
# T-Deck is an S3 platform
if("${IDF_TARGET}" STREQUAL "esp32s3")
list(APPEND project_components lilygo_tdeck)
endif()
idf_component_register(
SRC_DIRS "src"
"src/hello_world"
REQUIRES nanobake board_2432s024
REQUIRES ${project_components}
)

14
main/Kconfig Normal file
View File

@ -0,0 +1,14 @@
# Kconfig file for NanoBake example app
menu "NanoBake App"
choice
prompt "Board"
default NB_BOARD_CUSTOM
config NB_BOARD_CUSTOM
bool "Custom"
config NB_BOARD_YELLOW_BOARD_24_CAP
bool "Yellow Board (2.4\" capacitive)"
config NB_BOARD_LILYGO_TDECK
bool "LilyGo T-Deck"
endchoice
endmenu

View File

@ -1,6 +1,7 @@
dependencies:
espressif/esp_lcd_ili9341: "^2.0.0"
espressif/esp_lcd_touch_cst816s: "^1.0.3"
espressif/esp_lcd_touch_gt911: "^1.0.0"
espressif/esp_lcd_touch: "1.1.1"
esp_lvgl_port: '1.4.0'
idf: '>=5.1'

14
main/src/board_config.h Normal file
View File

@ -0,0 +1,14 @@
#pragma once
// Supported hardware:
#if defined(CONFIG_NB_BOARD_LILYGO_TDECK)
#include "lilygo_tdeck.h"
#define NB_BOARD_HARDWARE &lilygo_tdeck
#elif defined(CONFIG_NB_BOARD_YELLOW_BOARD_24_CAP)
#include "yellow_board.h"
#define NB_BOARD_HARDWARE &yellow_board_24inch_cap
#elif defined(CONFIG_NB_BOARD_CUSTOM)
#define NB_BOARD_HARDWARE furi_crash( \
"Replace NB_BOARD_HARDWARE in main.c with your own, or use \"idf.py menuconfig\" to select a supported board." \
)
#endif

View File

@ -1,5 +1,4 @@
#include "hello_world.h"
#include "furi.h"
#include "apps/services/gui/gui.h"
#include "apps/services/loader/loader.h"
@ -21,6 +20,5 @@ const AppManifest hello_world_app = {
.type = AppTypeUser,
.on_start = NULL,
.on_stop = NULL,
.on_show = &app_show,
.stack_size = AppStackSizeNormal,
.on_show = &app_show
};

View File

@ -1,17 +1,12 @@
#include "nanobake.h"
#include "record.h"
#include "apps/services/loader/loader.h"
// Hardware
#include "board_2432s024.h"
#include "board_config.h"
// Apps
#include "hello_world/hello_world.h"
__attribute__((unused)) void app_main(void) {
static Config config = {
.display_driver = &board_2432s024_create_display_driver,
.touch_driver = &board_2432s024_create_touch_driver,
static const Config config = {
.hardware = NB_BOARD_HARDWARE,
.apps = {
&hello_world_app
},
@ -19,12 +14,4 @@ __attribute__((unused)) void app_main(void) {
};
nanobake_start(&config);
// FURI_RECORD_TRANSACTION(RECORD_LOADER, Loader*, loader, {
// FuriString* error_message = furi_string_alloc();
// if (loader_start_app(loader, hello_world_app.id, NULL, error_message) != LoaderStatusOk) {
// FURI_LOG_E(hello_world_app.id, "%s\r\n", furi_string_get_cstr(error_message));
// }
// furi_string_free(error_message);
// });
}

View File

@ -0,0 +1,23 @@
# Software defaults
CONFIG_LV_FONT_MONTSERRAT_14=y
CONFIG_LV_FONT_MONTSERRAT_18=y
CONFIG_LV_USE_USER_DATA=y
CONFIG_LV_USE_FS_STDIO=y
CONFIG_LV_FS_STDIO_LETTER=65
CONFIG_LV_FS_STDIO_PATH=""
CONFIG_LV_FS_STDIO_CACHE_SIZE=4096
CONFIG_LV_USE_PNG=y
CONFIG_FREERTOS_HZ=1000
CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=2
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
# Hardware defaults
CONFIG_NB_BOARD_LILYGO_TDECK=y
CONFIG_IDF_TARGET="esp32s3"
CONFIG_LV_COLOR_16_SWAP=y
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y
CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
CONFIG_FLASHMODE_QIO=y

View File

@ -0,0 +1,23 @@
# Software defaults
CONFIG_LV_FONT_MONTSERRAT_14=y
CONFIG_LV_FONT_MONTSERRAT_18=y
CONFIG_LV_USE_USER_DATA=y
CONFIG_LV_USE_FS_STDIO=y
CONFIG_LV_FS_STDIO_LETTER=65
CONFIG_LV_FS_STDIO_PATH=""
CONFIG_LV_FS_STDIO_CACHE_SIZE=4096
CONFIG_LV_USE_PNG=y
CONFIG_FREERTOS_HZ=1000
CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=2
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
# Hardware defaults
CONFIG_NB_BOARD_YELLOW_BOARD_24_CAP=y
CONFIG_IDF_TARGET="esp32"
CONFIG_LV_COLOR_16_SWAP=y
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_FLASHMODE_QIO=y

View File

@ -1,5 +1,4 @@
CONFIG_IDF_TARGET="esp32"
CONFIG_LV_COLOR_16_SWAP=y
# Software defaults
CONFIG_LV_FONT_MONTSERRAT_14=y
CONFIG_LV_FONT_MONTSERRAT_18=y
CONFIG_LV_USE_USER_DATA=y
@ -8,8 +7,13 @@ CONFIG_LV_FS_STDIO_LETTER=65
CONFIG_LV_FS_STDIO_PATH=""
CONFIG_LV_FS_STDIO_CACHE_SIZE=4096
CONFIG_LV_USE_PNG=y
CONFIG_FREERTOS_HZ=1000
CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=2
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
# Work-around for Furi issue
CONFIG_FREERTOS_UNICORE=y
# Hardware defaults
CONFIG_NB_BOARD_CUSTOM=y