553 lines
18 KiB
C
553 lines
18 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 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 = 0; // 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;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
led_rgbprog[rgb_prog_idx](LED_RGBPROG_NORMAL, tick);
|
||
|
|
||
|
// 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++;
|
||
|
rgb_prog_idx = w;
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case MODE_PROGRAM: {
|
||
|
// always postpone config saving
|
||
|
config_save_timer = UI_CONF_SAVE_TIMEOUT;
|
||
|
|
||
|
// 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 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();
|
||
|
}
|