Files
v2x-sniffer/v2x_fw/components/v2x_driver/v2x_display.c
T
Clawdia 240a374148 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
2026-05-09 11:47:55 +02:00

259 lines
7.6 KiB
C

/**
* 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;
}