mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-10-23 05:11:24 +00:00
Merge branch 'master' into dev
This commit is contained in:
commit
6ef03fb10e
29 files changed
+1886
-65
No files matched your search
@@ -970,6 +970,14 @@ static const uint8_t cfb_font_0608[224][6] = {
|
||||
{ 0x0C, 0x51, 0x50, 0x51, 0x3C, 0x00 },
|
||||
};
|
||||
|
||||
const uint8_t *zephcore_font_6x8_glyph(uint8_t c)
|
||||
{
|
||||
if (c < 32) {
|
||||
c = '?';
|
||||
}
|
||||
return cfb_font_0608[c - 32];
|
||||
}
|
||||
|
||||
FONT_ENTRY_DEFINE(cfb_font_6x8,
|
||||
6, 8,
|
||||
CFB_FONT_MONO_VPACKED,
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <zephyr/drivers/display.h>
|
||||
#include <zephyr/drivers/regulator.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/sys/byteorder.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <limits.h>
|
||||
@@ -54,6 +55,39 @@ static uint16_t disp_height;
|
||||
static uint8_t font_w;
|
||||
static uint8_t font_h;
|
||||
static bool is_epd; /* true for e-paper displays */
|
||||
static bool has_color; /* true when a raw RGB565 TFT is available */
|
||||
|
||||
const uint8_t *zephcore_font_6x8_glyph(uint8_t c);
|
||||
|
||||
#define COLOR_TEXT_MAX_CHARS 32
|
||||
#define COLOR_MAX_OPS 72
|
||||
#define COLOR_MAX_WIDTH 320
|
||||
|
||||
enum color_op_type {
|
||||
COLOR_OP_TEXT,
|
||||
COLOR_OP_RECT,
|
||||
};
|
||||
|
||||
struct color_op {
|
||||
enum color_op_type type;
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
int16_t w;
|
||||
int16_t h;
|
||||
uint16_t color;
|
||||
char text[COLOR_TEXT_MAX_CHARS];
|
||||
};
|
||||
|
||||
static struct color_op color_ops[COLOR_MAX_OPS];
|
||||
static uint8_t color_op_count;
|
||||
static uint16_t color_line[COLOR_MAX_WIDTH];
|
||||
|
||||
#if DT_NODE_EXISTS(DT_NODELABEL(tft))
|
||||
static const struct device *color_dev =
|
||||
DEVICE_DT_GET_OR_NULL(DT_NODELABEL(tft));
|
||||
#else
|
||||
static const struct device *color_dev;
|
||||
#endif
|
||||
|
||||
/* Optional symmetric inset (pixels). Shrinks reported width/height and
|
||||
* offsets all draw primitives so panels with edge artefacts can hide them
|
||||
@@ -131,6 +165,159 @@ static inline void panel_vdd_enable(void)
|
||||
}
|
||||
}
|
||||
|
||||
static void color_overlay_probe(void)
|
||||
{
|
||||
has_color = false;
|
||||
|
||||
if (!color_dev || color_dev == disp_dev || !device_is_ready(color_dev)) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct display_capabilities caps;
|
||||
|
||||
display_get_capabilities(color_dev, &caps);
|
||||
if ((caps.current_pixel_format == PIXEL_FORMAT_RGB_565 ||
|
||||
caps.current_pixel_format == PIXEL_FORMAT_RGB_565X ||
|
||||
(caps.supported_pixel_formats & (PIXEL_FORMAT_RGB_565 | PIXEL_FORMAT_RGB_565X))) &&
|
||||
!(caps.screen_info & SCREEN_INFO_EPD)) {
|
||||
has_color = true;
|
||||
LOG_INF("display: color overlay enabled");
|
||||
}
|
||||
}
|
||||
|
||||
static bool color_queue(enum color_op_type type, int x, int y, int w, int h,
|
||||
const char *text, uint16_t color)
|
||||
{
|
||||
if (!has_color || color_op_count >= COLOR_MAX_OPS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct color_op *op = &color_ops[color_op_count++];
|
||||
|
||||
op->type = type;
|
||||
op->x = (int16_t)x;
|
||||
op->y = (int16_t)y;
|
||||
op->w = (int16_t)w;
|
||||
op->h = (int16_t)h;
|
||||
op->color = color;
|
||||
op->text[0] = '\0';
|
||||
if (text) {
|
||||
strncpy(op->text, text, sizeof(op->text) - 1);
|
||||
op->text[sizeof(op->text) - 1] = '\0';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void color_write_rect_now(int x, int y, int w, int h, uint16_t color)
|
||||
{
|
||||
if (!has_color || w <= 0 || h <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
x += DISP_INSET;
|
||||
y += DISP_INSET;
|
||||
if (x < 0) {
|
||||
w += x;
|
||||
x = 0;
|
||||
}
|
||||
if (y < 0) {
|
||||
h += y;
|
||||
y = 0;
|
||||
}
|
||||
if (x + w > (int)disp_width) {
|
||||
w = (int)disp_width - x;
|
||||
}
|
||||
if (y + h > (int)disp_height) {
|
||||
h = (int)disp_height - y;
|
||||
}
|
||||
if (w <= 0 || h <= 0) {
|
||||
return;
|
||||
}
|
||||
if (w > COLOR_MAX_WIDTH) {
|
||||
w = COLOR_MAX_WIDTH;
|
||||
}
|
||||
|
||||
uint16_t be = sys_cpu_to_be16(color);
|
||||
for (int i = 0; i < w; i++) {
|
||||
color_line[i] = be;
|
||||
}
|
||||
|
||||
const struct display_buffer_descriptor desc = {
|
||||
.buf_size = (uint32_t)w * 2U,
|
||||
.width = (uint16_t)w,
|
||||
.height = 1U,
|
||||
.pitch = (uint16_t)w,
|
||||
};
|
||||
|
||||
for (int row = 0; row < h; row++) {
|
||||
display_write(color_dev, (uint16_t)x, (uint16_t)(y + row),
|
||||
&desc, color_line);
|
||||
}
|
||||
}
|
||||
|
||||
static void color_write_char_now(int x, int y, uint8_t c, uint16_t color)
|
||||
{
|
||||
uint16_t glyph_buf[6 * 8];
|
||||
const uint8_t *glyph = zephcore_font_6x8_glyph(c);
|
||||
uint16_t fg = sys_cpu_to_be16(color);
|
||||
uint16_t bg = sys_cpu_to_be16(MC_COLOR_BLACK);
|
||||
|
||||
for (int row = 0; row < 8; row++) {
|
||||
for (int col = 0; col < 6; col++) {
|
||||
bool on = (glyph[col] >> row) & 0x01;
|
||||
glyph_buf[row * 6 + col] = on ? fg : bg;
|
||||
}
|
||||
}
|
||||
|
||||
const struct display_buffer_descriptor desc = {
|
||||
.buf_size = sizeof(glyph_buf),
|
||||
.width = 6,
|
||||
.height = 8,
|
||||
.pitch = 6,
|
||||
};
|
||||
|
||||
display_write(color_dev, (uint16_t)(x + DISP_INSET),
|
||||
(uint16_t)(y + DISP_INSET), &desc, glyph_buf);
|
||||
}
|
||||
|
||||
static void color_write_text_now(int x, int y, const char *text, uint16_t color)
|
||||
{
|
||||
if (!has_color || !text) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const char *p = text; *p; p++, x += 6) {
|
||||
uint8_t c = (uint8_t)*p;
|
||||
|
||||
if (c < 32) {
|
||||
c = '?';
|
||||
}
|
||||
if (x + 6 > (int)mc_display_width() || y + 8 > (int)mc_display_height()) {
|
||||
break;
|
||||
}
|
||||
color_write_char_now(x, y, c, color);
|
||||
}
|
||||
}
|
||||
|
||||
static void color_flush_ops(void)
|
||||
{
|
||||
if (!has_color) {
|
||||
color_op_count = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < color_op_count; i++) {
|
||||
struct color_op *op = &color_ops[i];
|
||||
|
||||
if (op->type == COLOR_OP_RECT) {
|
||||
color_write_rect_now(op->x, op->y, op->w, op->h, op->color);
|
||||
} else {
|
||||
color_write_text_now(op->x, op->y, op->text, op->color);
|
||||
}
|
||||
}
|
||||
color_op_count = 0;
|
||||
}
|
||||
|
||||
/* Auto-off work */
|
||||
static struct k_work_delayable auto_off_work;
|
||||
|
||||
@@ -228,6 +415,7 @@ int mc_display_init(void)
|
||||
|
||||
LOG_INF("display: %ux%u%s", disp_width, disp_height,
|
||||
is_epd ? " (e-paper)" : "");
|
||||
color_overlay_probe();
|
||||
|
||||
/* OLED: blank before CFB init so stale VRAM isn't visible while we
|
||||
* build the first frame. EPD: driver init already performed a clean
|
||||
@@ -395,12 +583,18 @@ bool mc_display_is_epd(void)
|
||||
return is_epd;
|
||||
}
|
||||
|
||||
bool mc_display_has_color(void)
|
||||
{
|
||||
return has_color;
|
||||
}
|
||||
|
||||
void mc_display_clear(void)
|
||||
{
|
||||
if (!disp_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
color_op_count = 0;
|
||||
if (is_epd) {
|
||||
epd_frame_hash = 2166136261u;
|
||||
}
|
||||
@@ -430,6 +624,17 @@ void mc_display_text(int x, int y, const char *text, bool invert)
|
||||
}
|
||||
}
|
||||
|
||||
void mc_display_color_text(int x, int y, const char *text, uint16_t color)
|
||||
{
|
||||
if (!disp_initialized || !text) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!color_queue(COLOR_OP_TEXT, x, y, 0, 0, text, color)) {
|
||||
mc_display_text(x, y, text, false);
|
||||
}
|
||||
}
|
||||
|
||||
void mc_display_fill_rect(int x, int y, int w, int h)
|
||||
{
|
||||
if (!disp_initialized) {
|
||||
@@ -452,6 +657,17 @@ void mc_display_fill_rect(int x, int y, int w, int h)
|
||||
}
|
||||
}
|
||||
|
||||
void mc_display_color_fill_rect(int x, int y, int w, int h, uint16_t color)
|
||||
{
|
||||
if (!disp_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!color_queue(COLOR_OP_RECT, x, y, w, h, NULL, color)) {
|
||||
mc_display_fill_rect(x, y, w, h);
|
||||
}
|
||||
}
|
||||
|
||||
void mc_display_hline(int x, int y, int w)
|
||||
{
|
||||
if (!disp_initialized) {
|
||||
@@ -564,6 +780,7 @@ void mc_display_finalize(void)
|
||||
}
|
||||
|
||||
cfb_framebuffer_finalize(disp_dev);
|
||||
color_flush_ops();
|
||||
|
||||
if (is_epd) {
|
||||
epd_last_frame_hash = epd_frame_hash;
|
||||
|
||||
@@ -83,6 +83,12 @@ bool mc_display_is_on(void);
|
||||
*/
|
||||
bool mc_display_is_epd(void);
|
||||
|
||||
/**
|
||||
* @return true when a raw RGB565-capable color panel is available for
|
||||
* optional color overlays. Monochrome displays return false.
|
||||
*/
|
||||
bool mc_display_has_color(void);
|
||||
|
||||
/**
|
||||
* Clear the framebuffer (fill with black).
|
||||
* Call before rendering a new frame.
|
||||
@@ -99,6 +105,25 @@ void mc_display_clear(void);
|
||||
*/
|
||||
void mc_display_text(int x, int y, const char *text, bool invert);
|
||||
|
||||
/* Common RGB565 colors for optional color-capable pages. */
|
||||
#define MC_COLOR_BLACK 0x0000
|
||||
#define MC_COLOR_WHITE 0xffff
|
||||
#define MC_COLOR_GREEN 0x07e0
|
||||
#define MC_COLOR_CYAN 0x07ff
|
||||
#define MC_COLOR_YELLOW 0xffe0
|
||||
#define MC_COLOR_ORANGE 0xfd20
|
||||
#define MC_COLOR_RED 0xf800
|
||||
#define MC_COLOR_BLUE 0x001f
|
||||
#define MC_COLOR_GRAY 0x8410
|
||||
|
||||
/**
|
||||
* Draw text using RGB565 color when supported. On non-color displays this
|
||||
* falls back to mc_display_text(..., invert=false).
|
||||
*
|
||||
* Color overlays are flushed after the normal CFB frame in mc_display_finalize().
|
||||
*/
|
||||
void mc_display_color_text(int x, int y, const char *text, uint16_t color);
|
||||
|
||||
/**
|
||||
* Draw a filled rectangle.
|
||||
*
|
||||
@@ -109,6 +134,12 @@ void mc_display_text(int x, int y, const char *text, bool invert);
|
||||
*/
|
||||
void mc_display_fill_rect(int x, int y, int w, int h);
|
||||
|
||||
/**
|
||||
* Draw a filled rectangle using RGB565 color when supported. On non-color
|
||||
* displays this falls back to mc_display_fill_rect().
|
||||
*/
|
||||
void mc_display_color_fill_rect(int x, int y, int w, int h, uint16_t color);
|
||||
|
||||
/**
|
||||
* Draw a horizontal line.
|
||||
*/
|
||||
|
||||
@@ -49,6 +49,25 @@ WEAK void ui_set_radio_params(uint32_t freq_hz, uint8_t sf,
|
||||
ARG_UNUSED(cr); ARG_UNUSED(tx_power); ARG_UNUSED(noise_floor);
|
||||
}
|
||||
|
||||
WEAK void ui_set_radio_runtime(int8_t effective_tx_power, bool apc_enabled,
|
||||
int8_t apc_reduction, int16_t apc_margin_x10,
|
||||
uint8_t apc_target_margin, uint8_t sync_word,
|
||||
uint16_t preamble_len, bool rx_duty_cycle,
|
||||
bool radio_ready, bool in_rx, bool tx_active)
|
||||
{
|
||||
ARG_UNUSED(effective_tx_power); ARG_UNUSED(apc_enabled);
|
||||
ARG_UNUSED(apc_reduction); ARG_UNUSED(apc_margin_x10);
|
||||
ARG_UNUSED(apc_target_margin); ARG_UNUSED(sync_word);
|
||||
ARG_UNUSED(preamble_len); ARG_UNUSED(rx_duty_cycle);
|
||||
ARG_UNUSED(radio_ready); ARG_UNUSED(in_rx); ARG_UNUSED(tx_active);
|
||||
}
|
||||
|
||||
WEAK void ui_set_radio_stats(uint32_t packets_rx, uint32_t packets_tx,
|
||||
uint32_t packets_err)
|
||||
{
|
||||
ARG_UNUSED(packets_rx); ARG_UNUSED(packets_tx); ARG_UNUSED(packets_err);
|
||||
}
|
||||
|
||||
WEAK void ui_set_gps_data(bool has_fix, uint8_t sats,
|
||||
int32_t lat_mdeg, int32_t lon_mdeg, int32_t alt_mm)
|
||||
{
|
||||
|
||||
@@ -334,13 +334,38 @@ extern "C" void mesh_housekeeping_ui_refresh(void)
|
||||
ui_set_clock(s_rtc_clock->getCurrentTime());
|
||||
|
||||
ui_set_radio_params(
|
||||
(uint32_t)(s_mesh->prefs.freq * 1000000.0f + 0.5f),
|
||||
s_mesh->prefs.sf,
|
||||
(uint16_t)(s_mesh->prefs.bw * 10.0f + 0.5f),
|
||||
s_mesh->prefs.cr,
|
||||
s_mesh->prefs.tx_power_dbm,
|
||||
s_lora_radio->getActiveFrequencyHz(),
|
||||
s_lora_radio->getActiveSpreadingFactor(),
|
||||
s_lora_radio->getActiveBandwidthKHzX10(),
|
||||
s_lora_radio->getActiveCodingRate(),
|
||||
s_lora_radio->getConfiguredTxPower(),
|
||||
s_lora_radio->getNoiseFloor());
|
||||
ui_notify_radio_stats(
|
||||
{
|
||||
bool apc_enabled = false;
|
||||
int8_t apc_reduction = 0;
|
||||
int16_t apc_margin_x10 = 0;
|
||||
uint8_t apc_target = s_mesh->prefs.apc_margin;
|
||||
|
||||
#ifdef CONFIG_ZEPHCORE_APC
|
||||
apc_enabled = s_mesh->isAPCEnabled();
|
||||
apc_reduction = s_mesh->getAPCReduction();
|
||||
apc_margin_x10 = (int16_t)(s_mesh->getAPCMargin() * 10.0f);
|
||||
apc_target = s_mesh->getAPCTargetMargin();
|
||||
#endif
|
||||
ui_set_radio_runtime(
|
||||
s_lora_radio->getEffectiveTxPower(),
|
||||
apc_enabled,
|
||||
apc_reduction,
|
||||
apc_margin_x10,
|
||||
apc_target,
|
||||
s_lora_radio->getActiveSyncWord(),
|
||||
s_lora_radio->getActivePreambleLength(),
|
||||
s_lora_radio->isRxDutyCycleEnabled(),
|
||||
s_lora_radio->isRadioReady(),
|
||||
s_lora_radio->isInRecvMode(),
|
||||
s_lora_radio->isTxActive());
|
||||
}
|
||||
ui_set_radio_stats(
|
||||
s_lora_radio->getPacketsRecv(),
|
||||
s_lora_radio->getPacketsSent(),
|
||||
s_lora_radio->getPacketsRecvErrors());
|
||||
|
||||
@@ -84,6 +84,21 @@ void ui_set_ble_status(bool connected, const char *name);
|
||||
void ui_set_radio_params(uint32_t freq_hz, uint8_t sf, uint16_t bw_khz_x10,
|
||||
uint8_t cr, int8_t tx_power, int16_t noise_floor);
|
||||
|
||||
/**
|
||||
* Update extended live radio/APC details for display.
|
||||
*/
|
||||
void ui_set_radio_runtime(int8_t effective_tx_power, bool apc_enabled,
|
||||
int8_t apc_reduction, int16_t apc_margin_x10,
|
||||
uint8_t apc_target_margin, uint8_t sync_word,
|
||||
uint16_t preamble_len, bool rx_duty_cycle,
|
||||
bool radio_ready, bool in_rx, bool tx_active);
|
||||
|
||||
/**
|
||||
* Update radio packet counters for display.
|
||||
*/
|
||||
void ui_set_radio_stats(uint32_t packets_rx, uint32_t packets_tx,
|
||||
uint32_t packets_err);
|
||||
|
||||
/**
|
||||
* Update GPS data for display.
|
||||
*/
|
||||
|
||||
Reference in new issue
Block a user