dc32-retro-tech-addon/firmware/retro_tech_fw/user/src/ui.c
true 218819f75f fixed led flashing in programming mode being fucked
don't write code when sleep deprived you make stupid mistakes
2024-08-08 03:51:24 -07:00

628 lines
20 KiB
C

/*
* 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 192
#define UI_PROG_RUNTIME_MIN (15) // 15 seconds
#define UI_PROG_RUNTIME_MAX (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 = 0;
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 = 0xff; // currently running rgbled program index
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, w;
// 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++;
// parameter mode only works if a program is enabled to adjust
if (mode == MODE_PARAMETER) {
if (!userconf.ledprog_ena_mask) mode = MODE_RUN;
}
// parameter mode is the last mode
if (mode > MODE_PARAMETER) {
mode = MODE_RUN;
}
// ensure a valid program is selected
if (mode == MODE_PARAMETER) {
w = preview_idx;
for (i = 0; i < 8; i++) {
w &= 0x7;
if (userconf.ledprog_ena_mask & (1 << w)) {
preview_idx = w;
break;
}
w++;
}
}
// 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;
}
}
}
// there are no on-push events.
switch (mode) {
case MODE_RUN: {
}
}
}
void ui_btn_hold_cb(uint8_t idx)
{
switch (mode) {
case MODE_PARAMETER: {
switch (idx) {
case 0: {
if (rgb_prog_is_editing) {
userconf.ledprog_setting[preview_idx][0]++;
}
}
}
}
}
}
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.
* this is handled in the push event handler.
*
* if cursor is off, mode changing cannot occur.
*/
/*
* NORMAL MODE: cursor is flashing (or off if disabled).
*
* cursor changes are committed to EEPROM about a second after changing is done.
*
* 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;
// force cursor to change immediately, force cursor on
cursor_flash = 0;
cursor_state = 0;
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.
*
* by default, all are disabled. selected program will flash.
* cursor will light on the selected program if it is enabled.
*
* 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.
*
* - top button: change some parameter
* - knob: change some parameter (color/hue, etc)
*/
case MODE_PARAMETER: {
switch (idx) {
case 0: { // - top button: when editing disabled: selects the next program
// when editing enabled: tapped or held: increments a value, with loop
if (rgb_prog_is_editing) {
userconf.ledprog_setting[preview_idx][0]++;
} else {
w = preview_idx;
for (i = 0; i < 8; i++) {
w++;
w &= 0x7;
if (userconf.ledprog_ena_mask & (1 << w)) {
preview_idx = w;
break;
}
}
}
break;
}
case 1: { // - bot button: enables / disables editing of selected program
rgb_prog_is_editing = !rgb_prog_is_editing;
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) {
if (!cursor_flash) {
// toggle on/off
cursor_state++;
cursor_state &= 1;
// set new cursor rate, force cursor on
cursor_flash = cursor_flash_rates[flash] >> 1;
// set all colors off
cursor[0] = cursor[1] = cursor[2] = 0;
}
// wind down counter
cursor_flash--;
// set brightness of led, if we're in an on state
if (color < CONF_CURSOR_OFF) {
if (cursor_state) {
// 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 >>= (4 - cursor_flash);
}
}
// set the level on the cursor
if (cursor[color] != level) {
cursor[color] = led_gamma(level);
led_is_updated();
}
} else {
// clear any cursors that may have been left on
cursor[0] = 0;
cursor[1] = 0;
cursor[2] = 0;
}
}
break;
}
case MODE_PROGRAM: {
// cursor is on if this program is flagged as on
cursor[0] = (userconf.ledprog_ena_mask & (1 << preview_idx)) ? 127 : 0;
cursor[1] = 0;
cursor[2] = 0;
break;
}
case MODE_PARAMETER: {
// cursor is on when program is being edited
cursor[0] = rgb_prog_is_editing ? 127 : 0;
cursor[1] = 0;
cursor[2] = 0;
break;
}
}
}
void ui_init()
{
btn[0].hold = 420 >> 1;
btn[0].repeat = (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 = 420 >> 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;
}
static void rgb_prog_random_timer_generate() {
uint32_t t;
t = prng_get16();
t *= (UI_PROG_RUNTIME_MAX - UI_PROG_RUNTIME_MIN) * 32;
t >>= 16;
t += UI_PROG_RUNTIME_MIN * 32;
rgb_prog_timer = t;
}
static uint8_t rgb_prog_random_select()
{
uint8_t i;
uint8_t w;
uint8_t new;
w = prng_get8();
w &= 0x7;
for (i = 0; i < 8; i++) {
new = (w + i) & 0x7;
if (userconf.ledprog_ena_mask & (1 << new)) {
// bias selecting a new program
if (rgb_prog_idx != new) {
rgb_prog_idx = new;
led_rgb_firstrun();
break;
}
}
}
if (i == 8) return 0;
return 1;
}
void ui_render()
{
uint8_t i;
uint16_t w;
uint8_t flash;
uint8_t out[16];
// 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();
}
}
tick++;
switch (mode) {
case MODE_RUN: { // render an existing program
// ensure rear LED is not on
GPIOD->BCR = GPIO_Pin_0;
// 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);
}
if (userconf.ledprog_ena_mask) {
// fade and change programs depending on the timer
// actually run the program
if (rgb_prog_timer) {
if (led_rgbprog[rgb_prog_idx] && (rgb_prog_idx < LED_RGBPROG_COUNT)) {
led_rgbprog[rgb_prog_idx](LED_RGBPROG_NORMAL, tick);
}
}
if ((tick & 0x3) == 0) {
rgb_prog_timer--;
if (rgb_prog_timer <= 17) {
led_is_updated();
if (rgb_prog_timer == 17) {
// if no new program was selected (likely only one program selected?)
// just reset the timer
if (!rgb_prog_random_select()) {
rgb_prog_random_timer_generate();
}
}
else if (rgb_prog_timer >= 9) {
// fade out current program
w = 7 - (rgb_prog_timer - 9);
for (i = 0; i < 16; i++) {
out[i] = 0xff >> w;
}
is31fl3729_set_scaling_current_multi(FL3729_ADDR, out, 15);
}
else if (rgb_prog_timer > 7) {
// 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;
}
// try to select a new program if we haven't set one before
if (rgb_prog_idx == 0xff) {
rgb_prog_random_select();
}
}
else if (rgb_prog_timer > 0) {
// fade in new program
w = (rgb_prog_timer & 0x7);
for (i = 0; i < 16; i++) {
out[i] = 0xff >> w;
}
is31fl3729_set_scaling_current_multi(FL3729_ADDR, out, 15);
}
else { // 0
// randomize next program timing
for (i = 0; i < 16; i++) {
out[i] = 0xff;
}
is31fl3729_set_scaling_current_multi(FL3729_ADDR, out, 15);
rgb_prog_random_timer_generate();
}
}
}
}
break;
}
case MODE_PROGRAM: {
// always postpone config saving
config_save_timer = UI_CONF_SAVE_TIMEOUT;
// rapidly flash lsens
if ((tick & 0x7) == 0) {
GPIOD->OUTDR ^= GPIO_Pin_0;
}
// 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
if (led_rgbprog[i]) {
led_rgbprog[i](LED_RGBPROG_PREVIEW | preview_idx, tick);
}
if (preview_idx == i) {
// flash the selected output
if (flash & 1) {
rgb[i][0] = 16;
rgb[i][1] = 16;
rgb[i][2] = 16;
//rgb[i][0] >>= 3;
//rgb[i][1] >>= 3;
//rgb[i][2] >>= 3;
}
} else {
// dim inactive outputs
//if (!(userconf.ledprog_ena_mask & (1 << i))) {
// rgb[i][0] >>= 1;
// rgb[i][1] >>= 1;
// rgb[i][2] >>= 1;
//}
}
}
break;
}
case MODE_PARAMETER: {
// always postpone config saving
config_save_timer = UI_CONF_SAVE_TIMEOUT;
// slowly flash lsnes
if ((tick & 0x20) == 0) {
GPIOD->OUTDR ^= GPIO_Pin_0;
}
// 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)) {
if (led_rgbprog[i]) {
led_rgbprog[i](LED_RGBPROG_PREVIEW | preview_idx, tick);
}
if (preview_idx == i) {
// flash the selected output
if (flash & 1) {
rgb[i][0] = 32;
rgb[i][1] = 32;
rgb[i][2] = 32;
//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();
}