mirror of
https://gitlab.com/zephray/glider.git
synced 2025-12-30 12:47:27 +00:00
346 lines
10 KiB
C
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));
|
|
}
|
|
}
|