5
mirror of https://gitlab.com/zephray/glider.git synced 2025-12-30 12:47:27 +00:00
glider/fw/User/ui.c
2025-12-27 16:15:26 +08:00

346 lines
10 KiB
C

//
// Grimoire
// Copyright 2025 Wenting Zhang
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#include "platform.h"
#include "board.h"
#include "app.h"
#include "ui.h"
static QueueHandle_t btn_queue;
typedef enum {
BTN1_SHORT_PRESSED,
BTN1_LONG_PRESSED,
BTN2_SHORT_PRESSED,
BTN2_LONG_PRESSED,
BTN3_SHORT_PRESSED,
BTN3_LONG_PRESSED
} btn_event_t;
static int mode = 0;
typedef struct {
update_mode_t id;
char *name;
} update_mode_item_t;
const update_mode_item_t modes[] = {
{UM_FAST_MONO_BAYER, "Browsing"},
{UM_FAST_MONO_BLUE_NOISE, "Watching"},
{UM_FAST_GREY, "Typing"},
{UM_AUTO_LUT_NO_DITHER, "Reading"},
};
static int mode_max = sizeof(modes) / sizeof(modes[0]);
#define OSD_WIDTH 256
#define OSD_HEIGHT 128
static uint8_t osd_fb[OSD_WIDTH * OSD_HEIGHT / 8];
void ui_init(void) {
btn_queue = xQueueCreate(8, sizeof(btn_event_t));
}
static void osd_set_pixel(int x, int y, bool p) {
if (x >= OSD_WIDTH)
return;
if (y >= OSD_HEIGHT)
return;
if (x < 0)
return;
if (y < 0)
return;
int x_byte = x / 8;
int x_bit = x % 8;
if (p)
osd_fb[y * OSD_WIDTH / 8 + x_byte] |= (0x80 >> x_bit);
else
osd_fb[y * OSD_WIDTH / 8 + x_byte] &= ~(0x80 >> x_bit);
}
static int osd_draw_char(font_t *font, int x, int y, uint32_t chr, bool fg) {
uint32_t bw = font->buf_width;
uint32_t dw = font->disp_width;
uint32_t h = font->height;
uint32_t font_byte_per_chr = font->buf_width * font->height / 8;
const uint8_t *gbuf;
chr = chr - font->offset;
if (chr > font->chars)
return 0;
gbuf = font->font + chr * font_byte_per_chr;
for (uint32_t yy = 0; yy < h; yy++) {
uint8_t byte = 0; // not necessary, to silence warning
for (uint32_t xx = 0; xx < dw; xx++) {
if (xx % 8 == 0) {
byte = gbuf[yy * bw / 8 + xx / 8];
}
else {
byte >>= 1;
}
if (byte & 0x1) {
osd_set_pixel(x + xx, y + yy, fg);
}
else {
osd_set_pixel(x + xx, y + yy, !fg);
}
}
}
return dw;
}
static void osd_draw_string(font_t *font, int x, int y, char *string, uint32_t maxlen, bool fg) {
char c;
uint32_t i = 0;
uint32_t xx = x;
while ((i++ < maxlen) && (c = *string++)) {
xx += osd_draw_char(font, xx, y, c, fg);
}
}
static void osd_clear(uint8_t c) {
memset(osd_fb, c, OSD_WIDTH * OSD_HEIGHT / 8);
}
static font_t *load_font(const char *fn) {
SPIFFS_clearerr(&spiffs_fs);
spiffs_file f = SPIFFS_open(&spiffs_fs, fn, SPIFFS_O_RDONLY, 0);
if (SPIFFS_errno(&spiffs_fs) != 0)
return NULL;
spiffs_stat s;
SPIFFS_fstat(&spiffs_fs, f, &s);
uint32_t size = s.size;
uint8_t *buf = pvPortMalloc(size);
if (!buf) {
SPIFFS_close(&spiffs_fs, f);
return NULL;
}
SPIFFS_read(&spiffs_fs, f, buf, size);
SPIFFS_close(&spiffs_fs, f);
return (font_t *)buf;
}
static bool is_tmds_active(void) {
uint8_t val;
val = adv7611_read_reg(HDMI_I2C_ADDR, 0x04);
return !!(val & 0x2);
}
static bool is_dp_active(void) {
return dp_ready;
}
static void restart_fpga(void) {
fpga_init("fpga.bit");
syslog_printf("Waiting for video signal to lock");
while (true) {
// Wait for PLL to lock
vTaskDelay(pdMS_TO_TICKS(100));
uint8_t result = fpga_write_reg8(CSR_ID0, 0x00);
if (result == 0x35) {
break;
}
}
syslog_printf("FPGA up");
}
portTASK_FUNCTION(ui_task, pvParameters) {
TickType_t osd_timeout = 0;
bool setmode = false;
TickType_t autoclear_timeout = 0;
bool autoclear = false;
// Load font into memory
font_t *font_24x40 = load_font("font_24x40.bin");
//font_t *font_32x53 = load_font("font_32x53.bin");
// First wait link establish
bool tmds_mode = false;
if (config.input_sel == INPUT_SEL_DP) {
tmds_mode = false;
syslog_print("Forced to use DP input");
}
else if (config.input_sel == INPUT_SEL_TMDS) {
tmds_mode = true;
syslog_print("Forced to use TMDS input");
}
else {
while (1) {
// Check is DP on
if (is_tmds_active()) {
tmds_mode = true;
break;
}
else if (is_dp_active()) {
tmds_mode = false;
break;
}
vTaskDelay(pdMS_TO_TICKS(100)); // Wait before next iteration
}
}
if (tmds_mode)
ptn3460_powerdown();
else
adv7611_powerdown();
restart_fpga();
power_on_epd();
caster_init(); // Start refresh
syslog_printf("FPGA started with status %02x", fpga_write_reg8(CSR_STATUS, 0x00));
while (1) {
// Check OSD timeout
if ((osd_timeout != 0) && (((int32_t)xTaskGetTickCount() - (int32_t)osd_timeout) >= 0)) {
osd_timeout = 0;
caster_osd_set_enable(false);
}
// Update auto clear
if ((autoclear_timeout != 0) && (((int32_t)xTaskGetTickCount() - (int32_t)autoclear_timeout) >= 0)) {
caster_redraw(0,0,2400,1800);
autoclear_timeout = 0; // Pending reset
}
if (autoclear && (autoclear_timeout == 0)) {
autoclear_timeout = xTaskGetTickCount() + pdMS_TO_TICKS(60000);
}
if (!autoclear) {
autoclear_timeout = 0;
}
// Detect loss of signal / lost access to FPGA
if ((tmds_mode && (!is_tmds_active())) || (fpga_write_reg8(CSR_ID0, 0x00) != 0x35)) {
// Stop and restart
power_off_epd();
NVIC_SystemReset();
}
// Detect signal mode
// TODO: This should be implemented in FPGA
#if 0
if (tmds_mode) {
uint16_t x, y;
x = (uint16_t)(adv7611_read_reg(HDMI_I2C_ADDR, 0x07) & 0x1f) << 8;
x |= adv7611_read_reg(HDMI_I2C_ADDR, 0x08);
y = (uint16_t)(adv7611_read_reg(HDMI_I2C_ADDR, 0x09) & 0x1f) << 8;
y |= adv7611_read_reg(HDMI_I2C_ADDR, 0x0a);
if ((x != config.hact) || (y != config.vact)) {
fatal("Incorrect input resolution, detected %d x %d.", x, y);
}
}
#endif
// Key press logic
btn_event_t btn_event;
BaseType_t result = xQueueReceive(btn_queue, &btn_event, pdMS_TO_TICKS(200));
if (result != pdTRUE)
continue;
if (btn_event == BTN1_SHORT_PRESSED) {
// First key short press
mode--;
if (mode < 0) mode = mode_max - 1;
setmode = true;
}
else if (btn_event == BTN2_SHORT_PRESSED) {
mode++;
if (mode >= mode_max) mode = 0;
setmode = true;
}
else if (btn_event == BTN3_SHORT_PRESSED) {
// Clear screen
caster_redraw(0,0,2400,1800);
}
else if (btn_event == BTN3_LONG_PRESSED) {
// Toggle auto clearing
autoclear = !autoclear;
osd_timeout = xTaskGetTickCount() + pdMS_TO_TICKS(2000);
osd_clear(0xff);
osd_draw_string(font_24x40, 10, 10, "Auto Clear", 10, false);
osd_draw_string(font_24x40, 10, 60, autoclear ? "ON" : "OFF", 4, false);
caster_osd_send_buf(osd_fb);
caster_osd_set_enable(true);
}
else if (btn_event == BTN1_LONG_PRESSED) {
osd_clear(0xff);
osd_draw_string(font_24x40, 10, 10, "Turned Off", 10, false);
caster_osd_send_buf(osd_fb);
caster_redraw_blank();
caster_osd_set_enable(true);
sleep_ms(200);
caster_redraw(0,0,2400,1800);
sleep_ms(600);
power_off();
}
if (setmode) {
caster_setmode(0, 0, config.hact, config.vact, modes[mode].id);
osd_timeout = xTaskGetTickCount() + pdMS_TO_TICKS(2000);
osd_clear(0xff);
osd_draw_string(font_24x40, 10, 10, "Mode:", 5, false);
osd_draw_string(font_24x40, 10, 60, modes[mode].name, 8, false);
caster_osd_send_buf(osd_fb);
caster_osd_set_enable(true);
setmode = false;
}
}
}
portTASK_FUNCTION(key_scan_task, pvParameters) {
while (1) {
uint32_t scan = button_scan();
btn_event_t event;
// No wait, if the ui task doesn't take it, the key press is lost
if ((scan & BTN_MASK) == BTN_SHORT_PRESSED) {
event = BTN1_SHORT_PRESSED;
xQueueSend(btn_queue, &event, 0);
}
else if ((scan & BTN_MASK) == BTN_LONG_PRESSED) {
event = BTN1_LONG_PRESSED;
xQueueSend(btn_queue, &event, 0);
}
if (((scan >> BTN_SHIFT) & BTN_MASK) == BTN_SHORT_PRESSED) {
event = BTN2_SHORT_PRESSED;
xQueueSend(btn_queue, &event, 0);
}
else if (((scan >> BTN_SHIFT) & BTN_MASK) == BTN_LONG_PRESSED) {
event = BTN2_LONG_PRESSED;
xQueueSend(btn_queue, &event, 0);
}
if (((scan >> (BTN_SHIFT * 2)) & BTN_MASK) == BTN_SHORT_PRESSED) {
event = BTN3_SHORT_PRESSED;
xQueueSend(btn_queue, &event, 0);
}
else if (((scan >> (BTN_SHIFT * 2)) & BTN_MASK) == BTN_LONG_PRESSED) {
event = BTN3_LONG_PRESSED;
xQueueSend(btn_queue, &event, 0);
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}