diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 37e4c10f..3400443e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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: './' diff --git a/CMakeLists.txt b/CMakeLists.txt index 464588f9..cdc773f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/README.md b/README.md index b6d5020c..0878b5cd 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/boards/lilygo_tdeck/CMakeLists.txt b/boards/lilygo_tdeck/CMakeLists.txt new file mode 100644 index 00000000..70ce0831 --- /dev/null +++ b/boards/lilygo_tdeck/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRC_DIRS "." + INCLUDE_DIRS "." + REQUIRES nanobake esp_lcd esp_lcd_touch_gt911 +) diff --git a/boards/lilygo_tdeck/bootstrap.c b/boards/lilygo_tdeck/bootstrap.c new file mode 100644 index 00000000..fc4964ba --- /dev/null +++ b/boards/lilygo_tdeck/bootstrap.c @@ -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); +} diff --git a/boards/lilygo_tdeck/display.c b/boards/lilygo_tdeck/display.c new file mode 100644 index 00000000..46db1ea6 --- /dev/null +++ b/boards/lilygo_tdeck/display.c @@ -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 + }; +} diff --git a/boards/lilygo_tdeck/lilygo_tdeck.c b/boards/lilygo_tdeck/lilygo_tdeck.c new file mode 100644 index 00000000..6a66dd14 --- /dev/null +++ b/boards/lilygo_tdeck/lilygo_tdeck.c @@ -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 +}; diff --git a/boards/lilygo_tdeck/lilygo_tdeck.h b/boards/lilygo_tdeck/lilygo_tdeck.h new file mode 100644 index 00000000..778f710c --- /dev/null +++ b/boards/lilygo_tdeck/lilygo_tdeck.h @@ -0,0 +1,5 @@ +#pragma once + +#include "nanobake.h" + +extern const HardwareConfig lilygo_tdeck; diff --git a/boards/lilygo_tdeck/touch.c b/boards/lilygo_tdeck/touch.c new file mode 100644 index 00000000..4329aa9e --- /dev/null +++ b/boards/lilygo_tdeck/touch.c @@ -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 + }; +} diff --git a/components/board_2432s024/CMakeLists.txt b/boards/yellow_board/CMakeLists.txt similarity index 100% rename from components/board_2432s024/CMakeLists.txt rename to boards/yellow_board/CMakeLists.txt diff --git a/components/board_2432s024/board_2432s024_display.c b/boards/yellow_board/display.c similarity index 90% rename from components/board_2432s024/board_2432s024_display.c rename to boards/yellow_board/display.c index 6cb16ca9..a25166e2 100644 --- a/components/board_2432s024/board_2432s024_display.c +++ b/boards/yellow_board/display.c @@ -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; } diff --git a/components/board_2432s024/board_2432s024_touch.c b/boards/yellow_board/touch.c similarity index 79% rename from components/board_2432s024/board_2432s024_touch.c rename to boards/yellow_board/touch.c index 3f1a01f7..0323d8b1 100644 --- a/components/board_2432s024/board_2432s024_touch.c +++ b/boards/yellow_board/touch.c @@ -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) { diff --git a/boards/yellow_board/yellow_board.c b/boards/yellow_board/yellow_board.c new file mode 100644 index 00000000..4543ef60 --- /dev/null +++ b/boards/yellow_board/yellow_board.c @@ -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 +}; diff --git a/boards/yellow_board/yellow_board.h b/boards/yellow_board/yellow_board.h new file mode 100644 index 00000000..7ba9a1fc --- /dev/null +++ b/boards/yellow_board/yellow_board.h @@ -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; diff --git a/components/board_2432s024/board_2432s024.h b/components/board_2432s024/board_2432s024.h deleted file mode 100644 index 226bc9a5..00000000 --- a/components/board_2432s024/board_2432s024.h +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once - -#include "board_2432s024_display.h" -#include "board_2432s024_touch.h" diff --git a/components/board_2432s024/board_2432s024_display.h b/components/board_2432s024/board_2432s024_display.h deleted file mode 100644 index fb995bf6..00000000 --- a/components/board_2432s024/board_2432s024_display.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include "display.h" - -#ifdef __cplusplus -extern "C" { -#endif - -extern DisplayDriver board_2432s024_create_display_driver(); - -#ifdef __cplusplus -} -#endif diff --git a/components/board_2432s024/board_2432s024_touch.h b/components/board_2432s024/board_2432s024_touch.h deleted file mode 100644 index 7c804b4a..00000000 --- a/components/board_2432s024/board_2432s024_touch.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include "touch.h" - -#ifdef __cplusplus -extern "C" { -#endif - -extern TouchDriver board_2432s024_create_touch_driver(); - -#ifdef __cplusplus -} -#endif diff --git a/components/furi/src/app_manifest.h b/components/furi/src/app_manifest.h index b0e4bec1..d463b67a 100644 --- a/components/furi/src/app_manifest.h +++ b/components/furi/src/app_manifest.h @@ -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 diff --git a/components/furi/src/check.h b/components/furi/src/check.h index 6285c4ac..098b0e1e 100644 --- a/components/furi/src/check.h +++ b/components/furi/src/check.h @@ -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 diff --git a/components/furi/src/event_flag.c b/components/furi/src/event_flag.c index f9630bd5..bbe26a92 100644 --- a/components/furi/src/event_flag.c +++ b/components/furi/src/event_flag.c @@ -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) { diff --git a/components/furi/src/kernel.h b/components/furi/src/kernel.h index f6863d9d..c1236634 100644 --- a/components/furi/src/kernel.h +++ b/components/furi/src/kernel.h @@ -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" { diff --git a/components/furi/src/pubsub.c b/components/furi/src/pubsub.c index 4d5112af..9d0f004a 100644 --- a/components/furi/src/pubsub.c +++ b/components/furi/src/pubsub.c @@ -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); diff --git a/components/furi/src/record.c b/components/furi/src/record.c index 5ea69527..110363d1 100644 --- a/components/furi/src/record.c +++ b/components/furi/src/record.c @@ -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(); diff --git a/components/furi/src/thread.c b/components/furi/src/thread.c index 9e643521..8f3a3a73 100644 --- a/components/furi/src/thread.c +++ b/components/furi/src/thread.c @@ -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, diff --git a/components/furi/src/thread.h b/components/furi/src/thread.h index e85b44db..efbf48d4 100644 --- a/components/furi/src/thread.h +++ b/components/furi/src/thread.h @@ -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 * diff --git a/components/nanobake/src/apps/services/desktop/desktop.c b/components/nanobake/src/apps/services/desktop/desktop.c index d98a90b7..c457651f 100644 --- a/components/nanobake/src/apps/services/desktop/desktop.c +++ b/components/nanobake/src/apps/services/desktop/desktop.c @@ -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 }; diff --git a/components/nanobake/src/apps/services/gui/gui.c b/components/nanobake/src/apps/services/gui/gui.c index ef95899d..944333c8 100644 --- a/components/nanobake/src/apps/services/gui/gui.c +++ b/components/nanobake/src/apps/services/gui/gui.c @@ -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 diff --git a/components/nanobake/src/apps/services/gui/gui.h b/components/nanobake/src/apps/services/gui/gui.h index b2fa87d1..644239f9 100644 --- a/components/nanobake/src/apps/services/gui/gui.h +++ b/components/nanobake/src/apps/services/gui/gui.h @@ -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 } diff --git a/components/nanobake/src/apps/services/gui/gui_draw.c b/components/nanobake/src/apps/services/gui/gui_draw.c index d72e97ce..3144f6ad 100644 --- a/components/nanobake/src/apps/services/gui/gui_draw.c +++ b/components/nanobake/src/apps/services/gui/gui_draw.c @@ -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); } diff --git a/components/nanobake/src/apps/services/gui/gui_i.h b/components/nanobake/src/apps/services/gui/gui_i.h index 63ab36f9..d8305ef8 100644 --- a/components/nanobake/src/apps/services/gui/gui_i.h +++ b/components/nanobake/src/apps/services/gui/gui_i.h @@ -1,57 +1,28 @@ #pragma once +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" #include "gui.h" - -#include -#include -#include - #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 #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(); diff --git a/components/nanobake/src/apps/services/gui/view_port.c b/components/nanobake/src/apps/services/gui/view_port.c index b83bfc77..e74a0ef8 100644 --- a/components/nanobake/src/apps/services/gui/view_port.c +++ b/components/nanobake/src/apps/services/gui/view_port.c @@ -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); } diff --git a/components/nanobake/src/apps/services/gui/widgets/toolbar.c b/components/nanobake/src/apps/services/gui/widgets/toolbar.c index a5a3afd1..8886b05e 100644 --- a/components/nanobake/src/apps/services/gui/widgets/toolbar.c +++ b/components/nanobake/src/apps/services/gui/widgets/toolbar.c @@ -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) { diff --git a/components/nanobake/src/apps/services/loader/loader.c b/components/nanobake/src/apps/services/loader/loader.c index f8249dd9..15b4075e 100644 --- a/components/nanobake/src/apps/services/loader/loader.c +++ b/components/nanobake/src/apps/services/loader/loader.c @@ -6,18 +6,55 @@ #include #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 \ No newline at end of file diff --git a/components/nanobake/src/apps/services/loader/loader.h b/components/nanobake/src/apps/services/loader/loader.h index 5ede7eb1..b0dca57f 100644 --- a/components/nanobake/src/apps/services/loader/loader.h +++ b/components/nanobake/src/apps/services/loader/loader.h @@ -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 } diff --git a/components/nanobake/src/apps/services/loader/loader_i.h b/components/nanobake/src/apps/services/loader/loader_i.h index 98403127..39cdb462 100644 --- a/components/nanobake/src/apps/services/loader/loader_i.h +++ b/components/nanobake/src/apps/services/loader/loader_i.h @@ -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 { diff --git a/components/nanobake/src/apps/system/system_info/system_info.c b/components/nanobake/src/apps/system/system_info/system_info.c index d1e3ee54..93a86728 100644 --- a/components/nanobake/src/apps/system/system_info/system_info.c +++ b/components/nanobake/src/apps/system/system_info/system_info.c @@ -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 }; diff --git a/components/nanobake/src/devices.c b/components/nanobake/src/devices.c index 613421f2..dff6bd16 100644 --- a/components/nanobake/src/devices.c +++ b/components/nanobake/src/devices.c @@ -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 }; diff --git a/components/nanobake/src/devices.h b/components/nanobake/src/devices.h index 798a941d..40cf5b2f 100644 --- a/components/nanobake/src/devices.h +++ b/components/nanobake/src/devices.h @@ -10,7 +10,7 @@ extern "C" { typedef struct { DisplayDevice* _Nonnull display; TouchDevice* _Nullable touch; -} Devices; +} Hardware; #ifdef __cplusplus } diff --git a/components/nanobake/src/devices_i.h b/components/nanobake/src/devices_i.h index 634d8228..f03a20e1 100644 --- a/components/nanobake/src/devices_i.h +++ b/components/nanobake/src/devices_i.h @@ -6,7 +6,7 @@ extern "C" { #endif -Devices nb_devices_create(Config _Nonnull* config); +Hardware nb_hardware_init(const HardwareConfig _Nonnull* config); #ifdef __cplusplus } diff --git a/components/nanobake/src/display.h b/components/nanobake/src/display.h index ce7bca8a..ca8be2e7 100644 --- a/components/nanobake/src/display.h +++ b/components/nanobake/src/display.h @@ -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; diff --git a/components/nanobake/src/graphics.c b/components/nanobake/src/graphics.c index 7eb11803..59cbfcd4 100644 --- a/components/nanobake/src/graphics.c +++ b/components/nanobake/src/graphics.c @@ -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, diff --git a/components/nanobake/src/graphics_i.h b/components/nanobake/src/graphics_i.h index b37de168..0156faa2 100644 --- a/components/nanobake/src/graphics_i.h +++ b/components/nanobake/src/graphics_i.h @@ -7,7 +7,7 @@ extern "C" { #endif -Lvgl nb_graphics_init(Devices _Nonnull* hardware); +Lvgl nb_graphics_init(Hardware _Nonnull* hardware); #ifdef __cplusplus } diff --git a/components/nanobake/src/nanobake.c b/components/nanobake/src/nanobake.c index ad807779..982e65bc 100644 --- a/components/nanobake/src/nanobake.c +++ b/components/nanobake/src/nanobake.c @@ -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); diff --git a/components/nanobake/src/nanobake.h b/components/nanobake/src/nanobake.h index 508613d4..9160d079 100644 --- a/components/nanobake/src/nanobake.h +++ b/components/nanobake/src/nanobake.h @@ -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(); diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index a9d59e36..36f50510 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -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} ) diff --git a/main/Kconfig b/main/Kconfig new file mode 100644 index 00000000..2f0f7237 --- /dev/null +++ b/main/Kconfig @@ -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 diff --git a/main/idf_component.yml b/main/idf_component.yml index 47aff82f..b02f5a80 100644 --- a/main/idf_component.yml +++ b/main/idf_component.yml @@ -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' diff --git a/main/src/board_config.h b/main/src/board_config.h new file mode 100644 index 00000000..dffdada9 --- /dev/null +++ b/main/src/board_config.h @@ -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 diff --git a/main/src/hello_world/hello_world.c b/main/src/hello_world/hello_world.c index d671ac18..ffb4ec78 100644 --- a/main/src/hello_world/hello_world.c +++ b/main/src/hello_world/hello_world.c @@ -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 }; diff --git a/main/src/main.c b/main/src/main.c index 5339a463..6fe352b1 100644 --- a/main/src/main.c +++ b/main/src/main.c @@ -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); -// }); } diff --git a/sdkconfig.board.lilygo_tdeck b/sdkconfig.board.lilygo_tdeck new file mode 100644 index 00000000..ac104ab0 --- /dev/null +++ b/sdkconfig.board.lilygo_tdeck @@ -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 diff --git a/sdkconfig.board.yellow_board b/sdkconfig.board.yellow_board new file mode 100644 index 00000000..b3a30fba --- /dev/null +++ b/sdkconfig.board.yellow_board @@ -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 diff --git a/sdkconfig.defaults b/sdkconfig.defaults index e9fae618..31cb3d6b 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -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