Merge f1c4e7e8f0 into 2c136aca74
This commit is contained in:
commit
83f323c391
|
|
@ -52,9 +52,17 @@ if(CONFIG_CSI_MOCK_ENABLED)
|
|||
list(APPEND SRCS "mock_csi.c" "rv_radio_ops_mock.c")
|
||||
endif()
|
||||
|
||||
# ADR-045: AMOLED display support (compile-time optional)
|
||||
# ADR-045: on-device display support (compile-time optional).
|
||||
# Exactly one panel HAL is compiled, selected by the DISPLAY_PANEL choice.
|
||||
if(CONFIG_DISPLAY_ENABLE)
|
||||
list(APPEND SRCS "display_hal.c" "display_ui.c" "display_task.c")
|
||||
list(APPEND SRCS "display_task.c")
|
||||
if(CONFIG_DISPLAY_PANEL_ST7789)
|
||||
# 240x280 ST7789: compact UI + ST7789 HAL.
|
||||
list(APPEND SRCS "display_hal_st7789.c" "display_ui_st7789.c")
|
||||
else()
|
||||
# 368x448 SH8601 AMOLED: 4-view UI + QSPI HAL.
|
||||
list(APPEND SRCS "display_hal.c" "display_ui.c")
|
||||
endif()
|
||||
list(APPEND REQUIRES esp_lcd esp_lcd_touch lvgl)
|
||||
endif()
|
||||
|
||||
|
|
|
|||
|
|
@ -170,15 +170,104 @@ menu "Adaptive Controller (ADR-081)"
|
|||
|
||||
endmenu
|
||||
|
||||
menu "AMOLED Display (ADR-045)"
|
||||
menu "Display (ADR-045)"
|
||||
|
||||
config DISPLAY_ENABLE
|
||||
bool "Enable AMOLED display support"
|
||||
bool "Enable on-device display support"
|
||||
default y
|
||||
help
|
||||
Enable RM67162 QSPI AMOLED display and LVGL UI.
|
||||
Auto-detects at boot; gracefully skips if no display hardware.
|
||||
Requires SPIRAM for frame buffers.
|
||||
Enable the LVGL UI on an attached panel. Auto-detects at boot;
|
||||
gracefully skips if no display hardware. Requires SPIRAM for
|
||||
frame buffers. Choose the panel type below — the CSI pipeline
|
||||
runs unchanged regardless of panel.
|
||||
|
||||
choice DISPLAY_PANEL
|
||||
prompt "Display panel"
|
||||
depends on DISPLAY_ENABLE
|
||||
default DISPLAY_PANEL_SH8601
|
||||
help
|
||||
Which physical panel is attached. Exactly one HAL is compiled.
|
||||
|
||||
config DISPLAY_PANEL_SH8601
|
||||
bool "SH8601 QSPI AMOLED (Waveshare ESP32-S3-Touch-AMOLED-1.8, 368x448)"
|
||||
|
||||
config DISPLAY_PANEL_ST7789
|
||||
bool "ST7789V2 SPI LCD (Waveshare ESP32-S3-Touch-LCD-1.69, 240x280)"
|
||||
endchoice
|
||||
|
||||
# ---- ST7789 SPI LCD pins / geometry (Waveshare 1.69 defaults) ----
|
||||
config DISPLAY_ST7789_SCLK
|
||||
int "ST7789 SPI SCLK GPIO"
|
||||
default 6
|
||||
depends on DISPLAY_PANEL_ST7789
|
||||
|
||||
config DISPLAY_ST7789_MOSI
|
||||
int "ST7789 SPI MOSI GPIO"
|
||||
default 7
|
||||
depends on DISPLAY_PANEL_ST7789
|
||||
|
||||
config DISPLAY_ST7789_CS
|
||||
int "ST7789 SPI CS GPIO"
|
||||
default 5
|
||||
depends on DISPLAY_PANEL_ST7789
|
||||
|
||||
config DISPLAY_ST7789_DC
|
||||
int "ST7789 SPI DC GPIO"
|
||||
default 4
|
||||
depends on DISPLAY_PANEL_ST7789
|
||||
|
||||
config DISPLAY_ST7789_RST
|
||||
int "ST7789 RST GPIO"
|
||||
default 8
|
||||
depends on DISPLAY_PANEL_ST7789
|
||||
|
||||
config DISPLAY_ST7789_BL
|
||||
int "ST7789 backlight PWM GPIO"
|
||||
default 15
|
||||
depends on DISPLAY_PANEL_ST7789
|
||||
|
||||
config DISPLAY_ST7789_H_RES
|
||||
int "ST7789 horizontal resolution"
|
||||
default 240
|
||||
depends on DISPLAY_PANEL_ST7789
|
||||
|
||||
config DISPLAY_ST7789_V_RES
|
||||
int "ST7789 vertical resolution"
|
||||
default 280
|
||||
depends on DISPLAY_PANEL_ST7789
|
||||
|
||||
config DISPLAY_ST7789_GAP_X
|
||||
int "ST7789 column offset (gap X)"
|
||||
default 0
|
||||
depends on DISPLAY_PANEL_ST7789
|
||||
|
||||
config DISPLAY_ST7789_GAP_Y
|
||||
int "ST7789 row offset (gap Y)"
|
||||
default 20
|
||||
depends on DISPLAY_PANEL_ST7789
|
||||
help
|
||||
The 240x280 visible window sits at row 20 of the ST7789's
|
||||
240x320 controller RAM. Adjust if the image is shifted.
|
||||
|
||||
config DISPLAY_ST7789_TOUCH_SDA
|
||||
int "CST816 touch I2C SDA GPIO"
|
||||
default 11
|
||||
depends on DISPLAY_PANEL_ST7789
|
||||
|
||||
config DISPLAY_ST7789_TOUCH_SCL
|
||||
int "CST816 touch I2C SCL GPIO"
|
||||
default 10
|
||||
depends on DISPLAY_PANEL_ST7789
|
||||
|
||||
config DISPLAY_ST7789_TOUCH_RST
|
||||
int "CST816 touch RST GPIO"
|
||||
default 13
|
||||
depends on DISPLAY_PANEL_ST7789
|
||||
|
||||
config DISPLAY_ST7789_TOUCH_INT
|
||||
int "CST816 touch INT GPIO"
|
||||
default 14
|
||||
depends on DISPLAY_PANEL_ST7789
|
||||
|
||||
config DISPLAY_FPS_LIMIT
|
||||
int "Display refresh rate limit (FPS)"
|
||||
|
|
|
|||
|
|
@ -379,4 +379,13 @@ void display_hal_set_brightness(uint8_t percent)
|
|||
panel_write_cmd(0x51, &val, 1);
|
||||
}
|
||||
|
||||
void display_hal_refresh(void)
|
||||
{
|
||||
/* Re-assert display-on (0x29) + brightness so a transient brownout that
|
||||
* dimmed the panel self-recovers without a full re-init. */
|
||||
if (!s_io_handle) return;
|
||||
panel_write_cmd(0x29, NULL, 0);
|
||||
display_hal_set_brightness(CONFIG_DISPLAY_BRIGHTNESS);
|
||||
}
|
||||
|
||||
#endif /* CONFIG_DISPLAY_ENABLE */
|
||||
|
|
|
|||
|
|
@ -64,6 +64,16 @@ bool display_hal_touch_read(uint16_t *x, uint16_t *y);
|
|||
*/
|
||||
void display_hal_set_brightness(uint8_t percent);
|
||||
|
||||
/**
|
||||
* Self-heal: re-assert backlight + display-on state.
|
||||
*
|
||||
* Called periodically by the display task so a transient power sag
|
||||
* (e.g. shared-USB brownout dimming the backlight) auto-recovers
|
||||
* instead of leaving the panel dark while the MCU keeps running.
|
||||
* Cheap and flicker-free — does NOT re-init the panel.
|
||||
*/
|
||||
void display_hal_refresh(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -0,0 +1,309 @@
|
|||
/**
|
||||
* @file display_hal_st7789.c
|
||||
* @brief ADR-045: ST7789V2 SPI LCD + CST816 touch HAL.
|
||||
*
|
||||
* Hardware abstraction for the Waveshare ESP32-S3-Touch-LCD-1.69
|
||||
* (240x280 IPS, ST7789V2 over 4-wire SPI, CST816 capacitive touch).
|
||||
*
|
||||
* Implements the same display_hal.h contract as the SH8601 QSPI AMOLED HAL,
|
||||
* but on ESP-IDF's built-in esp_lcd ST7789 panel driver. Selected at build
|
||||
* time via CONFIG_DISPLAY_PANEL_ST7789 (see Kconfig.projbuild). Exactly one
|
||||
* display_hal_*.c is compiled into the image (CMakeLists picks by panel).
|
||||
*
|
||||
* Pin assignments (Waveshare ESP32-S3-Touch-LCD-1.69, Kconfig-overridable):
|
||||
* LCD SPI: SCLK=6, MOSI=7, CS=5, DC=4, RST=8, BL=15
|
||||
* Touch: I2C SDA=11, SCL=10, RST=13, INT=14 (CST816 @ 0x15)
|
||||
*
|
||||
* Runs concurrently with the full CSI pipeline — the panel sits on SPI2_HOST
|
||||
* and a private I2C bus, neither of which the radio/UDP data plane touches.
|
||||
*/
|
||||
|
||||
#include "display_hal.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#if CONFIG_DISPLAY_ENABLE && CONFIG_DISPLAY_PANEL_ST7789
|
||||
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_lcd_panel_io.h"
|
||||
#include "esp_lcd_panel_ops.h"
|
||||
#include "esp_lcd_panel_vendor.h"
|
||||
#include "driver/spi_master.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/ledc.h"
|
||||
#include "driver/i2c.h"
|
||||
#include "esp_heap_caps.h"
|
||||
|
||||
static const char *TAG = "disp_hal_st7789";
|
||||
|
||||
/* ---- LCD SPI pins (Kconfig-overridable) ---- */
|
||||
#define LCD_PIN_SCLK CONFIG_DISPLAY_ST7789_SCLK
|
||||
#define LCD_PIN_MOSI CONFIG_DISPLAY_ST7789_MOSI
|
||||
#define LCD_PIN_CS CONFIG_DISPLAY_ST7789_CS
|
||||
#define LCD_PIN_DC CONFIG_DISPLAY_ST7789_DC
|
||||
#define LCD_PIN_RST CONFIG_DISPLAY_ST7789_RST
|
||||
#define LCD_PIN_BL CONFIG_DISPLAY_ST7789_BL
|
||||
|
||||
#define LCD_H_RES CONFIG_DISPLAY_ST7789_H_RES
|
||||
#define LCD_V_RES CONFIG_DISPLAY_ST7789_V_RES
|
||||
#define LCD_GAP_X CONFIG_DISPLAY_ST7789_GAP_X
|
||||
#define LCD_GAP_Y CONFIG_DISPLAY_ST7789_GAP_Y
|
||||
|
||||
#define LCD_HOST SPI2_HOST
|
||||
#define LCD_PCLK_HZ (40 * 1000 * 1000)
|
||||
|
||||
/* ---- Backlight PWM ---- */
|
||||
#define BL_LEDC_TIMER LEDC_TIMER_0
|
||||
#define BL_LEDC_MODE LEDC_LOW_SPEED_MODE
|
||||
#define BL_LEDC_CHANNEL LEDC_CHANNEL_0
|
||||
#define BL_LEDC_DUTY_RES LEDC_TIMER_8_BIT
|
||||
#define BL_LEDC_FREQ_HZ 5000
|
||||
|
||||
/* ---- CST816 touch (I2C) ---- */
|
||||
#define TOUCH_I2C_NUM I2C_NUM_0
|
||||
#define TOUCH_I2C_FREQ_HZ 400000
|
||||
#define TOUCH_PIN_SDA CONFIG_DISPLAY_ST7789_TOUCH_SDA
|
||||
#define TOUCH_PIN_SCL CONFIG_DISPLAY_ST7789_TOUCH_SCL
|
||||
#define TOUCH_PIN_RST CONFIG_DISPLAY_ST7789_TOUCH_RST
|
||||
#define TOUCH_PIN_INT CONFIG_DISPLAY_ST7789_TOUCH_INT
|
||||
#define CST816_ADDR 0x15
|
||||
#define CST816_REG_CHIPID 0xA7
|
||||
|
||||
/* ---- State ---- */
|
||||
static esp_lcd_panel_io_handle_t s_io_handle = NULL;
|
||||
static esp_lcd_panel_handle_t s_panel = NULL;
|
||||
static bool s_bl_initialized = false;
|
||||
static bool s_touch_initialized = false;
|
||||
|
||||
/* ===================== Backlight ===================== */
|
||||
|
||||
static void init_backlight(void)
|
||||
{
|
||||
ledc_timer_config_t timer = {
|
||||
.speed_mode = BL_LEDC_MODE,
|
||||
.timer_num = BL_LEDC_TIMER,
|
||||
.duty_resolution = BL_LEDC_DUTY_RES,
|
||||
.freq_hz = BL_LEDC_FREQ_HZ,
|
||||
.clk_cfg = LEDC_AUTO_CLK,
|
||||
};
|
||||
if (ledc_timer_config(&timer) != ESP_OK) {
|
||||
ESP_LOGW(TAG, "LEDC timer config failed — backlight will be GPIO-on");
|
||||
gpio_set_direction(LCD_PIN_BL, GPIO_MODE_OUTPUT);
|
||||
gpio_set_level(LCD_PIN_BL, 1);
|
||||
return;
|
||||
}
|
||||
ledc_channel_config_t ch = {
|
||||
.gpio_num = LCD_PIN_BL,
|
||||
.speed_mode = BL_LEDC_MODE,
|
||||
.channel = BL_LEDC_CHANNEL,
|
||||
.timer_sel = BL_LEDC_TIMER,
|
||||
.duty = 0,
|
||||
.hpoint = 0,
|
||||
};
|
||||
ledc_channel_config(&ch);
|
||||
s_bl_initialized = true;
|
||||
}
|
||||
|
||||
/* ===================== Panel ===================== */
|
||||
|
||||
static esp_err_t draw_test_pattern(void)
|
||||
{
|
||||
/* Clear to background once (low power — no full-white frame, which spiked
|
||||
* current and worsened the startup brownout). LVGL draws over this. */
|
||||
uint16_t *line = heap_caps_malloc(LCD_H_RES * sizeof(uint16_t), MALLOC_CAP_DMA);
|
||||
if (!line) return ESP_ERR_NO_MEM;
|
||||
for (int x = 0; x < LCD_H_RES; x++) line[x] = 0x0841; /* near-black */
|
||||
for (int y = 0; y < LCD_V_RES; y++)
|
||||
esp_lcd_panel_draw_bitmap(s_panel, 0, y, LCD_H_RES, y + 1, line);
|
||||
free(line);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t display_hal_init_panel(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "Initializing Waveshare 1.69\" LCD (ST7789V2 %dx%d)...",
|
||||
LCD_H_RES, LCD_V_RES);
|
||||
|
||||
spi_bus_config_t bus_cfg = {
|
||||
.sclk_io_num = LCD_PIN_SCLK,
|
||||
.mosi_io_num = LCD_PIN_MOSI,
|
||||
.miso_io_num = -1,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1,
|
||||
.max_transfer_sz = LCD_H_RES * 80 * sizeof(uint16_t),
|
||||
};
|
||||
esp_err_t ret = spi_bus_initialize(LCD_HOST, &bus_cfg, SPI_DMA_CH_AUTO);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "SPI bus init failed: %s", esp_err_to_name(ret));
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
esp_lcd_panel_io_spi_config_t io_config = {
|
||||
.dc_gpio_num = LCD_PIN_DC,
|
||||
.cs_gpio_num = LCD_PIN_CS,
|
||||
.pclk_hz = LCD_PCLK_HZ,
|
||||
.lcd_cmd_bits = 8,
|
||||
.lcd_param_bits = 8,
|
||||
.spi_mode = 0,
|
||||
.trans_queue_depth = 10,
|
||||
};
|
||||
ret = esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST, &io_config, &s_io_handle);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Panel IO init failed: %s", esp_err_to_name(ret));
|
||||
spi_bus_free(LCD_HOST);
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
esp_lcd_panel_dev_config_t panel_config = {
|
||||
.reset_gpio_num = LCD_PIN_RST,
|
||||
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
|
||||
.bits_per_pixel = 16,
|
||||
};
|
||||
ret = esp_lcd_new_panel_st7789(s_io_handle, &panel_config, &s_panel);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "ST7789 panel create failed: %s", esp_err_to_name(ret));
|
||||
esp_lcd_panel_io_del(s_io_handle);
|
||||
spi_bus_free(LCD_HOST);
|
||||
s_io_handle = NULL;
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
esp_lcd_panel_reset(s_panel);
|
||||
esp_lcd_panel_init(s_panel);
|
||||
/* IPS ST7789 panels show inverted colour without this. */
|
||||
esp_lcd_panel_invert_color(s_panel, true);
|
||||
/* 240x280 visible window sits at row LCD_GAP_Y of the 240x320 controller RAM. */
|
||||
esp_lcd_panel_set_gap(s_panel, LCD_GAP_X, LCD_GAP_Y);
|
||||
esp_lcd_panel_disp_on_off(s_panel, true);
|
||||
|
||||
init_backlight();
|
||||
display_hal_set_brightness(CONFIG_DISPLAY_BRIGHTNESS);
|
||||
|
||||
draw_test_pattern();
|
||||
|
||||
ESP_LOGI(TAG, "ST7789 panel init OK (%dx%d, gap %d,%d)",
|
||||
LCD_H_RES, LCD_V_RES, LCD_GAP_X, LCD_GAP_Y);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void display_hal_draw(int x_start, int y_start, int x_end, int y_end,
|
||||
const void *color_data)
|
||||
{
|
||||
if (!s_panel) return;
|
||||
/* esp_lcd takes an exclusive end coord, which is exactly what
|
||||
* display_task's flush callback already passes (area->x2 + 1). */
|
||||
esp_lcd_panel_draw_bitmap(s_panel, x_start, y_start, x_end, y_end, color_data);
|
||||
}
|
||||
|
||||
/* ===================== Touch (CST816) ===================== */
|
||||
|
||||
static esp_err_t touch_i2c_init(void)
|
||||
{
|
||||
i2c_config_t cfg = {
|
||||
.mode = I2C_MODE_MASTER,
|
||||
.sda_io_num = TOUCH_PIN_SDA,
|
||||
.scl_io_num = TOUCH_PIN_SCL,
|
||||
.sda_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.scl_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.master.clk_speed = TOUCH_I2C_FREQ_HZ,
|
||||
};
|
||||
esp_err_t ret = i2c_param_config(TOUCH_I2C_NUM, &cfg);
|
||||
if (ret != ESP_OK) return ret;
|
||||
return i2c_driver_install(TOUCH_I2C_NUM, I2C_MODE_MASTER, 0, 0, 0);
|
||||
}
|
||||
|
||||
static esp_err_t touch_read_reg(uint8_t reg, uint8_t *data, size_t len)
|
||||
{
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
i2c_master_start(cmd);
|
||||
i2c_master_write_byte(cmd, (CST816_ADDR << 1) | I2C_MASTER_WRITE, true);
|
||||
i2c_master_write_byte(cmd, reg, true);
|
||||
i2c_master_start(cmd);
|
||||
i2c_master_write_byte(cmd, (CST816_ADDR << 1) | I2C_MASTER_READ, true);
|
||||
i2c_master_read(cmd, data, len, I2C_MASTER_LAST_NACK);
|
||||
i2c_master_stop(cmd);
|
||||
esp_err_t ret = i2c_master_cmd_begin(TOUCH_I2C_NUM, cmd, pdMS_TO_TICKS(100));
|
||||
i2c_cmd_link_delete(cmd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t display_hal_init_touch(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "Probing CST816 touch controller...");
|
||||
|
||||
/* Hardware reset pulse (CST816 needs RST toggled to boot). */
|
||||
gpio_set_direction(TOUCH_PIN_RST, GPIO_MODE_OUTPUT);
|
||||
gpio_set_level(TOUCH_PIN_RST, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
gpio_set_level(TOUCH_PIN_RST, 1);
|
||||
vTaskDelay(pdMS_TO_TICKS(60));
|
||||
|
||||
gpio_config_t int_cfg = {
|
||||
.pin_bit_mask = (1ULL << TOUCH_PIN_INT),
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
.pull_up_en = GPIO_PULLUP_ENABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&int_cfg);
|
||||
|
||||
esp_err_t ret = touch_i2c_init();
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Touch I2C init failed: %s", esp_err_to_name(ret));
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
uint8_t chip_id = 0;
|
||||
ret = touch_read_reg(CST816_REG_CHIPID, &chip_id, 1);
|
||||
if (ret != ESP_OK || chip_id == 0x00 || chip_id == 0xFF) {
|
||||
ESP_LOGW(TAG, "CST816 not found (ret=%s, id=0x%02X)", esp_err_to_name(ret), chip_id);
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
s_touch_initialized = true;
|
||||
ESP_LOGI(TAG, "CST816 touch init OK (chip_id=0x%02X)", chip_id);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
bool display_hal_touch_read(uint16_t *x, uint16_t *y)
|
||||
{
|
||||
if (!s_touch_initialized) return false;
|
||||
|
||||
/* Read gesture(0x01), finger_num(0x02), X hi/lo(0x03/04), Y hi/lo(0x05/06). */
|
||||
uint8_t buf[6] = {0};
|
||||
if (touch_read_reg(0x01, buf, sizeof(buf)) != ESP_OK) return false;
|
||||
|
||||
uint8_t fingers = buf[1] & 0x0F;
|
||||
if (fingers == 0) return false;
|
||||
|
||||
*x = ((uint16_t)(buf[2] & 0x0F) << 8) | buf[3];
|
||||
*y = ((uint16_t)(buf[4] & 0x0F) << 8) | buf[5];
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ===================== Brightness ===================== */
|
||||
|
||||
void display_hal_set_brightness(uint8_t percent)
|
||||
{
|
||||
if (percent > 100) percent = 100;
|
||||
if (!s_bl_initialized) {
|
||||
/* LEDC unavailable — drive backlight GPIO directly. */
|
||||
gpio_set_level(LCD_PIN_BL, percent > 0 ? 1 : 0);
|
||||
return;
|
||||
}
|
||||
uint32_t duty = (uint32_t)percent * 255 / 100; /* 8-bit resolution */
|
||||
ledc_set_duty(BL_LEDC_MODE, BL_LEDC_CHANNEL, duty);
|
||||
ledc_update_duty(BL_LEDC_MODE, BL_LEDC_CHANNEL);
|
||||
}
|
||||
|
||||
void display_hal_refresh(void)
|
||||
{
|
||||
/* Backlight-only self-heal: re-assert the LEDC duty (no SPI). The earlier
|
||||
* version also poked esp_lcd_panel_disp_on_off over SPI every 2s, which
|
||||
* could contend with the in-flight LVGL flush and hang the display task.
|
||||
* On a stable supply the panel never powers off, so backlight is enough. */
|
||||
display_hal_set_brightness(CONFIG_DISPLAY_BRIGHTNESS);
|
||||
}
|
||||
|
||||
#endif /* CONFIG_DISPLAY_ENABLE && CONFIG_DISPLAY_PANEL_ST7789 */
|
||||
|
|
@ -24,13 +24,20 @@ bool display_is_active(void) { return s_display_active; }
|
|||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_timer.h"
|
||||
#include "lvgl.h"
|
||||
|
||||
#include "display_hal.h"
|
||||
#include "display_ui.h"
|
||||
|
||||
/* Panel geometry: ST7789 (240x280) vs SH8601 AMOLED (368x448). */
|
||||
#if CONFIG_DISPLAY_PANEL_ST7789
|
||||
#define DISP_H_RES CONFIG_DISPLAY_ST7789_H_RES
|
||||
#define DISP_V_RES CONFIG_DISPLAY_ST7789_V_RES
|
||||
#else
|
||||
#define DISP_H_RES 368
|
||||
#define DISP_V_RES 448
|
||||
#endif
|
||||
|
||||
static const char *TAG = "disp_task";
|
||||
|
||||
|
|
@ -67,6 +74,16 @@ static void lvgl_touch_cb(lv_indev_drv_t *drv, lv_indev_data_t *data)
|
|||
}
|
||||
}
|
||||
|
||||
/* ---- LVGL tick source ----
|
||||
* Kconfig has CONFIG_LV_TICK_CUSTOM unset and nothing calls lv_tick_inc(),
|
||||
* so LVGL's tick never advances and its refresh timer never fires — the panel
|
||||
* draws once and never repaints. This esp_timer drives the tick so LVGL
|
||||
* actually refreshes (works headless; esp_timer runs with no USB host). */
|
||||
static void lvgl_tick_cb(void *arg)
|
||||
{
|
||||
lv_tick_inc(2);
|
||||
}
|
||||
|
||||
/* ---- Display task ---- */
|
||||
static void display_task(void *arg)
|
||||
{
|
||||
|
|
@ -78,9 +95,15 @@ static void display_task(void *arg)
|
|||
display_ui_create(lv_scr_act());
|
||||
|
||||
TickType_t last_wake = xTaskGetTickCount();
|
||||
TickType_t last_heal = last_wake;
|
||||
while (1) {
|
||||
display_ui_update();
|
||||
lv_timer_handler();
|
||||
/* Backlight-only self-heal every ~2s (LEDC, no SPI). */
|
||||
if ((xTaskGetTickCount() - last_heal) >= pdMS_TO_TICKS(2000)) {
|
||||
last_heal = xTaskGetTickCount();
|
||||
display_hal_refresh();
|
||||
}
|
||||
vTaskDelayUntil(&last_wake, frame_period);
|
||||
}
|
||||
}
|
||||
|
|
@ -117,6 +140,19 @@ esp_err_t display_task_start(void)
|
|||
/* Initialize LVGL */
|
||||
lv_init();
|
||||
|
||||
/* Start the LVGL tick (2 ms) — WITHOUT this the display never refreshes. */
|
||||
const esp_timer_create_args_t tick_args = {
|
||||
.callback = &lvgl_tick_cb,
|
||||
.name = "lvgl_tick",
|
||||
};
|
||||
esp_timer_handle_t tick_timer = NULL;
|
||||
if (esp_timer_create(&tick_args, &tick_timer) == ESP_OK &&
|
||||
esp_timer_start_periodic(tick_timer, 2000) == ESP_OK) {
|
||||
ESP_LOGI(TAG, "LVGL tick timer started (2 ms)");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "LVGL tick timer failed — display will not refresh");
|
||||
}
|
||||
|
||||
/* Double-buffered draw buffers — prefer PSRAM, fall back to internal DMA */
|
||||
size_t buf_lines = use_psram ? DISP_BUF_LINES : 10; /* Smaller buffers without PSRAM */
|
||||
size_t buf_size = DISP_H_RES * buf_lines * sizeof(lv_color_t);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,134 @@
|
|||
/**
|
||||
* @file display_ui_st7789.c
|
||||
* @brief ADR-045: compact LVGL UI for the 240x280 ST7789 panel.
|
||||
*
|
||||
* The 4-view AMOLED UI (display_ui.c) is laid out for 368x448 and overflows a
|
||||
* 1.69" LCD. This variant is a single legible screen sized for 240x280: node
|
||||
* identity, a big ACTIVITY bar driven by the CSI motion/presence metrics (so
|
||||
* movement is visible across a room), and a live CSI packet-rate footer.
|
||||
* Selected at build time via CONFIG_DISPLAY_PANEL_ST7789 (CMakeLists compiles
|
||||
* this instead of display_ui.c). Same display_ui.h contract.
|
||||
*/
|
||||
|
||||
#include "display_ui.h"
|
||||
#include "csi_collector.h" /* node id + live CSI packet rate */
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#if CONFIG_DISPLAY_ENABLE && CONFIG_DISPLAY_PANEL_ST7789
|
||||
|
||||
#include <stdio.h>
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_system.h"
|
||||
#include "edge_processing.h"
|
||||
|
||||
static const char *TAG = "disp_ui_st7789";
|
||||
|
||||
#define COLOR_BG lv_color_make(0x0A, 0x0A, 0x0F)
|
||||
#define COLOR_CYAN lv_color_make(0x00, 0xD4, 0xFF)
|
||||
#define COLOR_TEXT lv_color_make(0xCC, 0xCC, 0xDD)
|
||||
#define COLOR_DIM lv_color_make(0x66, 0x66, 0x77)
|
||||
#define COLOR_GREEN lv_color_make(0x00, 0xFF, 0x80)
|
||||
#define COLOR_TRACK lv_color_make(0x1C, 0x1C, 0x26)
|
||||
|
||||
/* Activity = max(motion_energy, presence_score); ~0 idle, climbs past 6 on
|
||||
* movement near the link. Scale x10 to a 0-100 bar (clamped). */
|
||||
#define ACT_SCALE 10.0f
|
||||
|
||||
static lv_obj_t *s_node = NULL;
|
||||
static lv_obj_t *s_act_val = NULL; /* big activity number */
|
||||
static lv_obj_t *s_bar = NULL; /* activity bar */
|
||||
static lv_obj_t *s_foot = NULL; /* CSI rate + RSSI */
|
||||
static lv_obj_t *s_foot2 = NULL; /* uptime + heap */
|
||||
static lv_obj_t *s_hb = NULL; /* heartbeat dot */
|
||||
|
||||
static lv_obj_t *label(lv_obj_t *p, const lv_font_t *font, lv_color_t color,
|
||||
lv_align_t align, int x, int y, const char *text)
|
||||
{
|
||||
lv_obj_t *l = lv_label_create(p);
|
||||
lv_label_set_text(l, text);
|
||||
lv_obj_set_style_text_font(l, font, 0);
|
||||
lv_obj_set_style_text_color(l, color, 0);
|
||||
lv_obj_align(l, align, x, y);
|
||||
return l;
|
||||
}
|
||||
|
||||
void display_ui_create(lv_obj_t *parent)
|
||||
{
|
||||
lv_obj_set_style_bg_color(parent, COLOR_BG, 0);
|
||||
lv_obj_set_style_bg_opa(parent, LV_OPA_COVER, 0);
|
||||
|
||||
label(parent, &lv_font_montserrat_20, COLOR_DIM, LV_ALIGN_TOP_MID, 0, 6, "RuView CSI");
|
||||
s_node = label(parent, &lv_font_montserrat_36, COLOR_CYAN, LV_ALIGN_TOP_MID, 0, 30, "NODE -");
|
||||
s_hb = label(parent, &lv_font_montserrat_20, COLOR_CYAN, LV_ALIGN_TOP_RIGHT, -10, 8, "*");
|
||||
|
||||
label(parent, &lv_font_montserrat_20, COLOR_TEXT, LV_ALIGN_TOP_MID, 0, 92, "ACTIVITY");
|
||||
s_act_val = label(parent, &lv_font_montserrat_36, COLOR_GREEN, LV_ALIGN_TOP_MID, 0, 116, "0");
|
||||
|
||||
s_bar = lv_bar_create(parent);
|
||||
lv_obj_set_size(s_bar, 200, 24);
|
||||
lv_obj_align(s_bar, LV_ALIGN_TOP_MID, 0, 172);
|
||||
lv_bar_set_range(s_bar, 0, 100);
|
||||
lv_bar_set_value(s_bar, 0, LV_ANIM_OFF);
|
||||
lv_obj_set_style_bg_color(s_bar, COLOR_TRACK, LV_PART_MAIN);
|
||||
lv_obj_set_style_bg_color(s_bar, COLOR_GREEN, LV_PART_INDICATOR);
|
||||
lv_obj_set_style_radius(s_bar, 4, LV_PART_MAIN);
|
||||
|
||||
s_foot = label(parent, &lv_font_montserrat_14, COLOR_TEXT, LV_ALIGN_BOTTOM_MID, 0, -26, "CSI --/s RSSI --");
|
||||
s_foot2 = label(parent, &lv_font_montserrat_14, COLOR_DIM, LV_ALIGN_BOTTOM_MID, 0, -6, "up 0h00m heap -- KB");
|
||||
|
||||
ESP_LOGI(TAG, "Compact ST7789 UI created (240x280, activity bar)");
|
||||
}
|
||||
|
||||
void display_ui_update(void)
|
||||
{
|
||||
char buf[48];
|
||||
|
||||
/* Heartbeat — blink ~2 Hz so the UI is visibly alive regardless of data. */
|
||||
static uint32_t s_hb_last = 0;
|
||||
static bool s_hb_on = false;
|
||||
uint32_t now_ms = (uint32_t)(esp_timer_get_time() / 1000);
|
||||
if (now_ms - s_hb_last >= 500) {
|
||||
s_hb_last = now_ms;
|
||||
s_hb_on = !s_hb_on;
|
||||
lv_label_set_text(s_hb, s_hb_on ? "*" : " ");
|
||||
}
|
||||
|
||||
snprintf(buf, sizeof(buf), "NODE %u", (unsigned)csi_collector_get_node_id());
|
||||
lv_label_set_text(s_node, buf);
|
||||
|
||||
uint16_t pps = csi_collector_get_pkt_yield_per_sec();
|
||||
|
||||
edge_vitals_pkt_t v;
|
||||
int rssi = 0;
|
||||
float activity = 0.0f;
|
||||
if (edge_get_vitals(&v)) {
|
||||
rssi = v.rssi;
|
||||
activity = v.motion_energy > v.presence_score ? v.motion_energy : v.presence_score;
|
||||
}
|
||||
|
||||
int act100 = (int)(activity * ACT_SCALE);
|
||||
if (act100 > 100) act100 = 100;
|
||||
if (act100 < 0) act100 = 0;
|
||||
lv_bar_set_value(s_bar, act100, LV_ANIM_OFF);
|
||||
|
||||
snprintf(buf, sizeof(buf), "%d", act100);
|
||||
lv_label_set_text(s_act_val, buf);
|
||||
lv_obj_set_style_text_color(s_act_val, act100 > 10 ? COLOR_GREEN : COLOR_DIM, 0);
|
||||
|
||||
snprintf(buf, sizeof(buf), "CSI %u/s RSSI %d", (unsigned)pps, rssi);
|
||||
lv_label_set_text(s_foot, buf);
|
||||
|
||||
uint32_t up = (uint32_t)(esp_timer_get_time() / 1000000);
|
||||
snprintf(buf, sizeof(buf), "up %luh%02lum heap %luK",
|
||||
(unsigned long)(up / 3600), (unsigned long)((up % 3600) / 60),
|
||||
(unsigned long)(esp_get_free_heap_size() / 1024));
|
||||
lv_label_set_text(s_foot2, buf);
|
||||
|
||||
/* NOTE: no per-loop ESP_LOG here. On the USB-Serial-JTAG console, logging
|
||||
* with no host attached (e.g. running off a wall charger) blocks once the
|
||||
* TX buffer fills — which would hang the display task. Keep this loop
|
||||
* log-free so the panel keeps refreshing headless. */
|
||||
}
|
||||
|
||||
#endif /* CONFIG_DISPLAY_ENABLE && CONFIG_DISPLAY_PANEL_ST7789 */
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# ST7789 display build overlay — Waveshare ESP32-S3-Touch-LCD-1.69.
|
||||
# Merge AFTER sdkconfig.defaults (later file wins):
|
||||
# SDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.st7789"
|
||||
# Keeps the proven CSI-node config; only swaps the display panel HAL.
|
||||
CONFIG_DISPLAY_ENABLE=y
|
||||
CONFIG_DISPLAY_PANEL_ST7789=y
|
||||
|
||||
# LVGL fonts for the compact 240x280 UI (LVGL is Kconfig-configured, not lv_conf.h).
|
||||
CONFIG_LV_FONT_MONTSERRAT_20=y
|
||||
CONFIG_LV_FONT_MONTSERRAT_36=y
|
||||
CONFIG_LV_USE_BAR=y
|
||||
|
||||
# Dimmer backlight = less current draw on the marginal single-USB power path
|
||||
# (helps avoid the startup brownout boot-loop on the LCD board).
|
||||
CONFIG_DISPLAY_BRIGHTNESS=45
|
||||
Loading…
Reference in New Issue