/* * 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 #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 4 #define MAX_GC 60 #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; static const uint16_t cursor_flash_rates[] = { // 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 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); } } // set the level on the cursor if (cursor[color] != level) { cursor[color] = level; 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() { uint32_t t; uint8_t i; uint8_t w; 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 64 times/second // (the value actually updates less frequently, but we need to fade nicely) if (tick & 3 == 3) { w = adc_get_pot() >> 2; 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 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 led_rgbprog[rgb_prog_idx - 1](LED_RGBPROG_NORMAL, tick); } } // temp: remove me once buttons are tested and working led_rgbprog[0](LED_RGBPROG_NORMAL, tick); 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(); }