This commit is contained in:
2026-07-19 16:05:07 -05:00
parent 0d761b048f
commit 59d038efc2
9 changed files with 318 additions and 18 deletions
+9
View File
@@ -1,10 +1,19 @@
idf_component_register( idf_component_register(
SRCS "blk_box.cpp" SRCS "blk_box.cpp"
INCLUDE_DIRS "include" "." INCLUDE_DIRS "include" "."
REQUIRES
lvgl
PRIV_REQUIRES PRIV_REQUIRES
led_strip led_strip
esp_driver_gpio esp_driver_gpio
esp_driver_i2c esp_driver_i2c
esp_driver_spi
esp_timer
esp_lcd
)
target_compile_options(${COMPONENT_LIB} PRIVATE
-Wno-missing-field-initializers
) )
add_subdirectory(drivers) add_subdirectory(drivers)
+2
View File
@@ -5,6 +5,7 @@
#include "blk_box_drivers/leds.hpp" #include "blk_box_drivers/leds.hpp"
#include "blk_box_drivers/char_lcd.hpp" #include "blk_box_drivers/char_lcd.hpp"
#include "blk_box_drivers/ssegs.hpp" #include "blk_box_drivers/ssegs.hpp"
#include "blk_box_drivers/tft.hpp"
void init_blk_box(BlkBoxInitConfig cfg) { void init_blk_box(BlkBoxInitConfig cfg) {
init_main_i2c(); init_main_i2c();
@@ -12,4 +13,5 @@ void init_blk_box(BlkBoxInitConfig cfg) {
init_leds(); init_leds();
init_lcd(); init_lcd();
init_ssegs(); init_ssegs();
init_tft();
} }
+1
View File
@@ -8,6 +8,7 @@ set(SOURCES
"lcd2004.cpp" "lcd2004.cpp"
"leds.cpp" "leds.cpp"
"ssegs.cpp" "ssegs.cpp"
"tft.cpp"
"tm1640.cpp" "tm1640.cpp"
) )
+1
View File
@@ -445,6 +445,7 @@ int32_t SSegController::get_module_time() {
return module_time.load(std::memory_order_acquire); return module_time.load(std::memory_order_acquire);
} }
// TODO: take timeout to these functions \/
void SSegController::flush() { void SSegController::flush() {
xEventGroupWaitBits(ssegs_event_group, EVENT_CMDS_FLUSHED, pdFALSE, pdTRUE, portMAX_DELAY); xEventGroupWaitBits(ssegs_event_group, EVENT_CMDS_FLUSHED, pdFALSE, pdTRUE, portMAX_DELAY);
} }
+249
View File
@@ -0,0 +1,249 @@
#include "blk_box_drivers/tft.hpp"
#include "pins.h"
#include <freertos/FreeRTOS.h>
#include <esp_log.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include <esp_lcd_ili9488.h>
#include <esp_log.h>
#include <esp_timer.h>
static const char* TAG = "tft";
static esp_lcd_panel_io_handle_t lcd_io_handle = NULL;
static esp_lcd_panel_handle_t lcd_handle = NULL;
static lv_disp_draw_buf_t lv_disp_buf;
static lv_disp_drv_t lv_disp_drv;
static lv_disp_t *lv_display = NULL;
static lv_color_t *lv_buf_1 = NULL;
static lv_color_t *lv_buf_2 = NULL;
lv_obj_t* screen;
static lv_style_t style_screen;
SemaphoreHandle_t xGuiSemaphore;
// static bool replay_handler(const char* event, char* arg) {
// return false;
// }
static bool notify_lvgl_flush_ready(
esp_lcd_panel_io_handle_t panel_io,
esp_lcd_panel_io_event_data_t *edata,
void *user_ctx
) {
lv_disp_drv_t *disp_driver = (lv_disp_drv_t *)user_ctx;
lv_disp_flush_ready(disp_driver);
return false;
}
// const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
// /// Base 64 encodes a u16... sort of. This doesn't do any of the fancy padding stuff.
// static void encode_base64(char* buf, size_t start_idx, uint16_t value) {
// buf[start_idx+0] = base64_chars[(value >> 10) & 0x3F];
// buf[start_idx+1] = base64_chars[(value >> 4) & 0x3F];
// buf[start_idx+2] = base64_chars[(value << 2) & 0x3F];
// }
static void lvgl_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map) {
esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t) drv->user_data;
int offsetx1 = area->x1;
int offsetx2 = area->x2;
int offsety1 = area->y1;
int offsety2 = area->y2;
esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, color_map);
// TODO: change this to be a kconfig value
#if false
if (is_state_tracking()) {
size_t size = (offsetx2 + 1 - offsetx1) * (offsety2 + 1 - offsety1) + 1;
// if (size > 1024) {
// ESP_LOGW("tft_track_state", "Write too big (%d)! truncating to 1024!", size);
// }
// size = MIN(1024, size);
// 24 bytes for the offsets
// 3 bytes per encoded color
// 1 byte for null terminator
size_t alloc_size = 24 + size * 3 + 1;
char* buf = (char*)malloc(alloc_size);
if (buf != nullptr) {
size_t initial_offset = sprintf(buf, "%d,%d,%d,%d:", offsetx1, offsety1, offsetx2 + 1, offsety2 + 1);
for (size_t i = 0; i < size; i++) {
size_t index = initial_offset + i * 3;
// we assume that the size of the color data is 16b
static_assert(sizeof(lv_color_t) == sizeof(uint16_t), "lv_color_t must be 16b wide");
encode_base64(buf, index, color_map[i].full);
}
buf[initial_offset + (size-1) * 3 + 1] = '\0';
event_occured("TFT_W", buf);
free(buf);
} else {
ESP_LOGE("tft_track_state", "buffer alloc failed!");
}
}
#endif
}
static void IRAM_ATTR lv_tick_task(void *param) {
lv_tick_inc(LVGL_UPDATE_PERIOD_MS);
}
static void initialize_spi() {
ESP_LOGI(TAG, "Initializing SPI bus (MOSI:%d, MISO:%d, CLK:%d)",
PIN_TFT_MOSI, PIN_TFT_MISO, PIN_TFT_CLK);
spi_bus_config_t bus = {
.mosi_io_num = PIN_TFT_MOSI,
.miso_io_num = PIN_TFT_MISO,
.sclk_io_num = PIN_TFT_CLK,
.quadwp_io_num = GPIO_NUM_NC,
.quadhd_io_num = GPIO_NUM_NC,
.data4_io_num = GPIO_NUM_NC,
.data5_io_num = GPIO_NUM_NC,
.data6_io_num = GPIO_NUM_NC,
.data7_io_num = GPIO_NUM_NC,
.max_transfer_sz = SPI_MAX_TRANSFER_SIZE,
.flags = SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MISO |
SPICOMMON_BUSFLAG_MOSI | SPICOMMON_BUSFLAG_MASTER,
.isr_cpu_id = ESP_INTR_CPU_AFFINITY_AUTO,
.intr_flags = ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM
};
ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &bus, SPI_DMA_CH_AUTO));
}
static void initialize_display() {
const esp_lcd_panel_io_spi_config_t io_config = {
.cs_gpio_num = PIN_TFT_CS,
.dc_gpio_num = PIN_TFT_RS,
.spi_mode = 0,
.pclk_hz = DISPLAY_REFRESH_HZ,
.trans_queue_depth = DISPLAY_SPI_QUEUE_LEN,
.on_color_trans_done = notify_lvgl_flush_ready,
.user_ctx = &lv_disp_drv,
.lcd_cmd_bits = DISPLAY_COMMAND_BITS,
.lcd_param_bits = DISPLAY_PARAMETER_BITS,
.flags = {
.dc_high_on_cmd = 0, /*!< If enabled, DC level = 1 indicates command transfer */
.dc_low_on_data = 0, /*!< If enabled, DC level = 0 indicates color data transfer */
.dc_low_on_param = 0, /*!< If enabled, DC level = 0 indicates parameter transfer */
.octal_mode = 0, /*!< transmit with octal mode (8 data lines), this mode is used to simulate Intel 8080 timing */
.quad_mode = 0, /*!< transmit with quad mode (4 data lines), this mode is useful when transmitting LCD parameters (Only use one line for command) */
.sio_mode = 0, /*!< Read and write through a single data line (MOSI) */
.lsb_first = 0, /*!< transmit LSB bit first */
.cs_high_active = 0, /*!< CS line is high active */
}
};
const esp_lcd_panel_dev_config_t lcd_config = {
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR,
.data_endian = LCD_RGB_DATA_ENDIAN_BIG,
.bits_per_pixel = 18,
.reset_gpio_num = PIN_TFT_RST,
.flags = {
.reset_active_high = 0
},
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)SPI2_HOST, &io_config, &lcd_io_handle));
ESP_ERROR_CHECK(esp_lcd_new_panel_ili9488_ips(lcd_io_handle, &lcd_config, LV_BUFFER_SIZE, &lcd_handle));
// ESP_ERROR_CHECK(esp_lcd_new_panel_ili9488(lcd_io_handle, &lcd_config, LV_BUFFER_SIZE, &lcd_handle));
ESP_ERROR_CHECK(esp_lcd_panel_reset(lcd_handle));
ESP_ERROR_CHECK(esp_lcd_panel_init(lcd_handle));
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(lcd_handle, true));
ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(lcd_handle, true));
ESP_ERROR_CHECK(esp_lcd_panel_mirror(lcd_handle, false, true));
ESP_ERROR_CHECK(esp_lcd_panel_set_gap(lcd_handle, 0, 0));
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
ESP_ERROR_CHECK(esp_lcd_panel_disp_off(lcd_handle, false));
#else
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(lcd_handle, true));
#endif
}
static void guiTask(void *pvParameter) {
xGuiSemaphore = xSemaphoreCreateRecursiveMutex();
ESP_LOGI(TAG, "Initializing LVGL");
lv_init();
ESP_LOGI(TAG, "Allocating %zu bytes for LVGL buffer", LV_BUFFER_SIZE * sizeof(lv_color_t));
lv_buf_1 = (lv_color_t *)heap_caps_malloc(LV_BUFFER_SIZE * sizeof(lv_color_t), MALLOC_CAP_DMA);
#if USE_DOUBLE_BUFFERING
ESP_LOGI(TAG, "Allocating %zu bytes for second LVGL buffer", LV_BUFFER_SIZE * sizeof(lv_color_t));
lv_buf_2 = (lv_color_t *)heap_caps_malloc(LV_BUFFER_SIZE * sizeof(lv_color_t), MALLOC_CAP_DMA);
#endif
ESP_LOGI(TAG, "Creating LVLG display buffer");
lv_disp_draw_buf_init(&lv_disp_buf, lv_buf_1, lv_buf_2, LV_BUFFER_SIZE);
ESP_LOGI(TAG, "Initializing %dx%d display", DISPLAY_HORIZONTAL_PIXELS, DISPLAY_VERTICAL_PIXELS);
lv_disp_drv_init(&lv_disp_drv);
lv_disp_drv.hor_res = DISPLAY_HORIZONTAL_PIXELS;
lv_disp_drv.ver_res = DISPLAY_VERTICAL_PIXELS;
lv_disp_drv.flush_cb = lvgl_flush_cb;
lv_disp_drv.draw_buf = &lv_disp_buf;
lv_disp_drv.user_data = lcd_handle;
// lv_disp_drv.rotated = LV_DISP_ROT_90;
lv_display = lv_disp_drv_register(&lv_disp_drv);
ESP_LOGI(TAG, "Creating LVGL tick timer");
const esp_timer_create_args_t lvgl_tick_timer_args = {
.callback = &lv_tick_task,
// .dispatch_method = ESP_TIMER_TASK,
.name = "periodic_gui",
// .skip_unhandled_events = false
};
esp_timer_handle_t periodic_timer;
ESP_ERROR_CHECK(esp_timer_create(&lvgl_tick_timer_args, &periodic_timer));
ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_timer, LVGL_UPDATE_PERIOD_MS * 1000));
screen = lv_scr_act();
lv_style_init(&style_screen);
lv_style_set_bg_color(&style_screen, lv_color_black());
lv_obj_add_style(screen, &style_screen, LV_STATE_DEFAULT);
while (1) {
/* Delay 1 tick (assumes FreeRTOS tick is 10ms */
vTaskDelay(pdMS_TO_TICKS(10));
/* Try to take the semaphore, call lvgl related function on success */
if (pdTRUE == xSemaphoreTakeRecursive(xGuiSemaphore, portMAX_DELAY)) {
lv_task_handler();
xSemaphoreGiveRecursive(xGuiSemaphore);
}
}
vTaskDelete(NULL);
}
void init_tft() {
ESP_LOGI(TAG, "Initializing TFT...");
initialize_spi();
initialize_display();
xTaskCreatePinnedToCore(guiTask, "gui", 4096*2, NULL, 5, NULL, 1);
// register_replay_fn(replay_handler);
ESP_LOGI(TAG, "TFT initialized!");
}
bool lvgl_lock(TickType_t ticks_to_wait) {
return xSemaphoreTakeRecursive(xGuiSemaphore, ticks_to_wait) == pdTRUE;
}
void lvgl_unlock() {
xSemaphoreGiveRecursive(xGuiSemaphore);
}
+5 -12
View File
@@ -3,16 +3,9 @@ dependencies:
## Required IDF version ## Required IDF version
idf: idf:
version: '>=6.0.0' version: '>=6.0.0'
# # Put list of dependencies here
# # For components maintained by Espressif:
# component: "~1.0.0"
# # For 3rd party components:
# username/component: ">=1.0.0,<2.0.0"
# username2/component2:
# version: "~1.0.0"
# # For transient dependencies `public` flag can be set.
# # `public` flag doesn't have an effect dependencies of the `main` component.
# # All dependencies of `main` are public by default.
# public: true
espressif/led_strip: ^3.0.3 espressif/led_strip: ^3.0.3
# atanisoft/esp_lcd_ili9488: ^1.1.1
atanisoft/esp_lcd_ili9488:
path: ../../../esp_lcd_ili9488
lvgl/lvgl: ^8.4
+6 -1
View File
@@ -26,10 +26,16 @@ enum class Button: uint8_t {
B2 = 1, B2 = 1,
B3 = 2, B3 = 2,
B4 = 3, B4 = 3,
GREEN = 0, GREEN = 0,
RED = 1, RED = 1,
YELLOW = 2, YELLOW = 2,
BLUE = 3, BLUE = 3,
LEFT = 0,
DOWN = 1,
UP = 2,
RIGHT = 3,
}; };
constexpr uint8_t raw_value(Button v) { return static_cast<uint8_t>(v); } constexpr uint8_t raw_value(Button v) { return static_cast<uint8_t>(v); }
@@ -85,7 +91,6 @@ constexpr char keypad_key_to_char(KeypadKey key) {
return lookup[static_cast<uint8_t>(key) & 0b1111]; return lookup[static_cast<uint8_t>(key) & 0b1111];
} }
struct SwitchFlip { struct SwitchFlip {
private: private:
// [bit2: up] [bit1-0: switch] // [bit2: up] [bit1-0: switch]
+39
View File
@@ -0,0 +1,39 @@
#ifndef TFT_HPP
#define TFT_HPP
#include <lvgl.h>
#include <freertos/FreeRTOS.h>
// Uncomment the following line to enable using double buffering of LVGL color
// data.
// #define USE_DOUBLE_BUFFERING 1
// rotation swaps the horizontal and vertical pixel counts
#define DISPLAY_HORIZONTAL_PIXELS 480
#define DISPLAY_VERTICAL_PIXELS 320
#define DISPLAY_COMMAND_BITS 8
#define DISPLAY_PARAMETER_BITS 8
#define DISPLAY_REFRESH_HZ 40000000
#define DISPLAY_SPI_QUEUE_LEN 10
#define SPI_MAX_TRANSFER_SIZE 32768
#define TFT_INVERT_COLOR false
// Default to 50 lines of color data
#define LV_BUFFER_SIZE DISPLAY_HORIZONTAL_PIXELS * 50
#define LVGL_UPDATE_PERIOD_MS 5
#define BACKLIGHT_LEDC_MODE LEDC_LOW_SPEED_MODE
#define BACKLIGHT_LEDC_CHANNEL LEDC_CHANNEL_0
#define BACKLIGHT_LEDC_TIMER LEDC_TIMER_1
#define BACKLIGHT_LEDC_TIMER_RESOLUTION LEDC_TIMER_10_BIT
#define BACKLIGHT_LEDC_FRQUENCY 5000
extern lv_obj_t* screen;
void init_tft();
bool lvgl_lock(TickType_t ticks_to_wait);
void lvgl_unlock();
#endif // TFT_HPP
+6 -5
View File
@@ -6,11 +6,12 @@
#define PIN_SDA (GPIO_NUM_7) #define PIN_SDA (GPIO_NUM_7)
#define PIN_SCL (GPIO_NUM_15) #define PIN_SCL (GPIO_NUM_15)
#define PIN_LCD_MISO (GPIO_NUM_16) #define PIN_TFT_CS (GPIO_NUM_NC)
#define PIN_LCD_MOSI (GPIO_NUM_17) #define PIN_TFT_MISO (GPIO_NUM_16)
#define PIN_LCD_CLK (GPIO_NUM_18) #define PIN_TFT_MOSI (GPIO_NUM_17)
#define PIN_LCD_RS (GPIO_NUM_8) #define PIN_TFT_CLK (GPIO_NUM_18)
#define PIN_LCD_RST (GPIO_NUM_9) #define PIN_TFT_RS (GPIO_NUM_8)
#define PIN_TFT_RST (GPIO_NUM_9)
#define PIN_USB_DM (GPIO_NUM_19) #define PIN_USB_DM (GPIO_NUM_19)
#define PIN_USB_DP (GPIO_NUM_20) #define PIN_USB_DP (GPIO_NUM_20)