/*
 * File:   main.c
 * Author: boos
 *
 * Created on August 12, 2023, 7:39 PM
 */

// CONFIG1
#pragma config FOSC = INTOSC    // Oscillator Selection Bits (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = ON       // Power-up Timer Enable (PWRT enabled)
#pragma config MCLRE = OFF      // MCLR Pin Function Select (MCLR/VPP pin function is digital input)
#pragma config CP = OFF         // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config BOREN = OFF      // Brown-out Reset Enable (Brown-out Reset disabled)
#pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = OFF       // Internal/External Switchover Mode (Internal/External Switchover Mode is disabled)
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is disabled)

// CONFIG2
#pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
#pragma config CPUDIV = NOCLKDIV// CPU System Clock Selection Bit (NO CPU system divide)
#pragma config USBLSCLK = 48MHz // USB Low Speed Clock Selection bit (System clock expects 48 MHz, FS/LS USB CLKENs divide-by is set to 8.)
#pragma config PLLMULT = 3x     // PLL Multipler Selection Bit (3x Output Frequency Selected)
#pragma config PLLEN = ENABLED  // PLL Enable Bit (3x or 4x PLL Enabled)
#pragma config STVREN = OFF     // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will not cause a Reset)
#pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config LPBOR = OFF      // Low-Power Brown Out Reset (Low-Power BOR is disabled)
#pragma config LVP = OFF        // Low-Voltage Programming Enable (Low-voltage programming disabled)

#include <xc.h>


// abbreviations for locations of external devices
// (this is not necessary, but useful)
#define WS2812_DATA  RC2
#define ENC_SW       !RA3
#define ENC_A        RA4
#define ENC_B        RA5
#define DS1302_CLK   RC3
#define DS1302_IO    RC4
#define DS1302_CE    RC5

// clock frequency (for __delay_ms function)
#define _XTAL_FREQ 48000000

// functions to control the WS2812 NeoPixel LEDs
#define WS2812_send_bit(b) WS2812_DATA=1; NOP(); NOP(); NOP(); WS2812_DATA=b; NOP(); NOP(); NOP(); NOP(); WS2812_DATA=0; NOP(); NOP(); NOP(); NOP();
void WS2812_send_byte (unsigned char b);
void WS2812_send_RGB (unsigned char r, unsigned char g, unsigned char b);

// helpful functions that allow us to sweep over a lot of colors with a single variable h (taking values from 0..255)
// (inspired by the curves presented on https://github.com/FastLED/FastLED/wiki/FastLED-HSV-Colors)
int hsv_red (int h);
int hsv_green (int h);
int hsv_blue (int h);

// function that converts Gray code into a binary number (for rotary encoder)
int gray_to_binary (void);

// functions for the DS1302 real-time clock
void DS1302_send (int value);
unsigned char DS1302_get (void);
void DS1302_send_bcd (int address, int data);
int DS1302_get_bcd (int address);
void DS1302_set_seconds (int data);
void DS1302_set_minutes (int data);
void DS1302_set_hours (int data);
int DS1302_get_seconds (void);
int DS1302_get_minutes (void);
int DS1302_get_hours (void);
void DS1302_start (void);
void DS1302_stop (void);
void DS1302_set24 (void);


// global variables used for the rotary encoder (we add "volatile" wherever they are used and updated in the ISR)
volatile int rotary_pos, rotary_pos_before, rotary_diff;
volatile int rotary_value;
int rotary_value_old, rotary_value_change;

// time information is global for simplicity
int hours, minutes, seconds;

// global debounce variable for the rotary encoder's pushbutton
// (is altered in the ISR, so we make it "volatile")
volatile int sw_buffer;


// *****
// main function
void main (void) {
    
    
    // *****
    // set up internal oscillator to run at 48 MHz
    
    // set the SCS settings (system clock select)
    // to use the frequency specified in configuration word (line 22)
    SCS0 = 0;
    SCS1 = 0;
    
    // set to 16 MHz configuration
    IRCF0 = 1;
    IRCF1 = 1;
    IRCF2 = 1;
    IRCF3 = 1;
    
    // enable PLL to result in 48MHz
    // (PLL is set to 3x in line 23)
    SPLLEN = 1;
    
    
    // *****
    // set up peripherals
    
    // WS2812_DATA is an output
    TRISC2 = 0;
    
    // DS1302_CLK is an output
    TRISC3 = 0;
    
    // DS1302_IO is usually an input
    // (unless we are sending out data)
    TRISC4 = 1;
    
    // DS1302_CE is an output
    TRISC5 = 0;
    
    // turn off all analog to digital converters
    ANSA4 = 0;
    ANSC2 = 0;
    ANSC3 = 0;
    
    // the rotary encoder pins are all inputs
    // (RA3 can only be an input, so we don't have to set its tristate register)
    TRISA4 = 1;
    TRISA5 = 1;
    
    // enable the weak internal pullup resistors on the rotary encoder inputs RA3, RA4, RA5
    WPUA3 = 1;
    WPUA4 = 1;
    WPUA5 = 1;
    nWPUEN = 0;


    // *****
    // configure TIMER0 (we use it for reading out the rotary encoder)
	// (see Sec. 19 in the PIC16F1455 datasheet for specifications)
    TMR0CS = 0;                 // internal oscillator (Fosc/4)
    PSA = 0;                    // prescaler ON
    PS2 = 0; PS1 = 1; PS0 = 1;  // set to 1:16
    TMR0IE = 1;                 // enable interrupt on overflow
    GIE = 1;                    // enable global interrupts

    
    // *****
    // variables
    
    // current color setting ("hue"), current brightness level, and floating point brightness (in percent)
    int hue, brightness_index;
    float brightness;

    // 16 LED brightness levels
    // (We use this as a lookup table, feel free to change your brightness levels between 0 and 1.)
    float brightness_table[] = {0, 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.08, 0.1, 0.19, 0.25, 0.34, 0.42, 0.52, 0.7, 1};
    
    // current position of the menu
    // (0=normal operation and adjust brightness, 1=adjust hours, 2=adjust minutes, 3=adjust color)
    unsigned char menu_index = 0;
    
    // allows for blanking of hours, minutes, or seconds
    // (useful during time adjustments)
    unsigned char show_hours = 1, show_minutes = 1, show_seconds = 1;
       
    // RGB information for hour, minutes, and second LEDs (six LEDs each)
    // (separate arrays make the code more readable, I think)
    unsigned char hours_red[]     = {0, 0, 0, 0, 0, 0};
    unsigned char hours_green[]   = {0, 0, 0, 0, 0, 0};
    unsigned char hours_blue[]    = {0, 0, 0, 0, 0, 0};
    unsigned char minutes_red[]   = {0, 0, 0, 0, 0, 0};
    unsigned char minutes_green[] = {0, 0, 0, 0, 0, 0};
    unsigned char minutes_blue[]  = {0, 0, 0, 0, 0, 0};
    unsigned char seconds_red[]   = {0, 0, 0, 0, 0, 0};
    unsigned char seconds_green[] = {0, 0, 0, 0, 0, 0};
    unsigned char seconds_blue[]  = {0, 0, 0, 0, 0, 0};
    
    // color of main LEDs, PM indicator, and highlight color
    unsigned char color_red, color_green, color_blue;
    unsigned char color_pm_red, color_pm_green, color_pm_blue;
    unsigned char color_highlight_red, color_highlight_green, color_highlight_blue;
    
    
    // *****
    // set up DS1302 real-time clock IC
    
    // clear the WRITE PROTECTION bit
    DS1302_CE = 1;
    DS1302_send(0x8e);
    DS1302_send(0);
    DS1302_CE = 0;
    
    // read stored brightness preference from DS1302 custom memory
    DS1302_CE = 1;
    DS1302_send(0xc1);
    brightness_index = DS1302_get();
    DS1302_CE = 0;
    rotary_value = 4*brightness_index;
    rotary_value_old = rotary_value;
    menu_index = 0;
    
    // read stored hue preference from DS1302 custom memory
    DS1302_CE = 1;
    DS1302_send(0xc3);
    hue = DS1302_get();
    DS1302_CE = 0;
    
    // always set DS1302 to 24-hour mode
    // (the conversion to 12-hour mode, if required, is done by us in the code.)
    DS1302_set24();
    
    
    // *****
    // main loop
    while (1) {
        
        
        // *****
        // read out data from DS1302
        
        // retrieve time
        seconds = DS1302_get_seconds();
        minutes = DS1302_get_minutes();
        hours = DS1302_get_hours();
        
        // Did the rotary encoder turn?
        // (We check for this so that we only update the information stored
        // in the DS1302 whenever something actually has been changed. This
        // reduces the amount of unnecessary write operations to the DS1302.)
        if (rotary_value != rotary_value_old) {
            rotary_value_change = 1;
            rotary_value_old = rotary_value;
        } else {
            rotary_value_change = 0;
        }
        
        
        // *****
        // menu functionality
        
        // standard operation, rotary encoder changes LED brightness
        if (menu_index == 0) {
            
            // show all information
            show_hours = 1;
            show_minutes = 1;
            show_seconds = 1;
            
            // rotary encoder value is mapped to 0..28, which is a double
            // pass through all brightness levels
            // (if rotary encoder value becomes negative, reset values)
            brightness_index = (rotary_value % 116) / 4;
            if (brightness_index < 0) {
                brightness_index = 28;
                rotary_value = 4*brightness_index;
            }
            if (rotary_value_change) {
                DS1302_CE = 1;
                DS1302_send(0xc0);
                DS1302_send(brightness_index);
                DS1302_CE = 0;
            }
        
        // rotary encoder sets hours, blank minutes and seconds, pause the clock
        } else if (menu_index == 1) {

            // only show hours
            show_hours = 1;
            show_minutes = 0;
            show_seconds = 0;
            
            // rotary encoder value is mapped to 0..23
            // (if rotary encoder value becomes negative, reset values)
            hours = (rotary_value % 96) / 4;
            if (hours < 0) {
                hours = 23;
                rotary_value = 4*hours;
            }
            if (rotary_value_change) {
                DS1302_set_hours(hours);
            }
        
        // rotary encoder sets minutes, blank hours and seconds, pause the clock
        } else if (menu_index == 2) {
            
            // only show minutes
            show_hours = 0;
            show_minutes = 1;
            show_seconds = 0;
            
            // rotary encoder value is mapped to 0..59
            // (if rotary encoder value becomes negative, reset values)
            minutes = (rotary_value % 240) / 4;
            if (minutes < 0) {
                minutes = 59;
                rotary_value = 4*minutes;
            }
            if (rotary_value_change) {
                DS1302_set_minutes(minutes);
            }
        
        // rotary encoder sets color, pause the clock
        } else if (menu_index == 3) {
        
            // show all information
            show_hours = 1;
            show_minutes = 1;
            show_seconds = 1;
            
            // rotary encoder value is mapped to 0..255
            // (if rotary encoder value becomes negative, reset values)
            hue = rotary_value % 256;
            if (rotary_value_change) {
                DS1302_CE = 1;
                DS1302_send(0xc2);
                DS1302_send(hue);
                DS1302_CE = 0;
            }
        
        }
        
        // react to the rotary encoder's pushbutton
        // (but only if the brightness is non-zero)
        if (ENC_SW && (sw_buffer == 0) && (brightness_index > 0)) {
        
            // refill debounce buffer
            sw_buffer = 500;
            
            // advance through menu options
            menu_index += 1;
            if (menu_index > 3) {
                menu_index = 0;
            }
            
            // initialize normal mode
            if (menu_index == 0) {
                
                // reset seconds (convenient after setting the time)
                seconds = 0;
                DS1302_set_seconds(seconds);
                
                // restart the clock
                DS1302_start();
                
                // prime the rotary encoder value to currently selected brightness level
                // (multiply by 4 because our rotary encoder has 4 increments per tactile "step")
                rotary_value = 4*brightness_index;
            
            // initialize "set hours" mode
            } else if (menu_index == 1) {
                
                // prime the rotary encoder value to current hours
                // (multiply by 4 because our rotary encoder has 4 increments per tactile "step")
                rotary_value = 4*hours;
                
                // pause clock
                DS1302_stop();
                
            // initialize "set minutes" mode
            } else if (menu_index == 2) {

                // prime the rotary encoder value to current minutes
                // (multiply by 4 because our rotary encoder has 4 increments per tactile "step")
                rotary_value = 4*minutes;
                
                // pause clock
                DS1302_stop();
                
            // initialize "set color" mode
            } else if (menu_index == 3) {

                // prime the rotary encoder to currently selected color
                rotary_value = hue;
                
                // pause clock
                DS1302_stop();
                
            }

        }
        
        
        // *****
        // convert time information into LED data
        
        // convert time to 12 hour information
        int hours12 = hours;
        if (hours > 12) {
            hours12 -= 12;
        } else if (hours == 0) {
            hours12 = 12;
        }

        // bit 5 of the hours serves as the PM indicator
        if (hours > 11) {
            hours12 |= 1 << 5;
        }
        
        // determine brightness level
        if (brightness_index > 15) {
            brightness = brightness_table[29 - brightness_index];
        } else {
            brightness = brightness_table[brightness_index];
        }
        
        // main LED color is given by hue value, multiplied by selected brightness
        color_red = (unsigned char) (hsv_red(hue)*brightness);
        color_green = (unsigned char) (hsv_green(hue)*brightness);
        color_blue = (unsigned char) (hsv_blue(hue)*brightness);
        
        // PM indicator color is given by complementary color (by adding 128 to the hue),
        // multiplied by selected brightness
        color_pm_red = (unsigned char) (hsv_red(hue+128)*brightness);
        color_pm_green = (unsigned char) (hsv_green(hue+128)*brightness);
        color_pm_blue = (unsigned char) (hsv_blue(hue+128)*brightness);
        
        // highlight color is set to white (to highlight which data we are adjusting)
        // (but you can set it to any color you like, of course)
        color_highlight_red   = (unsigned char) 1;
        color_highlight_green = (unsigned char) 1;
        color_highlight_blue  = (unsigned char) 1;
        
        // update all LED color values with time information
        for (int i=0; i<=5; i++) {
            hours_red[i]     = ((hours12 >> i) & show_hours)*color_red;
            hours_green[i]   = ((hours12 >> i) & show_hours)*color_green;
            hours_blue[i]    = ((hours12 >> i) & show_hours)*color_blue;
            minutes_red[i]   = ((minutes >> i) & show_minutes)*color_red;
            minutes_green[i] = ((minutes >> i) & show_minutes)*color_green;
            minutes_blue[i]  = ((minutes >> i) & show_minutes)*color_blue;
            seconds_red[i]   = ((seconds >> i) & show_seconds)*color_red;
            seconds_green[i] = ((seconds >> i) & show_seconds)*color_green;
            seconds_blue[i]  = ((seconds >> i) & show_seconds)*color_blue;
        }
        
        // overwrite hours bit number 5 with PM indicator status, in its own color    
        hours_red[5]   = ((hours12 >> 5) & show_hours)*color_pm_red;
        hours_green[5] = ((hours12 >> 5) & show_hours)*color_pm_green;
        hours_blue[5]  = ((hours12 >> 5) & show_hours)*color_pm_blue;
        
        // only if we are in time-adjustment mode: highlight the row which we are
        // currently adjusting in white (either hours or minutes)
        if (menu_index == 1) {
            for (int i=0; i<=5; i++) {
                if ((hours_red[i] || hours_green[i] || hours_green[i]) == 0) {
                    hours_red[i]   = color_highlight_red;
                    hours_green[i] = color_highlight_green;
                    hours_blue[i]  = color_highlight_blue;
                }
            }
        } else if (menu_index == 2) {
            for (int i=0; i<=5; i++) {
                if ((minutes_red[i] || minutes_green[i] || minutes_green[i]) == 0) {
                    minutes_red[i]   = color_highlight_red;
                    minutes_green[i] = color_highlight_green;
                    minutes_blue[i]  = color_highlight_blue;
                }
            }
        }
        
        
        // *****
        // send time data to WS2812 LEDs
        // (we do this without loops because it would delay execution time, and this part is time critical)
        
        // we need to turn interrupts OFF during data transmission because this part is time critical
        GIE = 0;
        
        // wait a bit, just in case
        __delay_ms(1);
        
        // send hours data
        WS2812_send_RGB(hours_red[5], hours_green[5], hours_blue[5]);
        WS2812_send_RGB(hours_red[4], hours_green[4], hours_blue[4]);
        WS2812_send_RGB(hours_red[3], hours_green[3], hours_blue[3]);
        WS2812_send_RGB(hours_red[2], hours_green[2], hours_blue[2]);
        WS2812_send_RGB(hours_red[1], hours_green[1], hours_blue[1]);
        WS2812_send_RGB(hours_red[0], hours_green[0], hours_blue[0]);
        
        // send minutes data
        WS2812_send_RGB(minutes_red[0], minutes_green[0], minutes_blue[0]);
        WS2812_send_RGB(minutes_red[1], minutes_green[1], minutes_blue[1]);
        WS2812_send_RGB(minutes_red[2], minutes_green[2], minutes_blue[2]);
        WS2812_send_RGB(minutes_red[3], minutes_green[3], minutes_blue[3]);
        WS2812_send_RGB(minutes_red[4], minutes_green[4], minutes_blue[4]);
        WS2812_send_RGB(minutes_red[5], minutes_green[5], minutes_blue[5]);
        
        // send seconds data
        WS2812_send_RGB(seconds_red[5], seconds_green[5], seconds_blue[5]);
        WS2812_send_RGB(seconds_red[4], seconds_green[4], seconds_blue[4]);
        WS2812_send_RGB(seconds_red[3], seconds_green[3], seconds_blue[3]);
        WS2812_send_RGB(seconds_red[2], seconds_green[2], seconds_blue[2]);
        WS2812_send_RGB(seconds_red[1], seconds_green[1], seconds_blue[1]);
        WS2812_send_RGB(seconds_red[0], seconds_green[0], seconds_blue[0]);
        
        // wait a bit, just in case
        __delay_ms(1);
        
        // now it is safe to turn interrupts back on
        GIE = 1;
        
        
    }
    
    return;
    
}

// interrupt service routine (is called approximately 2930 times per second)
// (48MHz/4 clock speed, 8-bit counter, with 1:16 prescaler = 2930 Hz)
void __interrupt () isr (void) {  
 
    // timer overflow?
    if (TMR0IF) {
        
        // read out the current rotary encoder position
        rotary_pos = gray_to_binary();
        
        // calculate the difference to previous position
        rotary_diff = rotary_pos_before - rotary_pos;

        // turned clockwise
        if (((rotary_diff == -1) || (rotary_diff == 3))) {
            rotary_pos_before = rotary_pos;
            rotary_value++;
            
        // turned counter-clockwise
        } else if (((rotary_diff == 1) || (rotary_diff == -3))) {
            rotary_pos_before = rotary_pos;
            rotary_value--;
        
        // in this case, a step has been missed
        // (best practice: ignore it!)
        } else if ((rotary_diff == 2) || (rotary_diff == -2)) {
            
        }
        
        // clear the debounce buffer for the rotary encoder pushbutton,
        // but only if the pushbutton is not pressed
        if ((sw_buffer > 0) && !ENC_SW) {
            sw_buffer--;
        } 
        
        // clear timer flag
        TMR0IF = 0;
        
    }
    
}

// send out a byte b in WS2812 protocol
void WS2812_send_byte (unsigned char b) {

    if (b & 0b10000000) { WS2812_send_bit(1); } else { WS2812_send_bit(0); }
    if (b & 0b01000000) { WS2812_send_bit(1); } else { WS2812_send_bit(0); }
    if (b & 0b00100000) { WS2812_send_bit(1); } else { WS2812_send_bit(0); }
    if (b & 0b00010000) { WS2812_send_bit(1); } else { WS2812_send_bit(0); }
    if (b & 0b00001000) { WS2812_send_bit(1); } else { WS2812_send_bit(0); }
    if (b & 0b00000100) { WS2812_send_bit(1); } else { WS2812_send_bit(0); }
    if (b & 0b00000010) { WS2812_send_bit(1); } else { WS2812_send_bit(0); }
    if (b & 0b00000001) { WS2812_send_bit(1); } else { WS2812_send_bit(0); }
    
}

// send red, green, and blue values in WS2812 protocol
void WS2812_send_RGB (unsigned char r, unsigned char g, unsigned char b) {

    WS2812_send_byte(g);
    WS2812_send_byte(r);
    WS2812_send_byte(b);
    
}

// convert Gray code into a binary number
// 00 -> 0, 01 -> 1, 11 -> 2, 10 -> 3
int gray_to_binary () {
 
    if (ENC_A == 0 && ENC_B == 0) {
        return 0;
    } else if (ENC_A == 0 && ENC_B == 1) {
        return 1;
    } else if (ENC_A == 1 && ENC_B == 1) {
        return 2;
    } else {
        return 3;
    }
    
}

// send the byte "value" to the DS1302
void DS1302_send (int value) {
    
    // set data pin as output
    TRISC4 = 0;
    
    // auxiliary index variable
	int n = 0;
    
    // loop over all eight bits
	for (n = 0; n < 8; n++) {
        DS1302_IO = (value >> n) & 1;
        NOP(); NOP(); // important!
        DS1302_CLK = 1;
        DS1302_CLK = 0;
    }
    
    // set data pin back to 0
    DS1302_IO = 0;

}

// receive a byte from the DS1302
unsigned char DS1302_get (void) {
    
    // set data pin as input
    TRISC4 = 1;
    
    // dummy variables
    unsigned char value = 0;
    char n = 0;
    
    // collect all eight bits
    for (n = 0; n < 8; n++) {
        NOP(); NOP(); // important!
        value |= (DS1302_IO << n);
        DS1302_CLK = 1;
        DS1302_CLK = 0;
    }
    return value;

}

// convert decimal value "data" into BCD
// and store it in DS1302 at "address"
void DS1302_send_bcd (int address, int data) {
    
    int data10 = data / 10;
    int data1 = data % 10;
    DS1302_CE = 1;
    DS1302_send(address);
    DS1302_send((data10 << 4) | data1);
    DS1302_CE = 0;
    
}

// read a BCD value from DS1302 from "address"
// and convert it back into decimal
int DS1302_get_bcd (int address) {
    
    int tmp = 0, data10 = 0, data1 = 0;
    DS1302_CE = 1;
    DS1302_send(address);
    tmp = DS1302_get();
    DS1302_CE = 0;
    data10 = (tmp & 0b01110000) >> 4;
    data1  = tmp & 0b00001111;
    return 10*data10 + data1;
    
}

// short-hand functions that set the time directly
void DS1302_set_hours   (int data) { DS1302_send_bcd(0x84, data); }
void DS1302_set_minutes (int data) { DS1302_send_bcd(0x82, data); }
void DS1302_set_seconds (int data) { DS1302_send_bcd(0x80, data); }

// short-hand functions that read the time
int DS1302_get_hours   (void) { return DS1302_get_bcd(0x85); }
int DS1302_get_minutes (void) { return DS1302_get_bcd(0x83); }
int DS1302_get_seconds (void) { return DS1302_get_bcd(0x81); }

// initialize the DS1302
void DS1302_start (void) {
    
    // get the current seconds
    seconds = DS1302_get_seconds();
    
    // make sure DS1302 is running by clearing the CLOCK HALT signal
    // at bit 7 in the "seconds" register
    // (we need to extract the seconds first so they don't get deleted)
    DS1302_CE = 1;
    DS1302_send(0x80);
    DS1302_send(0<<7 | ((seconds / 10) << 4) | (seconds % 10));
    DS1302_CE = 0;
    
}

// stop the DS1302
void DS1302_stop (void) {
    
    // get the current seconds
    seconds = DS1302_get_seconds();
    
    // make sure DS1302 is stopped by setting the CLOCK HALT signal
    // at bit 7 in the "seconds" register
    // (we need to extract the seconds first so they don't get deleted)
    DS1302_CE = 1;
    DS1302_send(0x80);
    DS1302_send(1<<7 | ((seconds / 10) << 4) | (seconds % 10));
    DS1302_CE = 0;
    
}

// set the DS1302 to 24-hour mode
void DS1302_set24 (void) {
    
    // read full hours register (address 85h)
    int tmp=0;
    DS1302_CE = 1;
    DS1302_send(0x85);
    tmp = DS1302_get();
    DS1302_CE = 0;
    
    // is the 12-hour mode bit set?
    if (tmp >> 7) {
        
        // stop DS1302
        DS1302_stop();
        
        // set the mode back to 24-hour mode by clearing bit number 7
        DS1302_CE = 1;
        DS1302_send(0x84);
        DS1302_send(0);
        DS1302_CE = 0;
        
        // set hours back to previous value
        DS1302_CE = 1;
        DS1302_send(0x84);
        DS1302_send(tmp & 0b01111111);
        DS1302_CE = 0;
        
        // restart DS1302
        DS1302_start();
        
    }
    
}

// create the "red" component of the HSV color, based on h (which is between 0..255)
int hsv_red (int h) {

    // make sure h is really between 0..255
    h = h & 0xff;
    
    // this is my attempt at a piece-wise defined HSV curve
    if ((h >= 0) && (h <= 32)) {
        return (int) (255 - 2.66*h);
    } else if (h <= 64) {
        return 170;
    } else if (h <= 96) {
        return (int) (170 - 5.31*(h - 64));
    } else if (h <= 160) {
        return 0;
    } else {
        return (int) 2.68*(h - 160);
    }

}

// create the "green" component of the HSV color, based on h (which is between 0..255)
int hsv_green (int h) {
    
    // make sure h is really between 0..255
    h = h & 0xff;
    
    // this is my attempt at a piece-wise defined HSV curve
    if ((h >= 0) && (h <= 96)) {
        return (int) 2.66*h;
    } else if (h <= 128) {
        return (int) (255 - 2.66*(h - 96));
    } else if (h <= 160) {
        return (int) (170 - 5.31*(h - 128));
    } else {
        return 0;
    }
    
}

// create the "blue" component of the HSV color, based on h (which is between 0..255)
int hsv_blue (int h) {
    
    // make sure h is really between 0..255
    h = h & 0xff;
    
    // this is my attempt at a piece-wise defined HSV curve
    if ((h >= 0) && (h <= 96)) {
        return 0;
    } else if (h <= 128) {
        return (int) 2.66*(h - 96);
    } else if (h <= 160) {
        return (int) (85 + 5.31*(h - 128));
    } else {
        return (int) (255 - 2.66*(h - 160));
    }

}