main firmware: basic user UI, setting save / restore added

Can now use the buttons to change programs, change active LED zones, change LED brightness and enter and exit programming mode. Exiting programming mode will save settings. Holding MODE upon boot will load defaults (though not overwrite them in EEPROM).

Also added basic power management. If the badge is still for 15min, LEDs will go out. When LEDs are blank due to brightness off setting or timeout, they are turned off to save power. Moving badge, entering programming mode, changing the program, or changing brightness will enable the LEDs.
This commit is contained in:
true 2023-08-04 02:13:47 -07:00
parent 7bbd46fe5f
commit d0d07b82f6
11 changed files with 507 additions and 105 deletions

View File

@ -17,15 +17,26 @@
#define BTN_PUSH (1 << 0)
#define BTN_HELD (1 << 1)
#define BTN_RELEASE (1 << 2)
#define BTN_HELDRT (1 << 2)
#define BTN_RELEASE (1 << 3)
#define BTN_PUSH_CB (1 << 4)
#define BTN_HELD_CB (1 << 5)
#define BTN_RELEASE_CB (1 << 6)
#define BTN_HELDRT_CB (1 << 6)
#define BTN_RELEASE_CB (1 << 7)
#define BTN_DEBOUNCE 11 // how many button ticks to wait before registering press
#define BTN_MAX_HOLD (512*30) // longest reported / processed hold time (30s)
#define BTN_HOLD_SHIFT 6 // rshift value to get 1/16th sec hold time
#define BTN_HOLD_SHIFT 5 // rshift value to get 1/16th sec hold time
#define BTN_HOLD_0_25S 4
#define BTN_HOLD_0_50S 8
#define BTN_HOLD_1_00S 16
#define BTN_HOLD_1_50S 24
#define BTN_HOLD_2_00S (16 * 2)
#define BTN_HOLD_3_00S (16 * 3)
#define BTN_HOLD_4_00S (16 * 4)
#define BTN_HOLD_5_00S (16 * 5)
@ -36,9 +47,9 @@ typedef struct Btn_t {
uint16_t hold_retrig; // hold time retrigger threshold in button ticks, 0 disables
uint16_t hold_rt_ctr; // hold retrigger counter
void (*push_cb)();
void (*held_cb)();
void (*release_cb)();
void (*push_cb)(uint8_t);
void (*held_cb)(uint8_t);
void (*release_cb)(uint8_t);
} Btn_t;
/*

View File

@ -10,12 +10,12 @@
extern void (*ledprog)();
void ledprog_default(); // set ledprog settings to defaults
void ledprog_change(); // change active ledprog idx
void ledprog_run();
void ledprog_default();
void ledprog_change();
void ledprog_btn(); // assign buttons to ledprog

View File

@ -21,6 +21,11 @@
//#define SK6X_HI 0xc0 // 0b11000000, or 0.25/0.75uS on/off
//#define SK6X_LO 0xfc // 0b11111100, or 0.75/0.25uS on/off
#define SK6X_ZONE_MAIN 0x01
#define SK6X_ZONE_SIDE 0x02
#define SK6X_ZONE_REAR 0x04
#define SK6X_ZONE_ALL (SK6X_ZONE_MAIN | SK6X_ZONE_SIDE | SK6X_ZONE_REAR)
void led_sk6x_init();
@ -31,6 +36,9 @@ void led_sk6x_set_all(uint8_t r, uint8_t g, uint8_t b);
void led_sk6x_process();
void led_sk6x_update();
void led_sk6x_zone_ena(uint8_t zone_mask);
void led_sk6x_brightness(uint8_t brightness);
#endif /* CODE_INC_LED_SK6X_SPI_H_ */

View File

@ -10,4 +10,9 @@
void userled_init();
void userled_set(uint8_t brightness);
#endif /* CODE_INC_LED_USER_H_ */

View File

@ -0,0 +1,36 @@
/*
* userconf.h
*
* Created on: Aug 3, 2023
* Author: true
*/
#ifndef CODE_INC_USERCONF_H_
#define CODE_INC_USERCONF_H_
#define UCONF_MAGIC 0x1f
typedef struct UserConf_t {
uint8_t magic;
uint8_t led_prog_idx;
uint8_t led_zone;
uint8_t led_bright;
uint8_t led[15][4];
} UserConf_t;
extern UserConf_t uconf;
void uconf_save();
void uconf_restore(uint8_t force_defaults);
#endif /* CODE_INC_USERCONF_H_ */

View File

@ -6,11 +6,17 @@
*/
static uint8_t mode = BTN_MODE_IDLE;
#include <stdint.h>
#include "hk32f030m.h"
#include "btn.h"
// static uint8_t mode = BTN_MODE_IDLE;
Btn_t btn[BTN_COUNT] = {0};
@ -28,6 +34,7 @@ static void btn_check(uint8_t idx, uint8_t state)
}
} else {
btn[idx].held = 0;
btn[idx].hold_rt_ctr = 0;
}
}
@ -36,9 +43,9 @@ void btn_tick()
uint8_t i;
// increment hold
btn_check(0, BTN1);
btn_check(1, BTN2);
btn_check(2, BTN3);
btn_check(BTN_MODE, GPIOA->IDR & 0x4);
btn_check(BTN_PROG, GPIOA->IDR & 0x2);
btn_check(BTN_SET, GPIOA->IDR & 0x8);
// process
for (i = 0; i < BTN_COUNT; i++) {
@ -47,14 +54,21 @@ void btn_tick()
// release first
if (btn[i].held == 0) {
// yes, released
btn[i].state = BTN_RELEASE;
btn[i].state |= BTN_RELEASE;
// do we trigger hold?
} else if (btn[i].hold_time && (btn[i].held >> BTN_HOLD_SHIFT) == btn[i].hold_time) {
} else if (btn[i].hold_thresh && (btn[i].held >> BTN_HOLD_SHIFT) >= btn[i].hold_thresh) {
// yes, held, but only set if we didn't already
if (!(btn[i].state & BTN_HELD)) {
btn[i].state = BTN_HELD;
btn[i].hold_rt_ctr = btn[i].hold_retrig;
} else if (btn[i].hold_retrig) {
// retrigger counter
btn[i].hold_rt_ctr++;
if (btn[i].hold_rt_ctr == btn[i].hold_retrig) {
// clear held callback
btn[i].state |= BTN_HELDRT;
btn[i].hold_rt_ctr = 0;
}
}
}
} else {
@ -77,30 +91,28 @@ void btn_callback()
if ((s & BTN_PUSH) && !(s & BTN_PUSH_CB)) {
btn[i].state |= BTN_PUSH_CB;
if (btn[i].push_cb) {
btn[i].push_cb();
}
}
if (btn[i].hold_retrig) {
if (btn[i].hold_rt_ctr) {
btn[i].hold_rt_ctr--;
} else {
btn[i].hold_rt_ctr = btn[i].hold_retrig;
btn[i].state &= ~(BTN_HELD_CB);
btn[i].push_cb(i);
}
}
if ((s & BTN_HELD) && !(s & BTN_HELD_CB)) {
btn[i].state |= BTN_HELD_CB;
if (btn[i].held_cb) {
btn[i].held_cb();
btn[i].held_cb(i);
}
}
if ((s & BTN_HELDRT) && !(s & BTN_HELD_CB)) {
btn[i].state |= BTN_HELDRT_CB;
if (btn[i].held_cb) {
btn[i].held_cb(i);
}
}
if ((s & BTN_RELEASE) && !(s & BTN_RELEASE_CB)) {
btn[i].state |= BTN_RELEASE_CB;
if (btn[i].release_cb) {
btn[i].release_cb();
btn[i].release_cb(i);
}
}
}

View File

@ -8,9 +8,17 @@
#include <stdint.h>
#include "btn.h"
#include "hsv2rgb.h"
#include "led_sk6x_spi.h"
#include "led_user.h"
#include "prng.h"
#include "userconf.h"
#define LEDPROG_MAX_PROGRAMS 7
@ -23,7 +31,6 @@ void ledprog5_flicker_all();
void ledprog6_ramper();
static uint8_t settings[16][4];
const void (*led_prog_list[])() = {
ledprog0_twinkle_white,
@ -35,38 +42,204 @@ const void (*led_prog_list[])() = {
ledprog6_ramper
};
void (*ledprog)();
static uint8_t led_prog_active = 0;
static uint32_t work[4];
static uint8_t r, g, b;
static uint8_t prog_mode = 0;
static uint8_t prog_tmr = 0;
static uint8_t prog_led = 0;
static uint8_t prog_idx = 0;
/*
* program pages:
* 1-4: 4 program bytes for active led program
*/
void ledprog_default()
{
settings[3][2] = 2;
settings[3][3] = 30;
uconf.led[3][2] = 2;
uconf.led[3][3] = 30;
settings[4][1] = 248;
settings[4][2] = 170;
settings[4][3] = 16;
uconf.led[4][1] = 248;
uconf.led[4][2] = 170;
uconf.led[4][3] = 16;
settings[6][1] = 192;
settings[6][2] = 30;
settings[6][3] = 6;
uconf.led[6][1] = 192;
uconf.led[6][2] = 30;
uconf.led[6][3] = 6;
}
void ledprog_change(uint8_t idx)
{
uint8_t i;
ledprog = led_prog_list[idx];
led_prog_active = idx;
for (i = 0; i < 4; i++) {
work[i] = 0;
}
}
void ledprog_btn_push_cb(uint8_t idx)
{
if (!prog_mode) return;
switch (idx) {
case BTN_MODE: {
}
case BTN_SET: {
}
}
}
void ledprog_btn_held_cb(uint8_t idx)
{
if (idx == BTN_PROG) {
prog_mode ^= 1;
prog_idx = prog_tmr = 0;
// when leaving programming mode we need to commit settings to flash
if (!prog_mode) {
uconf.led_prog_idx = led_prog_active;
uconf_save();
}
return;
}
if (prog_mode) {
switch (idx) {
case BTN_MODE:
case BTN_SET: {
ledprog_btn_push_cb(idx);
break;
}
}
// allow retrigger
btn[idx].state &= ~(BTN_HELDRT | BTN_HELDRT_CB);
} else {
// do not retrigger on these
if (btn[idx].state & BTN_HELDRT_CB) return;
switch (idx) {
case BTN_MODE: { // return to user's default program
ledprog_change(uconf.led_prog_idx);
led_sk6x_brightness(uconf.led_bright ? uconf.led_bright : 1);
break;
}
case BTN_SET: { // change active zones
uconf.led_zone++;
if (uconf.led_zone > 7) uconf.led_zone = 1;
led_sk6x_zone_ena(uconf.led_zone);
break;
}
}
}
}
void ledprog_btn_release_cb(uint8_t idx)
{
if (btn[idx].state & BTN_HELD) {
btn[idx].state = 0;
return;
}
if (prog_mode) {
switch (idx) {
case BTN_PROG: { // change program variable page
prog_idx++;
if (prog_idx >= 4) prog_idx = 0;
break;
}
}
} else {
switch (idx) {
case BTN_MODE: { // change active program
led_prog_active++;
if (led_prog_active >= LEDPROG_MAX_PROGRAMS) led_prog_active = 0;
ledprog_change(led_prog_active);
// increase brightness if we are dark
if (uconf.led_bright) {
break;
}
}
case BTN_PROG: { // change brightness
uconf.led_bright++;
if (uconf.led_bright > 5) uconf.led_bright = 0;
led_sk6x_brightness(uconf.led_bright);
break;
}
case BTN_SET: { // do nothing?
}
}
}
// clear any button state as button is now idle
btn[idx].state = 0;
}
void ledprog_btn()
{
btn[BTN_PROG].held_cb = ledprog_btn_held_cb;
btn[BTN_PROG].release_cb = ledprog_btn_release_cb;
btn[BTN_PROG].hold_thresh = BTN_HOLD_1_00S;
btn[BTN_MODE].push_cb = ledprog_btn_push_cb;
btn[BTN_MODE].held_cb = ledprog_btn_held_cb;
btn[BTN_MODE].release_cb = ledprog_btn_release_cb;
btn[BTN_MODE].hold_thresh = BTN_HOLD_1_00S;
btn[BTN_MODE].hold_retrig = 10;
btn[BTN_SET].push_cb = ledprog_btn_push_cb;
btn[BTN_SET].held_cb = ledprog_btn_held_cb;
btn[BTN_SET].release_cb = ledprog_btn_release_cb;
btn[BTN_SET].hold_thresh = BTN_HOLD_1_00S;
btn[BTN_SET].hold_retrig = 10;
}
void ledprog_run()
{
if (prog_mode) {
// we need to see the LEDs
if (!uconf.led_bright) {
uconf.led_bright++;
led_sk6x_brightness(uconf.led_bright);
}
// 2 second LED flash timer
if (!prog_tmr) prog_led = prog_idx + 1;
// flash LED to indicate the page we are programming
if (prog_led) {
switch (prog_tmr & 0x1f) {
case 0x00: {
userled_set(255);
break;
}
case 0x0a: {
userled_set(0);
prog_led--;
break;
}
}
}
prog_tmr++;
}
// always run a program
led_prog_list[led_prog_active]();
}
void ledprogi_twinkle(uint8_t r, uint8_t g, uint8_t b)
{
uint8_t x;
@ -82,7 +255,7 @@ void ledprogi_twinkle(uint8_t r, uint8_t g, uint8_t b)
}
/*
* settings:
* uconf.led:
* 0 dwell
* 1 threshold
*/
@ -92,7 +265,7 @@ void ledprog0_twinkle_white()
}
/*
* settings:
* uconf.led:
* 0 dwell
* 1 threshold
*/
@ -131,7 +304,7 @@ void ledprog1_twinkle_rgb_rand()
/*
* settings:
* uconf.led:
* 0 dwell
* 1 threshold
* 2 hue
@ -139,13 +312,13 @@ void ledprog1_twinkle_rgb_rand()
*/
void ledprog2_twinkle_rgb_set()
{
hsv2rgb_8b(settings[2][2] * 6, 255, settings[2][3], &r, &g, &b);
hsv2rgb_8b(uconf.led[2][2] * 6, 255, uconf.led[2][3], &r, &g, &b);
ledprogi_twinkle(r, g, b);
}
/*
* settings:
* uconf.led:
* 0 dwell
* 1 threshold
* 2 speed
@ -159,7 +332,7 @@ void ledprog3_rainbow()
uint8_t i;
uint16_t w;
work[0] += (settings[3][2] * 6) + 1;
work[0] += (uconf.led[3][2] * 6) + 1;
work[0] %= 1536;
w = work[0];
@ -167,7 +340,7 @@ void ledprog3_rainbow()
hsv2rgb_8b(w, 255, 255, &r, &g, &b);
led_sk6x_set(i, r, g, b);
w += (settings[3][3] * 6) + 1;
w += (uconf.led[3][3] * 6) + 1;
w %= 1536;
}
}
@ -177,14 +350,14 @@ void ledprogi_flicker()
uint16_t h;
uint8_t x;
h = settings[4][2] * 6;
h = uconf.led[4][2] * 6;
x = prng_get8();
if (x > settings[4][1]) {
if (x > uconf.led[4][1]) {
x = 255;
} else {
x = settings[4][3];
x = uconf.led[4][3];
}
x &= ~0x07;
@ -194,7 +367,7 @@ void ledprogi_flicker()
}
/*
* settings:
* uconf.led:
* 0 dwell
* 1 threshold
* 2 hue
@ -207,7 +380,7 @@ void ledprog4_flicker_same()
}
/*
* settings (uses ledprog4)
* uconf.led (uses ledprog4)
* 0 dwell
* 1 threshold
* 2 hue
@ -224,7 +397,7 @@ void ledprog5_flicker_all()
}
/*
* settings
* uconf.led
* 0 dwell
* 1 speed
* 2 hue
@ -245,11 +418,11 @@ void ledprog6_ramper()
uint8_t fr;
h = settings[6][2] * 6;
fr = settings[6][3] & 0x3f;
h = uconf.led[6][2] * 6;
fr = uconf.led[6][3] & 0x3f;
// speed -64 to -1, +1 to +64
spd = (settings[6][1] >> 1) - 64;
spd = (uconf.led[6][1] >> 1) - 64;
if (spd >= 0) spd++;
work[0] += spd;
work[0] &= 0xfff;

View File

@ -53,10 +53,13 @@
const uint8_t sk6x_map[8] = {0, 1, 2, 3, 4, 7, 6, 5};
uint8_t sk6x_led[SK6X_LED_MAX_COUNT][3]; // G-R-B order
uint8_t sk6x_buf[SK6X_BUF_SIZE];
static uint8_t sk6x_led[SK6X_LED_MAX_COUNT][3]; // G-R-B order
static uint8_t sk6x_buf[SK6X_BUF_SIZE];
uint8_t sk6x_brightness = 3;
static uint8_t sk6x_brightness = 0;
static uint8_t sk6x_led_enabled = 0;
static uint8_t sk6x_zone = SK6X_ZONE_ALL;
@ -80,14 +83,31 @@ __attribute__ ((long_call, section(".ramfunc"))) void led_sk6x_process()
uint16_t n = 0;
uint8_t b[8];
uint8_t v;
j = 0;
// do nothing if LEDs are turned off
if (!sk6x_led_enabled) {
return;
}
// pack RGB values into 5-bits-per-bit format
for (i = 0; i < SK6X_LED_MAX_COUNT; i++) {
for (j = 0; j < 3; j++) {
// pack values for next color
uint8_t *p = b;
SK6X_FILL(sk6x_led[sk6x_map[i]][j] >> sk6x_brightness);
v = sk6x_led[sk6x_map[i]][j] >> sk6x_brightness;
if (i <= 4) {
if (!(sk6x_zone & SK6X_ZONE_MAIN)) v = 0;
} else if (i <= 5) {
if (!(sk6x_zone & SK6X_ZONE_REAR)) v = 0;
} else {
if (!(sk6x_zone & SK6X_ZONE_SIDE)) v = 0;
}
SK6X_FILL(v);
// pack 8 bits into 5 bytes in final buffer
sk6x_buf[n + 0] = (b[0] << 3) | (b[1] >> 2);
@ -102,11 +122,36 @@ __attribute__ ((long_call, section(".ramfunc"))) void led_sk6x_process()
__attribute__ ((long_call, section(".ramfunc"))) void led_sk6x_update()
{
// configure MOSI pin and bit depth
spi_mosi_sel(SPI_MOSI_LED);
// turn on / off LEDs based on brightness setting
if (sk6x_brightness >= 8) {
// leds should be OFF
// begin sending data
// if already off, we're done
if (!sk6x_led_enabled) return;
// otherwise, blank the LEDs
led_sk6x_process();
} else {
// leds should be ON
if (!sk6x_led_enabled) {
// they aren't on, so turn them on
adxl345_set_intr_polarity(ADXL345_INTR_ACTIVE_LO);
sk6x_led_enabled = 1;
}
}
// configure MOSI pin and bit depth, then send LED data
spi_mosi_sel(SPI_MOSI_LED);
spi_xfer(sk6x_buf, 0, sizeof(sk6x_buf), SPI_NO_CALLBACK);
// turn off LEDs based on brightness setting
if (sk6x_brightness >= 8) {
// leds should be OFF
if (sk6x_led_enabled) {
// LEDs should be blanked, so we can turn them off now
adxl345_set_intr_polarity(ADXL345_INTR_ACTIVE_HI);
sk6x_led_enabled = 0;
}
}
}
__attribute__ ((long_call, section(".ramfunc"))) void led_sk6x_set(uint8_t index, uint8_t r, uint8_t g, uint8_t b)
@ -144,3 +189,18 @@ void led_sk6x_cb()
{
// there is nothing to handle after updating LEDs
}
void led_sk6x_zone_ena(uint8_t zone_mask)
{
sk6x_zone = zone_mask & SK6X_ZONE_ALL;
}
void led_sk6x_brightness(uint8_t brightness)
{
if (brightness >= 5) brightness = 5;
if (brightness == 0) brightness = 8;
else brightness = 5 - brightness;
sk6x_brightness = brightness;
}

View File

@ -6,3 +6,37 @@
*/
#include "hk32f030m.h"
#include <stdint.h>
#define USERLED_IPORT GPIOC // i2c port/pin
#define USERLED_IPIN 6
#define USERLED_LPORT GPIOD // led port/pin
#define USERLED_LPIN 7
void userled_init()
{
}
void userled_set(uint8_t brightness)
{
if (!brightness) {
USERLED_IPORT->MODER &= ~(0x3 << (USERLED_IPIN * 2));
USERLED_IPORT->MODER |= (GPIO_Mode_IN << (USERLED_IPIN * 2));
USERLED_LPORT->BRR = (1 << USERLED_LPIN);
} else {
// set i2c port low while LED is lit
USERLED_IPORT->MODER &= ~(0x3 << (USERLED_IPIN * 2));
USERLED_IPORT->MODER |= (GPIO_Mode_OUT << (USERLED_IPIN * 2));
USERLED_IPORT->BRR = (1 << USERLED_IPIN);
USERLED_LPORT->BSRR = (1 << USERLED_LPIN);
}
}

View File

@ -19,18 +19,23 @@
#include "prng.h"
#include "spi.h"
#include "timer.h"
#include "userconf.h"
#define GPIO_RCC_AHB_GPIO_ALL RCC_AHBPeriph_GPIOA | RCC_AHBPeriph_GPIOB | \
RCC_AHBPeriph_GPIOC | RCC_AHBPeriph_GPIOD
#define NO_MOVE_SLEEP_TIME (15 * 60) * 32
static uint32_t uptime = 0; // uptime in seconds
volatile uint16_t cnt = 0; // 1/1000 second tick
static uint16_t cnt2 = 0; // low priority loop check
static uint32_t no_move_ticks; // keep track of how long we are still
static void gpio_init()
@ -150,17 +155,18 @@ int main(void)
adxl345_init();
adxl345_tick();
// load user config, unless SET button is held, in which case we restore defaults
uconf_restore(!(GPIOA->IDR & (1 << 3)));
// configure prng
tinymt32_init(&tinymt32_s, (adxl.x << 16) | (adxl.y << 8) | adxl.z);
// configure addressable LEDs
led_sk6x_init();
ledprog_default();
ledprog_change(6);
// configure user input
btn_init();
ledprog_btn();
// configure mainline timer
#ifdef DEBUG
@ -183,7 +189,7 @@ int main(void)
// led programs (128Hz)
if ((cnt2 & 0x7) == 0) {
if (ledprog) ledprog();
ledprog_run();
// stuff next LED bits
led_sk6x_process();
@ -210,44 +216,22 @@ __attribute__ ((long_call, section(".ramfunc"))) void TIM6_IRQHandler()
btn_tick();
}
// temp: run LED program
/*
static uint8_t v[3];
static uint8_t x = 0;
if (!(cnt % 4)) {
v[x]++;
if (!v[x]) {
x++;
if (x >= 3) x = 0;
}
}
*/
// run next LED program
/*
uint8_t w[2];
w[0] = v[0] >> 2;
w[1] = v[1] >> 2;
w[2] = v[2] >> 2;
led_sk6x_set(0, w[0], w[1], w[2]);
led_sk6x_set(1, w[1], w[2], w[0]);
led_sk6x_set(2, w[2], w[0], w[1]);
led_sk6x_set(3, w[1], w[2], w[0]);
led_sk6x_set(4, w[0], w[1], w[2]);
led_sk6x_set(5, w[0], w[1], w[2]);
led_sk6x_set(6, w[1], w[2], w[0]);
led_sk6x_set(7, w[2], w[0], w[1]);
*/
/*
for (int i = 1; i < 8; i++) {
led_sk6x_set(i, v[0] + (i * 32), v[1] + (i * 32), v[2] + (i * 32));
}
*/
// accelerometer: tick
if ((cnt & 0x3f) == 0x3f) {
// accelerometer: tick 32Hz
if ((cnt & 0x1f) == 0x1e) {
adxl345_tick();
// should we sleep?
// todo: make this interrupt driven, and tune required accel value
if (adxl345_movement() < 6) {
if (no_move_ticks >= NO_MOVE_SLEEP_TIME) {
led_sk6x_brightness(0);
} else no_move_ticks++;
} else {
if (no_move_ticks >= NO_MOVE_SLEEP_TIME) {
led_sk6x_brightness(uconf.led_bright);
}
no_move_ticks = 0;
}
}

View File

@ -0,0 +1,79 @@
/*
* userconf.c
*
* Created on: Aug 3, 2023
* Author: true
*/
#include <stdint.h>
#include <string.h>
#include "hk32f030m.h"
#include "userconf.h"
#include "led_prog.h"
#include "led_sk6x_spi.h"
#define EEPROM_BASE_ADDR 0x0c000000;
UserConf_t uconf = {0};
void uconf_save()
{
uint8_t i;
UserConf_t usave;
uint8_t *eeprom = (uint8_t *)EEPROM_BASE_ADDR;
uint8_t *u8save = (uint8_t *)&usave;
memcpy((void *)&usave, (void *)&uconf, sizeof(uconf));
// never save brightness as minimum value
if (!usave.led_bright) usave.led_bright++;
// commit bytes to eeprom only if they differ
for (i = 0; i < sizeof(uconf); i++) {
if (*eeprom != *u8save) {
// erase byte
EEPROM_EraseByte((uint32_t)eeprom);
// write new byte
EEPROM_ProgramByte((uint32_t)eeprom, *u8save);
}
eeprom++;
u8save++;
}
}
void uconf_restore(uint8_t force_defaults)
{
uint8_t *eeprom = (uint8_t *)EEPROM_BASE_ADDR;
if ((*eeprom != 0x1f) || force_defaults) {
// nothing to restore. load defaults instead
uconf.magic = UCONF_MAGIC;
uconf.led_prog_idx = 3; // rainbow
uconf.led_bright = 3; // 50% brightness
uconf.led_zone = 7; // all zones active
ledprog_default();
} else {
// copy from eeprom to uconf
memcpy((void *)&uconf, (void *)eeprom, sizeof(uconf));
}
// set settings from user config
ledprog_change(uconf.led_prog_idx);
led_sk6x_zone_ena(uconf.led_zone);
led_sk6x_brightness(uconf.led_bright);
}