dc32-retro-tech-addon/firmware/retro_tech_fw/user/src/ui.c

580 lines
19 KiB
C
Raw Normal View History

/*
* Created on: Jul 27, 2024
*
* super basic interface for the buttons
* renderer for the cursor (always 50% duty cycle)
* interface for the rgb programs
*/
#include <stdint.h>
#include "adc.h"
#include "btn.h"
#include "config.h"
#include "led.h"
#include "led_rgbprog.h"
#include "rand.h"
#define MODE_RUN 0
#define MODE_PROGRAM 1
#define MODE_PARAMETER 2
#define MIN_GC 2
#define MAX_GC 26
#define UI_CONF_SAVE_TIMEOUT 160
#define UI_PROG_RUNTIME_MIN (128*15) // 15 seconds
#define UI_PROG_RUNTIME_MAX (128*120) // 120 seconds
static uint8_t mode = MODE_RUN;
static uint8_t tick = 0;
static uint16_t editor_timeout_timer = 0;
static uint8_t target_gc = 0;
static uint8_t cursor_flash = 0;
static uint8_t cursor_state = 0xff;
const uint16_t cursor_flash_rates[8] = { // off-to-on flash rates in 1/256 second increments
255, // (0.5 on-off/second)
191, // (0.75 on-off/second)
127, // (1.0 on-off/second)
85, // C64 rate (1.5 on-off/second)
68, // Windows default (530ms on-off)
41, // PC MDA (3.114 on-off/second)
34, // PC CGA/EGA (3.745 on-off/second)
29, // PC VGA text mode (4.38 on-off/second)
};
static uint8_t rgb_prog_idx = 0; // currently running rgbled program index
2024-08-02 17:33:02 -07:00
static uint16_t rgb_prog_timer = 9; // timeout until this program is done and switches
static uint8_t rgb_prog_is_editing = 0; // currently editing a program's parameters
static uint8_t preview_idx = 0; // currently selected program preview index
static uint8_t config_save_timer;
void ui_btn_push_cb(uint8_t idx)
{
uint8_t i;
// are both buttons pushed?
if ((btn[0]._mask & BTN_PUSH) && (btn[1]._mask & BTN_PUSH)) {
// are none held?
if (!(btn[0]._mask & BTN_HOLD) && !(btn[1]._mask & BTN_HOLD)) {
// can only enter programming mode if the cursor is flashing
if (userconf.cursor_color != CONF_CURSOR_OFF) {
// put both buttons into ignore mode, so hold and release events
// do not fire
btn[0]._mask |= BTN_IGNORE;
btn[1]._mask |= BTN_IGNORE;
// both buttons are pushed at the same time quickly, and not held
// this will toggle modes
mode++;
if (mode > MODE_PARAMETER) {
mode = MODE_RUN;
}
// reset any LED program
led_rgb_firstrun();
// configure the preview index to be the first available
// depending on the mode
if (mode == MODE_PROGRAM) preview_idx = 0;
if (mode == MODE_PARAMETER) {
preview_idx = 0;
for (i = 0; i < 5; i++) {
if (userconf.ledprog_ena_mask & (1 << preview_idx)) {
preview_idx = i;
break;
}
}
}
return;
}
}
}
// do normal button actions
switch (mode) {
/* button logic flow:
*
*
* mode changes happen by pressing both buttons.
*
*
* NORMAL MODE: cursor is flashing.
*
* cursor changes are committed to EEPROM a couple of seconds after changing is done.
*
* if cursor is off, programming mode cannot be entered.
*
* - top button: changes cursor color (white, green, orange, off)
* - bot button: changes cursor flash rate. press and hold while not off to
* enter program change mode.
*/
/*
* PROGRAM CHANGE MODE: cursor is off.
*
*
* letters in RETRO show enabled programs. by default, none are enabled.
* enabled programs are bright. disabled ones are dim.
* selected program will flash. if on, will flash brightly. if off, it will flash dimly.
*
* any program which is enabled can run. the active program randomly changes after random delays.
*
* letters in TECH preview the currently selected program.
* letters in RETRO will each preview their own program (any flash / twinkle will always have idle light)
*
* - top button: tap enables or disables the program. push and hold does nothing.
* - bot button: tap selects the next program index. push and hold enters parameter change mode.
*
*
* PARAMETER CHANGE MODE: cursor is on.
*
* letters in RETRO flash to show currently selected program.
* selected program will flash. if a program has been disabled, nothing will light for that letter.
*
* letters in TECH preview the current program, at some fixed brightness.
*
* - top button: increment some parameter, with loop (speed, etc)
* - bot button: selects the next program
* - knob: changes some parameter (color/hue, etc)
*
* settings are saved when the cursor returns to flashing from any other mode.
*
* settings are restored to default if the top button is held while powering on.
* restored settings are not committed until entering programming mode
*/
}
}
void ui_btn_hold_cb(uint8_t idx)
{
}
void ui_btn_release_cb(uint8_t idx)
{
uint8_t i;
uint8_t w;
switch (mode) {
/* button logic flow:
*
*
* mode changes happen by pressing both buttons.
*/
/*
* NORMAL MODE: cursor is flashing (or off if disabled).
*
* cursor changes are committed to EEPROM about a second after changing is done.
*
* if cursor is off, programming mode cannot be entered.
*
* any program which is enabled will run. the active program randomly changes after random delays.
*/
case MODE_RUN: {
switch (idx) {
case 0: { // - top button: changes cursor color (white, green, orange, off)
userconf.cursor_color++;
userconf.cursor_color &= 0x3;
config_save_timer = UI_CONF_SAVE_TIMEOUT;
break;
}
case 1: { // - bot button: changes cursor flash rate. press and hold
// while cursor is not off to enter program change mode.
userconf.cursor_flash++;
userconf.cursor_flash &= 0x7;
config_save_timer = UI_CONF_SAVE_TIMEOUT;
break;
}
}
break;
}
/*
* PROGRAM CHANGE MODE: cursor is on.
*
* letters in RETRO show programs. by default, all are disabled.
* enabled programs are bright. disabled programs are dim.
* selected program will flash. if program is on, will flash brightly. if off, it will flash dimly.
*
* letters in TECH preview the currently selected program.
* letters in RETRO will each preview their own program (any flash / twinkle will always have idle light)
*/
case MODE_PROGRAM: {
switch (idx) {
case 0: { // - top button: tap enables or disables the program. push and hold does nothing.
w = userconf.ledprog_ena_mask;
if (userconf.ledprog_ena_mask & (1 << preview_idx)) {
userconf.ledprog_ena_mask &= ~(1 << preview_idx);
} else {
userconf.ledprog_ena_mask |= (1 << preview_idx);
}
break;
}
case 1: { // - bot button: tap selects the next program index. push and hold does nothing.
preview_idx++;
if (preview_idx >= 5) preview_idx = 0;
break;
}
}
break;
}
/*
* PARAMETER CHANGE MODE: cursor is off (or rather, very dimly on).
*
* letters in RETRO only show enabled programs. dark for disabled programs.
* RETRO letters will flash to indicate currently selected program.
*
* letters in TECH preview the current program, at some fixed brightness.
*
*
*
* - knob: changes some parameter (color/hue, etc)
*/
case MODE_PARAMETER: {
switch (idx) {
case 0: { // - top button: tapped or held: increment some parameter, with loop (speed, etc)
userconf.ledprog_setting[preview_idx][0]++;
break;
}
case 1: { // - bot button: when editing disabled: selects the next program
// when editing enabled: decrements some parameter, with loop (speed, etc)
// held: enables / disables editing the program (done in other callback)
if (rgb_prog_is_editing) {
if (userconf.ledprog_setting[preview_idx][0]) {
userconf.ledprog_setting[preview_idx][0]--;
} else {
userconf.ledprog_setting[preview_idx][0] = 0xff;
}
} else {
// select new program, except the last program, since that doesn't have a user editable config
w = preview_idx;
for (i = 0; i < 4; i++) {
w++;
if (w > 4) w = 0;
if (userconf.ledprog_ena_mask & (1 << w)) {
preview_idx = w;
break;
}
}
}
break;
}
}
break;
}
}
}
static void ui_cursor_flash()
{
uint8_t color, flash;
uint8_t level = 0;
color = userconf.cursor_color;
flash = userconf.cursor_flash;
switch (mode) {
case MODE_RUN: {
if (rgb_prog_idx != 4) {
// initial setup
if (cursor_state == 0xff) {
cursor_state = 0;
goto ui_cursor_flash_setup;
}
// wind down counter
cursor_flash--;
// set brightness of led, if we're in an on state
if (cursor_state && (color < CONF_CURSOR_OFF)) {
// first frame of on = not quite full brightness
level = (cursor_flash == cursor_flash_rates[flash] - 1) ? 160 : 255;
// at final frames, dim
if (cursor_flash < 4) {
level >>= (cursor_flash << 1);
}
2024-08-02 17:33:02 -07:00
}
2024-08-02 17:33:02 -07:00
// set the level on the cursor
if (cursor[color] != level) {
cursor[color] = led_gamma(level);
2024-08-02 17:33:02 -07:00
led_is_updated();
}
if (!cursor_flash) {
// toggle on/off
cursor_state++;
cursor_state &= 1;
ui_cursor_flash_setup:
// set new cursor rate
cursor_flash = cursor_flash_rates[flash] >> 1;
// set all colors off
cursor[0] = cursor[1] = cursor[2] = 0;
}
}
break;
}
case MODE_PROGRAM: {
// cursor is always on bright
cursor[color] = 255;
break;
}
case MODE_PARAMETER: {
// cursor is always on dim
cursor[color] = 16;
break;
}
}
}
void ui_init()
{
btn[0].hold = 330 >> 1;
btn[0].repeat = 0; // (1000 / 20) >> 1;
btn[0].cb_push = ui_btn_push_cb;
btn[0].cb_hold = ui_btn_hold_cb;
btn[0].cb_release = ui_btn_release_cb;
btn[1].hold = 330 >> 1;
btn[1].repeat = 0;
btn[1].cb_push = ui_btn_push_cb;
btn[1].cb_hold = ui_btn_hold_cb;
btn[1].cb_release = ui_btn_release_cb;
}
void ui_render()
{
uint8_t i;
uint16_t w;
uint32_t t;
uint8_t flash;
// deal with eeprom
if (config_save_timer) {
config_save_timer--;
if (config_save_timer) {
userconf_save();
}
}
// flash rate at 2 times/second
flash = tick >> 5;
flash &= 0x3;
if (editor_timeout_timer) {
editor_timeout_timer--;
if (!editor_timeout_timer) {
mode = MODE_RUN;
// save settings to eeprom
userconf_save();
}
}
switch (mode) {
case MODE_RUN: { // render an existing program
tick++;
// set brightness from knob 32 times/second
// (the knob value updates less frequently, but we need to fade nicely)
if (tick & 7 == 7) {
w = adc_get_pot();
if (w < 2) w = 0;
else {
w *= MAX_GC - MIN_GC;
w >>= 8;
w += MIN_GC;
}
if (!w) {
if (target_gc) target_gc--; // full off = full off.
} else if (w < target_gc) {
if (target_gc > MIN_GC) target_gc--; // dim to min possible
} else if (w > target_gc) {
if (target_gc < MAX_GC) target_gc++; // dim to max possible
}
is31fl3729_set_global_current(FL3729_ADDR, target_gc);
}
2024-08-02 17:33:02 -07:00
if (userconf.ledprog_ena_mask) {
// fade and change programs depending on the timer
rgb_prog_timer--;
if (rgb_prog_timer <= 17) {
if (rgb_prog_timer > 9) {
// fade out current program
w = (rgb_prog_timer & 0x1f) - 10;
for (i = 0; i < 9; i++) {
rgb[i][0] >>= w;
rgb[i][1] >>= w;
rgb[i][2] >>= w;
}
} else if (rgb_prog_timer > 7) {
// select a new random program
if (rgb_prog_timer == 8) {
w = prng_get8();
w &= 0x3;
if (w == rgb_prog_idx) w++;
for (i = 0; i < 7; i++) {
if (userconf.ledprog_ena_mask & (1 << ((w + i) & 0x7))) {
rgb_prog_idx = w;
break;
}
}
led_rgb_firstrun();
}
// clear out for now, since new program hasn't actually been run
for (i = 0; i < 9; i++) {
rgb[i][0] = 0;
rgb[i][1] = 0;
rgb[i][2] = 0;
}
} else if (rgb_prog_timer >= 1) {
// fade in new program
w = 8 - (rgb_prog_timer & 0x1f);
for (i = 0; i < 9; i++) {
rgb[i][0] >>= w;
rgb[i][1] >>= w;
rgb[i][2] >>= w;
}
} else { // 0
// randomize next program timing
t = prng_get16();
t *= (UI_PROG_RUNTIME_MAX - UI_PROG_RUNTIME_MIN);
t >>= 16;
rgb_prog_timer = t + UI_PROG_RUNTIME_MIN;
}
// actually run the program
2024-08-02 18:49:47 -07:00
led_rgbprog[rgb_prog_idx](LED_RGBPROG_NORMAL, tick);
}
}
2024-08-02 17:33:02 -07:00
// temp: remove me once buttons are tested and working
2024-08-02 18:49:47 -07:00
rgb_prog_idx = 4;
led_rgbprog[rgb_prog_idx](LED_RGBPROG_NORMAL, tick);
2024-08-02 17:33:02 -07:00
break;
}
case MODE_PROGRAM: {
// always postpone config saving
config_save_timer = UI_CONF_SAVE_TIMEOUT;
// always increase brightness of LEDs while editing
is31fl3729_set_global_current(FL3729_ADDR, FL3729_GCC_MAX);
// always force rendering
led_is_updated();
for (i = 0; i < 5; i++) {
// render
led_rgbprog[i](LED_RGBPROG_PREVIEW | preview_idx, tick);
if (preview_idx == i) {
// flash the selected output
if (flash & 1) {
rgb[i][0] >>= 5;
rgb[i][1] >>= 5;
rgb[i][2] >>= 5;
}
} else {
// dim inactive outputs
if (!(userconf.ledprog_ena_mask & (1 << i))) {
rgb[i][0] >>= 3;
rgb[i][1] >>= 3;
rgb[i][2] >>= 3;
}
}
}
break;
}
case MODE_PARAMETER: {
// always postpone config saving
config_save_timer = UI_CONF_SAVE_TIMEOUT;
// always increase brightness of LEDs while editing
is31fl3729_set_global_current(FL3729_ADDR, FL3729_GCC_MAX);
// always force rendering
led_is_updated();
// deal with knob if editing is active
if (rgb_prog_is_editing) {
userconf.ledprog_setting[preview_idx][1] = adc_get_pot();
}
for (i = 0; i < 5; i++) {
// render
if (userconf.ledprog_ena_mask & (1 << i)) {
led_rgbprog[i](LED_RGBPROG_PREVIEW | preview_idx, tick);
if (preview_idx == i) {
// flash the selected output
if (flash & 1) {
rgb[i][0] >>= 5;
rgb[i][1] >>= 5;
rgb[i][2] >>= 5;
}
} else {
// dim the other outputs
rgb[i][0] >>= 2;
rgb[i][1] >>= 2;
rgb[i][2] >>= 2;
}
} else {
// clear the output if it isn't enabled
rgb[i][0] = 0;
rgb[i][1] = 0;
rgb[i][2] = 0;
}
}
break;
}
}
ui_cursor_flash();
}