+enum {
+ /* Operating modes */
+ MODE_SOLID = 0, /* Cycle through colors[][] before next mode */
+ MODE_CYCLE, /* Auto cycle through colors */
+ MODE_PARTY, /* Random yet fast incolor and fade */
+ MODE_COUNT,
+
+ /* Indexes in colors[][] for LEDs */
+ LED_RED = 0,
+ LED_GRN,
+ LED_BLU,
+ LED_WHT,
+ LED_COUNT,
+
+ HELD_TMR_PERIODS = 1, /* Held timer fires every 32.768 msec */
+ HELD_PERIODS = 16, /* Held this long before held events fire */
+ COLOR_COUNT = 12,
+ COLOR_CANDLE = 0, /* Color index for the candle color */
+ COLOR_WHITE = 11, /* Color index for white */
+ BRIGHT_TOP = 100, /* Bright ramps up to top ... */
+ BRIGHT_BOTTOM = -5, /* ... then down to bottom */
+ BRIGHT_MAX = 85, /* Bright values are constrained for computations */
+ BRIGHT_MIN = 1, /* ... by min and max. */
+ BRIGHT_INIT = 42, /* Initial value is replaced by value from eeprom */
+ CBRIGHT_VARIANCE = 20, /* Brightness range for candle flicker */
+ STD_FADE = 16, /* Fade time in 32.768 ms units */
+ STD_INCOLOR = 29491, /* Time in color when MODE_CYCLE, in 32.768 ms units */
+ PARTY_MIN = 8, /* Min party fade and incolor units */
+ PARTY_RANGE = 8, /* Party fade/incolor range mask for lfsr_get() */
+ /* ... see start_fade() */
+ CFG_ADDR = 0, /* EEPROM address of start of configuration */
+ CFG_DELAY = 183, /* 6 seconds in 32.768 msec units */
+};
+
+typedef struct {
+ int value;
+ int increment;
+ signed char remainder;
+} led_t;
+
+/* Offsets to brightness used for candle color */
+const signed char offsets[4] = { -1, -1, 1, 1 };
+
+/* Available colors, 6 bits per color (no values above 0x3f) */
+const unsigned char colors[COLOR_COUNT][LED_COUNT] = {
+ { 0x2c, 0x20, 0x00, 0x18 }, /* candle */
+ { 0x28, 0x00, 0x00, 0x00 }, /* red */
+ { 0x30, 0x20, 0x00, 0x00 }, /* orange */
+ { 0x2c, 0x2c, 0x00, 0x00 }, /* yellow */
+ { 0x20, 0x2c, 0x00, 0x00 }, /* yellow-green */
+ { 0x00, 0x30, 0x00, 0x00 }, /* green */
+ { 0x00, 0x30, 0x18, 0x00 }, /* green-cyan */
+ { 0x00, 0x30, 0x28, 0x00 }, /* cyan */
+ { 0x00, 0x18, 0x30, 0x00 }, /* cyan-blue */
+ { 0x00, 0x00, 0x30, 0x00 }, /* blue */
+ { 0x1c, 0x00, 0x30, 0x00 }, /* magenta */
+ { 0x3f, 0x3f, 0x3f, 0x3f }, /* white */
+};
+
+unsigned char mode = MODE_SOLID;
+unsigned char color = COLOR_CANDLE;
+signed char bright = BRIGHT_INIT;
+bit bright_up;
+led_t red;
+led_t grn;
+led_t blu;
+led_t wht;
+bit on;
+int fade_steps;
+signed char pbHeldCount;
+signed char cbright;
+
+void cfg_write()
+{
+ unsigned char addr = CFG_ADDR;
+ unsigned char m, c;
+ signed char b;
+
+ m = eeprom_read(addr++);
+ c = eeprom_read(addr++);
+ b = eeprom_read(addr);
+ if (mode != m || color != c || bright != b) {
+ addr = CFG_ADDR;
+ eeprom_write(addr++, mode);
+ eeprom_write(addr++, color);
+ eeprom_write(addr, bright);
+ }
+}
+
+void cfg_read()
+{
+ unsigned char addr = CFG_ADDR;
+ unsigned char tmp;
+
+ tmp = eeprom_read(addr++);
+ if (tmp < MODE_COUNT)
+ mode = tmp;
+
+ tmp = eeprom_read(addr++);
+ if (tmp < COLOR_COUNT)
+ color = tmp;
+
+ tmp = eeprom_read(addr);
+ if ((signed char)tmp >= BRIGHT_BOTTOM && (signed char)tmp <= BRIGHT_TOP)
+ bright = tmp;
+}
+
+/* Combine LED color value and brightness level to generate an RGB value.
+ * bright will be BRIGHT_MIN...BRIGHT_MAX
+ *
+ * @param value 0...1023 (6 bit color, left shifted 4 bits, for 10 bits)
+ * @param b brightness value BRIGHT_MIN...BRIGHT_MAX
+ * @return An RGB drive value, 0...255
+ */
+unsigned char led_get(int value, signed char b)
+{
+ return (unsigned long)value * b / 4 / BRIGHT_MAX;
+}
+
+/* Set the LEDs using color values and current brightness. */
+void leds_set()
+{
+ signed char b;
+
+ /* Use correct brightness value, then constrain it */
+ if (mode != MODE_PARTY && color != COLOR_WHITE)
+ b = cbright;
+ else
+ b = bright;
+ if (b > BRIGHT_MAX)
+ b = BRIGHT_MAX;
+ else if (b < BRIGHT_MIN)
+ b = BRIGHT_MIN;
+
+ rgb_set(led_get((red).value, b), led_get((grn).value, b),
+ led_get((blu).value, b), led_get((wht).value, b));
+}
+
+void start_fade()