Display UI: ST7735 Treiber + Frame-Scroller + Speicheranzeige
- ST7735 SPI-Treiber für 0.96 Zoll Display (80x160 Pixel) - ASCII-Font 6x8 mit Text-Ausgabe - Frame-Counter (gross, rot) - SD-Speicher-Balken (grün/gelb/rot) - Frame-Scroller: rollende Liste der letzten 7 Frames - RSSI-Farbbalken (rot/gelb/grün) - Message-Type (B=BSM, C=CAM, D=DENM) - BSSID Hex + Kanal/RSSI - Display UI Task aktualisiert alle 2 Sekunden - Frame Writer sendet Frames an Scroller Queue - Channel Hopper aktualisiert g_v2x_channel_idx
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
idf_component_register(
|
||||
SRCS "v2x_driver.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES esp_vfs_fat sdmmc esp_timer driver
|
||||
"v2x_display.c"
|
||||
"v2x_display_text.c"
|
||||
"v2x_display_scroller.c"
|
||||
INCLUDE_DIRS "." ".."
|
||||
REQUIRES esp_vfs_fat sdmmc esp_timer driver esp_lcd esp_lcd_st7735
|
||||
)
|
||||
|
||||
@@ -0,0 +1,258 @@
|
||||
/**
|
||||
* V2X-Display Driver - ST7735 SPI (T-Dongle-C5)
|
||||
*
|
||||
* Display: ST7735, 80x160 Pixel
|
||||
* SPI-Pins: CS=GPIO10, RST=GPIO1, SDA(MOSI)=GPIO2, SCK=GPIO6
|
||||
* LCD-Backlight: GPIO0
|
||||
*
|
||||
* Statusanzeige:
|
||||
* - Oben: SD-Speicher in %, Frame-Counter
|
||||
* - Unten: rollende Liste der letzten X Frames (RSSI-Balken + msg_type)
|
||||
*/
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "v2x_display.h"
|
||||
#include "esp_lcd_panel_ops.h"
|
||||
#include "esp_lcd_panel_vendor.h"
|
||||
#include "esp_lvgl_port.h"
|
||||
#include "driver/gpio.h"
|
||||
#include <stdio.h>
|
||||
|
||||
static const char *TAG = "v2x-display";
|
||||
|
||||
/* ST7735 Kommandos */
|
||||
#define ST7735_NOP 0x00
|
||||
#define ST7735_SWRESET 0x01
|
||||
#define ST7735_SLPIN 0x10
|
||||
#define ST7735_SLPOUT 0x11
|
||||
#define ST7735_PTLON 0x12
|
||||
#define ST7735_NORON 0x13
|
||||
#define ST7735_INVOFF 0x20
|
||||
#define ST7735_INVON 0x21
|
||||
#define ST7735_DIS5520 0x29
|
||||
#define ST7735_MADCTL 0x36
|
||||
#define ST7735_COLMOD 0x3A
|
||||
#define ST7735_CASET 0x2A
|
||||
#define ST7735_RASET 0x2B
|
||||
#define ST7735_RAMWR 0x2C
|
||||
|
||||
static esp_lcd_panel_handle_t s_panel = NULL;
|
||||
static bool s_initialized = false;
|
||||
|
||||
/* ST7735 Init-Sequenz (TFT 0.96 Zoll 80x160, SPI, ROTG) */
|
||||
static const esp_lcd_panel_cmd_init_t st7735_init[] = {
|
||||
{0x11, (uint8_t[]){0}, 0, 120}, /* SWRESET */
|
||||
{0x3A, (uint8_t[]){0x55}, 1, 0}, /* COLMOD=16bit */
|
||||
{0x36, (uint8_t[]){0x08}, 1, 0}, /* MADCTL (portrait) */
|
||||
{0x21, (uint8_t[]){}, 0, 0}, /* INVOFF */
|
||||
{0x29, (uint8_t[]){}, 0, 0}, /* DISON */
|
||||
};
|
||||
|
||||
esp_err_t v2x_display_init(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
|
||||
ESP_LOGI(TAG, "Initialisiere ST7735 Display...");
|
||||
|
||||
/* Backlight an */
|
||||
gpio_config_t bl_conf = {
|
||||
.pin_bit_mask = (1ULL << DISP_GPIO_BL),
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
};
|
||||
gpio_config(&bl_conf);
|
||||
gpio_set_level(DISP_GPIO_BL, 1);
|
||||
|
||||
/* Reset-Pin */
|
||||
gpio_config_t rst_conf = {
|
||||
.pin_bit_mask = (1ULL << DISP_GPIO_RST),
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
};
|
||||
gpio_config(&rst_conf);
|
||||
|
||||
/* SPI für LCD */
|
||||
spi_bus_config_t bus_cfg = {
|
||||
.mosi_io_num = DISP_GPIO_SDA, /* GPIO2 */
|
||||
.miso_io_num = -1, /* LCD braucht kein MISO */
|
||||
.sclk_io_num = DISP_GPIO_SCK, /* GPIO6 */
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1,
|
||||
.max_transfer_sz = DISP_W * DISP_H * sizeof(uint16_t),
|
||||
};
|
||||
|
||||
ret = spi_bus_initialize(SPI3_HOST, &bus_cfg, 1);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "spi_bus_init fehlgeschlagen");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* LCD-Panel konfigurieren */
|
||||
esp_lcd_panel_dev_config_t panel_cfg = {
|
||||
.reset_gpio_num = DISP_GPIO_RST,
|
||||
.color_space = ESP_LCD_COLOR_SPACE_RGB,
|
||||
.bits_per_pixel = 16,
|
||||
};
|
||||
|
||||
ret = esp_lcd_new_panel_st7735(NULL, &panel_cfg, &s_panel);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "ST7735 Panel konnte nicht erstellt werden");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Panel initialisieren */
|
||||
ret = esp_lcd_panel_reset(s_panel);
|
||||
if (ret != ESP_OK) goto err;
|
||||
|
||||
ret = esp_lcd_panel_init(s_panel);
|
||||
if (ret != ESP_OK) goto err;
|
||||
|
||||
ret = esp_lcd_panel_disp_on_off(s_panel, true);
|
||||
if (ret != ESP_OK) goto err;
|
||||
|
||||
/* Komplette Init-Sequenz (falls ST7735 nicht korrekt init) */
|
||||
ret = esp_lcd_panel_cmd_init(s_panel, st7735_init, sizeof(st7735_init)/sizeof(st7735_init[0]));
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "ST7735 Init-Kommandos fehlgeschlagen: %s", esp_err_to_name(ret));
|
||||
}
|
||||
|
||||
s_initialized = true;
|
||||
g_disp_initialized = true;
|
||||
g_disp_panel = s_panel;
|
||||
ESP_LOGI(TAG, "ST7735 Display bereit (80x160, SPI)");
|
||||
return ESP_OK;
|
||||
|
||||
err:
|
||||
ESP_LOGE(TAG, "Display-Init fehlgeschlagen");
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t v2x_display_clear(uint16_t color)
|
||||
{
|
||||
if (!s_initialized) return ESP_ERR_INVALID_STATE;
|
||||
|
||||
esp_lcd_panel_draw_bitmap(s_panel, 0, 0, DISP_W, DISP_H, &color);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t v2x_display_rect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color, bool filled)
|
||||
{
|
||||
if (!s_initialized) return ESP_ERR_INVALID_STATE;
|
||||
|
||||
uint8_t *buf = malloc(w * h * sizeof(uint16_t));
|
||||
if (!buf) return ESP_ERR_NO_MEM;
|
||||
|
||||
for (int i = 0; i < w * h; i++) {
|
||||
((uint16_t *)buf)[i] = color;
|
||||
}
|
||||
|
||||
esp_lcd_panel_draw_bitmap(s_panel, x, y, x + w, y + h, buf);
|
||||
free(buf);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t v2x_display_line(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t color)
|
||||
{
|
||||
/* Einfacher Bresenham-Linienzeiger für 1-Pixel Linien */
|
||||
if (!s_initialized) return ESP_ERR_INVALID_STATE;
|
||||
|
||||
int dx = abs(x1 - x0);
|
||||
int dy = abs(y1 - y0);
|
||||
int sx = (x0 < x1) ? 1 : -1;
|
||||
int sy = (y0 < y1) ? 1 : -1;
|
||||
int err = dx - dy;
|
||||
|
||||
while (true) {
|
||||
esp_lcd_panel_draw_bitmap(s_panel, x0, y0, x0 + 1, y0 + 1, &color);
|
||||
if (x0 == x1 && y0 == y1) break;
|
||||
int e2 = 2 * err;
|
||||
if (e2 > -dy) { err -= dy; x0 += sx; }
|
||||
if (e2 < dx) { err += dx; y0 += sy; }
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t v2x_display_off(void)
|
||||
{
|
||||
if (!s_initialized) return ESP_ERR_INVALID_STATE;
|
||||
|
||||
gpio_set_level(DISP_GPIO_BL, 0);
|
||||
if (s_panel) esp_lcd_panel_disp_on_off(s_panel, false);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* Fortschrittsbalken: progress 0.0 - 1.0 */
|
||||
esp_err_t v2x_display_progress_bar(int16_t x, int16_t y, int16_t w, int16_t h, float progress,
|
||||
uint16_t fg_color, uint16_t bg_color)
|
||||
{
|
||||
progress = (progress < 0.0f) ? 0.0f : (progress > 1.0f) ? 1.0f : progress;
|
||||
|
||||
/* Hintergrund */
|
||||
v2x_display_rect(x, y, w, h, bg_color, true);
|
||||
|
||||
/* Vordergrund */
|
||||
int16_t fw = (int16_t)(w * progress);
|
||||
if (fw > 0) {
|
||||
v2x_display_rect(x, y, fw, h, fg_color, true);
|
||||
}
|
||||
|
||||
/* Rahmen */
|
||||
for (int i = 0; i < w; i++) {
|
||||
v2x_display_line(x + i, y, x + i, y + h, DISP_COLOR_BLACK);
|
||||
}
|
||||
for (int i = 0; i < h; i++) {
|
||||
v2x_display_line(x, y + i, x + w, y + i, DISP_COLOR_BLACK);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* RSSI-Balken: -100 bis -20 dBm → 0-100% */
|
||||
esp_err_t v2x_display_rssi_gauge(int8_t rssi)
|
||||
{
|
||||
if (rssi < -100) rssi = -100;
|
||||
if (rssi > -20) rssi = -20;
|
||||
|
||||
float pct = (float)(rssi + 100) / 80.0f;
|
||||
|
||||
/* Farbe: rot → gelb → grün */
|
||||
uint16_t color = DISP_COLOR_RED;
|
||||
if (pct > 0.6f) color = DISP_COLOR_GREEN;
|
||||
else if (pct > 0.3f) color = DISP_COLOR_YELLOW;
|
||||
|
||||
return v2x_display_progress_bar(0, 0, DISP_W, 3, pct, color, DISP_COLOR_BLACK);
|
||||
}
|
||||
|
||||
/* Frame-Info als Zeile rendern */
|
||||
esp_err_t v2x_display_render_frame_line(int16_t x, int16_t y, const disp_frame_info_t *info,
|
||||
uint16_t bg_color)
|
||||
{
|
||||
/* Background */
|
||||
v2x_display_rect(x, y, DISP_W, 12, bg_color, true);
|
||||
|
||||
/* RSSI-Farbring links (5 Pixel breit) */
|
||||
if (info->rssi != 0) {
|
||||
int8_t r = info->rssi;
|
||||
if (r < -100) r = -100;
|
||||
if (r > -20) r = -20;
|
||||
float pct = (float)(r + 100) / 80.0f;
|
||||
uint16_t bar_color = DISP_COLOR_RED;
|
||||
if (pct > 0.6f) bar_color = DISP_COLOR_GREEN;
|
||||
else if (pct > 0.3f) bar_color = DISP_COLOR_YELLOW;
|
||||
v2x_display_rect(x, y + 1, 5, 10, bar_color, true);
|
||||
}
|
||||
|
||||
/* Message-Typ-Icon */
|
||||
char msg_char = '?';
|
||||
uint16_t msg_color = DISP_COLOR_GRAY;
|
||||
switch (info->msg_type) {
|
||||
case 1: msg_char = 'B'; msg_color = DISP_COLOR_GREEN; break; /* BSM */
|
||||
case 2: msg_char = 'C'; msg_color = DISP_COLOR_CYAN; break; /* CAM */
|
||||
case 3: msg_char = 'D'; msg_color = DISP_COLOR_RED; break; /* DENM */
|
||||
}
|
||||
|
||||
/* Zeichen: msg_char */
|
||||
/* (Wird in Schritt 2: Font-Ausgabe implementiert) */
|
||||
(void)msg_color; (void)x; (void)y;
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* Frame-Scroller — rollende Liste der letzten Frames
|
||||
*
|
||||
* Zeigt bis zu 7 Frames an, jeder Frame = 1 Zeile (12px hoch):
|
||||
* [RSSI-Balk] X #####-#####-##### CH:161 RSSI:-45
|
||||
*
|
||||
* Der Scroller aktualisiert sich nur wenn sich ein Frame ändert.
|
||||
*/
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "v2x_display.h"
|
||||
#include "esp_lcd_panel_ops.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
|
||||
extern esp_lcd_panel_handle_t s_panel;
|
||||
extern bool s_initialized;
|
||||
|
||||
static const char *TAG = "v2x-scroller";
|
||||
|
||||
/* Scroller-Konfiguration */
|
||||
#define SCROLLER_FRAMES_VISIBLE 7 /* Frames sichtbar */
|
||||
#define SCROLLER_LINE_HEIGHT 13 /* Pixel pro Zeile */
|
||||
#define SCROLLER_START_Y 28 /* Erste Zeile beginnt bei Y=28 */
|
||||
#define SCROLLER_MAX_ENTRIES 64 /* Frames im Buffer */
|
||||
|
||||
/* Frame-Daten für den Scroller */
|
||||
typedef struct {
|
||||
char msg_char; /* 'B', 'C', 'D', '?' */
|
||||
int8_t rssi; /* RSSI dBm */
|
||||
uint8_t ch; /* Kanal */
|
||||
char bssid_hex[9]; /* #####-#####-##### */
|
||||
} scroller_entry_t;
|
||||
|
||||
static scroller_entry_t s_entries[SCROLLER_MAX_ENTRIES];
|
||||
static uint32_t s_entry_count = 0;
|
||||
|
||||
/* RSSI-Balk als ASCII (20 Zeichen breit) */
|
||||
static void rssi_to_bar(int8_t rssi, char *bar, int len)
|
||||
{
|
||||
if (rssi <= -100) rssi = -100;
|
||||
if (rssi >= -20) rssi = -20;
|
||||
|
||||
float pct = (float)(rssi + 100) / 80.0f;
|
||||
int filled = (int)(pct * 20);
|
||||
|
||||
for (int i = 0; i < len - 1; i++) {
|
||||
bar[i] = (i < filled) ? '\x5C' : '\xC1'; /* ▌ und ░ (block chars) */
|
||||
}
|
||||
bar[len - 1] = '\0';
|
||||
}
|
||||
|
||||
/* BSSID als #####-#####-##### hex */
|
||||
static void bssid_to_str(const uint8_t *bssid, char *str)
|
||||
{
|
||||
snprintf(str, 14, "%02X-%02X-%02X-%02X-%02X-%02X",
|
||||
bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]);
|
||||
}
|
||||
|
||||
/* Scroller-Eintrag hinzufügen */
|
||||
void scroller_add_entry(uint8_t msg_type, int8_t rssi, uint8_t ch, const uint8_t *bssid)
|
||||
{
|
||||
scroller_entry_t *e = &s_entries[s_entry_count % SCROLLER_MAX_ENTRIES];
|
||||
|
||||
/* Msg-Type Character */
|
||||
switch (msg_type) {
|
||||
case 1: e->msg_char = 'B'; break; /* BSM */
|
||||
case 2: e->msg_char = 'C'; break; /* CAM */
|
||||
case 3: e->msg_char = 'D'; break; /* DENM */
|
||||
default: e->msg_char = '?'; break;
|
||||
}
|
||||
|
||||
e->rssi = rssi;
|
||||
e->ch = ch;
|
||||
if (bssid) {
|
||||
bssid_to_str(bssid, e->bssid_hex);
|
||||
} else {
|
||||
snprintf(e->bssid_hex, 14, "----------");
|
||||
}
|
||||
|
||||
s_entry_count++;
|
||||
}
|
||||
|
||||
/* Scroller neu zeichnen (vollständig) */
|
||||
void scroller_redraw(void)
|
||||
{
|
||||
if (!s_initialized) return;
|
||||
|
||||
int32_t total = s_entry_count;
|
||||
int start = (total > SCROLLER_FRAMES_VISIBLE) ?
|
||||
(total - SCROLLER_FRAMES_VISIBLE) : 0;
|
||||
|
||||
/* Hintergrund zeichnen */
|
||||
int total_h = SCROLLER_FRAMES_VISIBLE * SCROLLER_LINE_HEIGHT;
|
||||
v2x_display_rect(0, SCROLLER_START_Y, DISP_W, total_h, DISP_COLOR_BLACK, true);
|
||||
|
||||
/* Zeilenweise rendern (von oben nach unten = neueste Frames zuerst) */
|
||||
for (int i = SCROLLER_FRAMES_VISIBLE - 1; i >= 0; i--) {
|
||||
int idx = (start + i) % SCROLLER_MAX_ENTRIES;
|
||||
scroller_entry_t *e = &s_entries[idx];
|
||||
int y = SCROLLER_START_Y + (SCROLLER_FRAMES_VISIBLE - 1 - i) * SCROLLER_LINE_HEIGHT;
|
||||
|
||||
/* RSSI-Farbbalk links (5px breit, 11px hoch) */
|
||||
uint16_t bar_color = DISP_COLOR_RED;
|
||||
float pct = (float)(e->rssi + 100) / 80.0f;
|
||||
if (pct > 0.6f) bar_color = DISP_COLOR_GREEN;
|
||||
else if (pct > 0.3f) bar_color = DISP_COLOR_YELLOW;
|
||||
|
||||
v2x_display_rect(2, y + 1, 5, 11, bar_color, true);
|
||||
|
||||
/* Msg-Type Zeichen (grün/cyan/rot/gray) */
|
||||
uint16_t msg_color = DISP_COLOR_GRAY;
|
||||
if (e->msg_char == 'B') msg_color = DISP_COLOR_GREEN;
|
||||
else if (e->msg_char == 'C') msg_color = DISP_COLOR_CYAN;
|
||||
else if (e->msg_char == 'D') msg_color = DISP_COLOR_RED;
|
||||
|
||||
char msg_line[2] = { e->msg_char, '\0' };
|
||||
v2x_display_text(9, y + 1, msg_line, msg_color, FONT_6x8);
|
||||
|
||||
/* BSSID als Hex (blau) */
|
||||
v2x_display_text(16, y + 1, e->bssid_hex, DISP_COLOR_BLUE, FONT_6x8);
|
||||
|
||||
/* Kanal + RSSI (weiß) */
|
||||
char ch_buf[20];
|
||||
snprintf(ch_buf, sizeof(ch_buf), "CH:%d %3d", e->ch, e->rssi);
|
||||
v2x_display_text(60, y + 1, ch_buf, DISP_COLOR_WHITE, FONT_6x8);
|
||||
}
|
||||
}
|
||||
|
||||
/* Geänderte Zeile neu zeichnen (nur die betroffene Zeile — schnell) */
|
||||
void scroller_redraw_line(uint32_t entry_idx)
|
||||
{
|
||||
if (!s_initialized) return;
|
||||
|
||||
int32_t total = s_entry_count;
|
||||
if (entry_idx < (total > SCROLLER_FRAMES_VISIBLE ? total - SCROLLER_FRAMES_VISIBLE : 0)) {
|
||||
return; /* Zu alt, nicht sichtbar */
|
||||
}
|
||||
|
||||
int start = (total > SCROLLER_FRAMES_VISIBLE) ?
|
||||
(total - SCROLLER_FRAMES_VISIBLE) : 0;
|
||||
|
||||
int line_idx = (int)(entry_idx - start);
|
||||
if (line_idx < 0 || line_idx >= SCROLLER_FRAMES_VISIBLE) return;
|
||||
|
||||
int idx = entry_idx % SCROLLER_MAX_ENTRIES;
|
||||
scroller_entry_t *e = &s_entries[idx];
|
||||
int y = SCROLLER_START_Y + (SCROLLER_FRAMES_VISIBLE - 1 - line_idx) * SCROLLER_LINE_HEIGHT;
|
||||
|
||||
/* Zeile löschen */
|
||||
v2x_display_rect(0, y, DISP_W, SCROLLER_LINE_HEIGHT, DISP_COLOR_BLACK, true);
|
||||
|
||||
/* Neu zeichnen */
|
||||
uint16_t bar_color = DISP_COLOR_RED;
|
||||
float pct = (float)(e->rssi + 100) / 80.0f;
|
||||
if (pct > 0.6f) bar_color = DISP_COLOR_GREEN;
|
||||
else if (pct > 0.3f) bar_color = DISP_COLOR_YELLOW;
|
||||
|
||||
v2x_display_rect(2, y + 1, 5, 11, bar_color, true);
|
||||
|
||||
uint16_t msg_color = DISP_COLOR_GRAY;
|
||||
if (e->msg_char == 'B') msg_color = DISP_COLOR_GREEN;
|
||||
else if (e->msg_char == 'C') msg_color = DISP_COLOR_CYAN;
|
||||
else if (e->msg_char == 'D') msg_color = DISP_COLOR_RED;
|
||||
|
||||
char msg_line[2] = { e->msg_char, '\0' };
|
||||
v2x_display_text(9, y + 1, msg_line, msg_color, FONT_6x8);
|
||||
|
||||
v2x_display_text(16, y + 1, e->bssid_hex, DISP_COLOR_BLUE, FONT_6x8);
|
||||
|
||||
char ch_buf[20];
|
||||
snprintf(ch_buf, sizeof(ch_buf), "CH:%d %3d", e->ch, e->rssi);
|
||||
v2x_display_text(60, y + 1, ch_buf, DISP_COLOR_WHITE, FONT_6x8);
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
/**
|
||||
* ASCII-Font (6x8 Pixel) + Text-Ausgabe
|
||||
*/
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "v2x_display.h"
|
||||
#include "esp_lcd_panel_ops.h"
|
||||
|
||||
extern esp_lcd_panel_handle_t s_panel;
|
||||
extern bool s_initialized;
|
||||
|
||||
static const uint8_t font6x8[][6] = {
|
||||
{0x00,0x00,0x00,0x00,0x00,0x00}, // space
|
||||
{0x00,0x00,0x5F,0x00,0x00,0x00}, // !
|
||||
{0x00,0x07,0x00,0x07,0x00,0x00}, // "
|
||||
{0x14,0x7F,0x14,0x7F,0x14,0x00}, // #
|
||||
{0x24,0x2A,0x7F,0x2A,0x12,0x00}, // $
|
||||
{0x23,0x13,0x08,0x64,0x62,0x00}, // %
|
||||
{0x36,0x49,0x55,0x22,0x50,0x00}, // &
|
||||
{0x00,0x05,0x03,0x00,0x00,0x00}, // '
|
||||
{0x00,0x1C,0x22,0x41,0x00,0x00}, // (
|
||||
{0x00,0x41,0x22,0x1C,0x00,0x00}, // )
|
||||
{0x14,0x08,0x3E,0x08,0x14,0x00}, // *
|
||||
{0x08,0x08,0x3E,0x08,0x08,0x00}, // +
|
||||
{0x00,0x50,0x30,0x00,0x00,0x00}, // ,
|
||||
{0x08,0x08,0x08,0x08,0x08,0x00}, // -
|
||||
{0x00,0x60,0x60,0x00,0x00,0x00}, // .
|
||||
{0x20,0x10,0x08,0x04,0x02,0x00}, // /
|
||||
{0x3E,0x51,0x49,0x45,0x3E,0x00}, // 0
|
||||
{0x00,0x42,0x7F,0x40,0x00,0x00}, // 1
|
||||
{0x42,0x61,0x51,0x49,0x46,0x00}, // 2
|
||||
{0x21,0x41,0x45,0x4B,0x31,0x00}, // 3
|
||||
{0x18,0x14,0x12,0x7F,0x10,0x00}, // 4
|
||||
{0x27,0x45,0x45,0x45,0x39,0x00}, // 5
|
||||
{0x3C,0x4A,0x49,0x49,0x30,0x00}, // 6
|
||||
{0x01,0x71,0x09,0x05,0x03,0x00}, // 7
|
||||
{0x36,0x49,0x49,0x49,0x36,0x00}, // 8
|
||||
{0x06,0x49,0x49,0x29,0x1E,0x00}, // 9
|
||||
{0x00,0x36,0x36,0x00,0x00,0x00}, // :
|
||||
{0x00,0x56,0x36,0x00,0x00,0x00}, // ;
|
||||
{0x08,0x14,0x22,0x41,0x00,0x00}, // <
|
||||
{0x14,0x14,0x14,0x14,0x14,0x00}, // =
|
||||
{0x00,0x41,0x22,0x14,0x08,0x00}, // >
|
||||
{0x02,0x01,0x51,0x09,0x06,0x00}, // ?
|
||||
{0x3E,0x41,0x5D,0x55,0x1E,0x00}, // A
|
||||
{0x7F,0x09,0x09,0x09,0x7E,0x00}, // B
|
||||
{0x7F,0x49,0x49,0x49,0x36,0x00}, // C
|
||||
{0x3E,0x41,0x41,0x21,0x1E,0x00}, // D
|
||||
{0x7F,0x49,0x49,0x49,0x41,0x00}, // E
|
||||
{0x7F,0x49,0x49,0x45,0x43,0x00}, // F
|
||||
{0x7F,0x49,0x49,0x49,0x38,0x00}, // G
|
||||
{0x7F,0x08,0x08,0x08,0x7F,0x00}, // H
|
||||
{0x00,0x41,0x7F,0x41,0x00,0x00}, // I
|
||||
{0x20,0x40,0x41,0x3F,0x01,0x00}, // J
|
||||
{0x7F,0x08,0x14,0x22,0x41,0x00}, // K
|
||||
{0x7F,0x40,0x40,0x40,0x40,0x00}, // L
|
||||
{0x7F,0x02,0x0C,0x02,0x7F,0x00}, // M
|
||||
{0x7F,0x04,0x08,0x10,0x7F,0x00}, // N
|
||||
{0x3E,0x41,0x41,0x41,0x3E,0x00}, // O
|
||||
{0x7F,0x09,0x09,0x09,0x06,0x00}, // P
|
||||
{0x3E,0x41,0x51,0x21,0x5E,0x00}, // Q
|
||||
{0x7F,0x09,0x19,0x29,0x46,0x00}, // R
|
||||
{0x46,0x49,0x49,0x49,0x31,0x00}, // S
|
||||
{0x01,0x01,0x7F,0x01,0x01,0x00}, // T
|
||||
{0x3F,0x40,0x40,0x40,0x3F,0x00}, // U
|
||||
{0x1F,0x20,0x40,0x20,0x1F,0x00}, // V
|
||||
{0x3F,0x40,0x38,0x40,0x3F,0x00}, // W
|
||||
{0x63,0x14,0x08,0x14,0x63,0x00}, // X
|
||||
{0x07,0x08,0x70,0x08,0x07,0x00}, // Y
|
||||
{0x61,0x51,0x49,0x45,0x43,0x00}, // Z
|
||||
{0x00,0x7F,0x41,0x41,0x00,0x00}, // [
|
||||
{0x55,0x2A,0x55,0x2A,0x55,0x00}, // \
|
||||
{0x00,0x41,0x41,0x7F,0x00,0x00}, // ]
|
||||
{0x04,0x02,0x01,0x02,0x04,0x00}, // ^
|
||||
{0x40,0x40,0x40,0x40,0x40,0x00}, // _
|
||||
{0x00,0x01,0x02,0x04,0x00,0x00}, // `
|
||||
{0x00,0x20,0x3E,0x21,0x3E,0x00}, // a
|
||||
{0x7F,0x28,0x44,0x44,0x38,0x00}, // b
|
||||
{0x38,0x44,0x44,0x44,0x20,0x00}, // c
|
||||
{0x38,0x44,0x44,0x28,0x7F,0x00}, // d
|
||||
{0x38,0x54,0x54,0x54,0x18,0x00}, // e
|
||||
{0x08,0x7E,0x09,0x01,0x02,0x00}, // f
|
||||
{0x0C,0x52,0x52,0x52,0x3E,0x00}, // g
|
||||
{0x7F,0x08,0x04,0x04,0x78,0x00}, // h
|
||||
{0x00,0x44,0x7D,0x40,0x00,0x00}, // i
|
||||
{0x20,0x40,0x44,0x3D,0x00,0x00}, // j
|
||||
{0x7F,0x10,0x28,0x44,0x00,0x00}, // k
|
||||
{0x00,0x41,0x7F,0x40,0x00,0x00}, // l
|
||||
{0x7C,0x04,0x18,0x04,0x78,0x00}, // m
|
||||
{0x7C,0x08,0x04,0x04,0x78,0x00}, // n
|
||||
{0x38,0x44,0x44,0x44,0x38,0x00}, // o
|
||||
{0x7C,0x14,0x14,0x14,0x08,0x00}, // p
|
||||
{0x08,0x14,0x14,0x18,0x7C,0x00}, // q
|
||||
{0x7F,0x08,0x04,0x04,0x08,0x00}, // r
|
||||
{0x48,0x54,0x54,0x54,0x20,0x00}, // s
|
||||
{0x04,0x3F,0x44,0x40,0x20,0x00}, // t
|
||||
{0x3C,0x40,0x40,0x20,0x7C,0x00}, // u
|
||||
{0x1C,0x20,0x40,0x20,0x1C,0x00}, // v
|
||||
{0x3C,0x40,0x30,0x40,0x3C,0x00}, // w
|
||||
{0x44,0x28,0x10,0x28,0x44,0x00}, // x
|
||||
{0x0C,0x50,0x50,0x50,0x3C,0x00}, // y
|
||||
{0x44,0x64,0x54,0x4C,0x44,0x00}, // z
|
||||
{0x00,0x08,0x36,0x41,0x00,0x00}, // {
|
||||
{0x00,0x00,0x7F,0x00,0x00,0x00}, // |
|
||||
{0x00,0x41,0x36,0x08,0x00,0x00}, // }
|
||||
{0x10,0x08,0x08,0x10,0x08,0x00}, // ~
|
||||
};
|
||||
|
||||
esp_err_t v2x_display_text(int16_t x, int16_t y, const char *text,
|
||||
uint16_t color, disp_font_t font)
|
||||
{
|
||||
if (!s_initialized) return ESP_ERR_INVALID_STATE;
|
||||
|
||||
uint8_t char_bytes[6];
|
||||
int16_t tx = x;
|
||||
|
||||
for (int ci = 0; text[ci] && tx < DISP_W; ci++) {
|
||||
int idx = text[ci] - FONT_START;
|
||||
if (idx < 0 || idx >= 95) continue;
|
||||
|
||||
memcpy(char_bytes, font6x8[idx], 6);
|
||||
|
||||
for (int c = 0; c < 6; c++) {
|
||||
uint16_t row = char_bytes[c];
|
||||
for (int r = 0; r < 8; r++) {
|
||||
if (row & 0x01) {
|
||||
uint16_t pixel = color;
|
||||
int px = tx + c;
|
||||
int py = y + r;
|
||||
if (px >= 0 && px < DISP_W && py >= 0 && py < DISP_H) {
|
||||
esp_lcd_panel_draw_bitmap(s_panel, px, py, px + 1, py + 1, &pixel);
|
||||
}
|
||||
}
|
||||
row >>= 1;
|
||||
}
|
||||
}
|
||||
tx += 7;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t v2x_display_frame_count(uint32_t count)
|
||||
{
|
||||
if (!s_initialized) return ESP_ERR_INVALID_STATE;
|
||||
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "%lu", (unsigned long)count);
|
||||
int len = strlen(buf);
|
||||
int x = (DISP_W - len * 7) / 2;
|
||||
|
||||
uint8_t char_bytes[6];
|
||||
for (int ci = 0; ci < len; ci++) {
|
||||
int idx = buf[ci] - FONT_START;
|
||||
if (idx < 0 || idx >= 95) continue;
|
||||
|
||||
memcpy(char_bytes, font6x8[idx], 6);
|
||||
|
||||
for (int c = 0; c < 6; c++) {
|
||||
uint16_t row = char_bytes[c];
|
||||
for (int r = 0; r < 8; r++) {
|
||||
if (row & 0x01) {
|
||||
uint16_t pixel = DISP_COLOR_RED;
|
||||
int px = x + ci * 18 + c * 3;
|
||||
for (int dy = 0; dy < 3; dy++) {
|
||||
for (int dx = 0; dx < 3; dx++) {
|
||||
int py = y + r * 3 + dy;
|
||||
if (px + dx >= 0 && px + dx < DISP_W && py < DISP_H) {
|
||||
esp_lcd_panel_draw_bitmap(s_panel, px + dx, py, px + dx + 1, py + 1, &pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
row >>= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t v2x_display_storage_bar(int16_t x, int16_t y, int16_t w, int16_t h, float pct)
|
||||
{
|
||||
pct = (pct < 0.0f) ? 0.0f : (pct > 1.0f) ? 1.0f : pct;
|
||||
v2x_display_rect(x, y, w, h, DISP_COLOR_GRAY, true);
|
||||
|
||||
uint16_t fg = DISP_COLOR_GREEN;
|
||||
if (pct < 0.3f) fg = DISP_COLOR_RED;
|
||||
else if (pct < 0.6f) fg = DISP_COLOR_YELLOW;
|
||||
|
||||
int16_t fw = (int16_t)(w * pct);
|
||||
v2x_display_rect(x, y, fw, h, fg, true);
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Display UI Task — aktualisiert SD-Speicher und Frame-Counter
|
||||
*
|
||||
* Wird als FreeRTOS-Task gestartet und alle 2 Sekunden:
|
||||
* - SD-Speicher % berechnen + Balken zeichnen
|
||||
* - Frame-Counter groß anzeigen
|
||||
* - Scroller aktualisieren
|
||||
*/
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "v2x_display.h"
|
||||
#include "esp_vfs_fat.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "stdio.h"
|
||||
|
||||
static const char *TAG = "v2x-ui";
|
||||
|
||||
/* SD-Speicher % berechnen */
|
||||
static float get_sd_storage_percent(void)
|
||||
{
|
||||
FATFS *fs;
|
||||
DWORD free_clusters;
|
||||
|
||||
esp_err_t ret = f_getfree(SD_CARD_MOUNT_POINT, &free_clusters, &fs);
|
||||
if (ret != FR_OK) return 0.0f;
|
||||
|
||||
/* Gesamt-Cluster zählen */
|
||||
uint32_t total = fs->n_fatent - 12; /* FAT32: Cluster ab 12 */
|
||||
if (total == 0) return 0.0f;
|
||||
|
||||
return (float)(total - free_clusters) / total;
|
||||
}
|
||||
|
||||
/* Frame-Counter als extern (wird vom Writer Task gesetzt) */
|
||||
extern uint32_t g_v2x_frame_count;
|
||||
|
||||
/* Display UI Task */
|
||||
void v2x_display_ui_task(void *pvParameters)
|
||||
{
|
||||
uint32_t last_count = 0;
|
||||
|
||||
ESP_LOGI(TAG, "Display UI Task gestartet");
|
||||
|
||||
while (1) {
|
||||
/* --- Display löschen (schwarzer Hintergrund) --- */
|
||||
v2x_display_clear(DISP_COLOR_BLACK);
|
||||
|
||||
/* --- SD-Speicher-Balken (Y=0, Höhe=8px) --- */
|
||||
float sd_pct = get_sd_storage_percent();
|
||||
v2x_display_storage_bar(0, 0, DISP_W, 8, sd_pct);
|
||||
|
||||
/* Text: "SD: ##%" */
|
||||
char sd_label[16];
|
||||
snprintf(sd_label, sizeof(sd_label), "SD: %3.0f%%", sd_pct * 100);
|
||||
v2x_display_text(2, 9, sd_label, DISP_COLOR_WHITE, FONT_6x8);
|
||||
|
||||
/* --- Frame-Counter (groß, rot, Y=20) --- */
|
||||
uint32_t current_count = g_v2x_frame_count;
|
||||
if (current_count != last_count) {
|
||||
v2x_display_frame_count(current_count, 28, DISP_W, 40);
|
||||
last_count = current_count;
|
||||
}
|
||||
|
||||
/* --- RSSI-Anzeige (Y=72) --- */
|
||||
v2x_display_text(2, 72, "RSSI:", DISP_COLOR_YELLOW, FONT_6x8);
|
||||
char rssi_label[20];
|
||||
snprintf(rssi_label, sizeof(rssi_label), "Max: %3d dBm", g_v2x_max_rssi);
|
||||
v2x_display_text(40, 72, rssi_label, DISP_COLOR_WHITE, FONT_6x8);
|
||||
|
||||
/* --- Scroller (Y=84) --- */
|
||||
scroller_redraw();
|
||||
|
||||
/* --- Statistik-Balken (Y=132) --- */
|
||||
v2x_display_text(2, 132, "CH:", DISP_COLOR_GREEN, FONT_6x8);
|
||||
|
||||
/* Kanal + Frequenz */
|
||||
extern uint32_t g_v2x_current_freq;
|
||||
extern int g_v2x_channel_idx;
|
||||
char ch_info[20];
|
||||
snprintf(ch_info, sizeof(ch_info), "CH:%d %.0fMHz",
|
||||
g_v2x_channel_idx, g_v2x_current_freq / 1000.0f);
|
||||
v2x_display_text(20, 132, ch_info, DISP_COLOR_CYAN, FONT_6x8);
|
||||
|
||||
/* BSSID-Farbcodes */
|
||||
v2x_display_text(2, 148, "B=BSM C=CAM D=DENM", DISP_COLOR_GRAY, FONT_6x8);
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(2000));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user