// Wakey - SES/Rover Sleep Controller
// Controls power supplies to shut down and wake up PC-104 stack.
// Runs on a PIC16F886 with a 4 MHz main crystal and a 32,768 Hz
// crystal on the Timer1 oscillator.
//
// Compiled with Hi-Tech PICC PRO v9.83.
// The following routines are unique to PICC and must be replaced if a different
// compiler is used:
// __CONFIG()
// __delay_ms()
// eeprom_read()
// eeprom_write()


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pic.h>		// register definitions for this PIC
#include "intserial.h"		// hardware serial I/O routines
#include "adc.h"                // analog-to-digital converter routines

__CONFIG(FOSC_HS & WDTE_ON & PWRTE_OFF & MCLRE_OFF & LVP_OFF & CPD_OFF & BOREN_OFF & DEBUG_ON);

#define _XTAL_FREQ 4000000      // must define for delay function to work
#define SER_BUFSIZE 16
#define CR  0x0D
#define LF  0x0A
#define BS  0x08

#define TMR1DLY 32768   // Timer1 delay value, 1 sec with 32,768 Hz crystal
                        //  and divide-by-1 prescaler
#define BATTV   0       // adc channel assignments
#define INHIBIT5V   RB1 // digital output channel assignments
#define INHIBIT12V  RB2
#define ENABLEDIV   RB3

#define SERNOADD    0   // EEPROM address of board serial number
#define VCALADD     1   // EEPROM address of voltage calibration

#define TRUE    1
#define FALSE   0

enum {RESETOK, RESETWD, WAKEOK, WAKEWD};    // Cause for last reset or wake

void sleep(void);
void setsleep(void);
void setdelay(void);
void readsleep(void);
void readdelay(void);
void set12von(void);
void set12voff(void);
void read12v(void);
void setbatt(void);
void readbatt(void);
void readwake(void);
void setserno(void);
void readserno(void);
void help(void);
void test(void);

void timer_setup(void);
void interrupt isr(void);

// Global vars
unsigned int    gSleepMins = 1; // number of minutes to sleep
unsigned int    gDelaySecs = 0; // number of seconds to wait before sleeping
char            g12VOn;         // status of 12 V power supply
char            gWakeCause;     // reason for last reset or wakeup
unsigned int    gGain;          // for debug


// The main loop waits for serial input commands, parses them, and calls the
// appropriate routine.
void main(void) {
    
    extern bit __timeout;   // timeout bit in status reg saved by c startup code
    unsigned char   i;      // index for command buffer
    unsigned char   inChar; // input char for command buffer
    unsigned char   cmdBuf[SER_BUFSIZE];    // command buffer

    // The first thing we do after reset is save the reason for the reset.
    // In Hi-Tech PICC, the __timeout bit is a shadow of the nTO bit in the
    // status register, and is only valid if the compiler option
    // --RUNTIME=resetbits is set. To set this in the MPLABX IDE, go to the menu
    // File -> Project Properties… -> Conf: [default] -> HI-TECH PICC -> Linker
    // and check the "Backup reset condition flags" option.

    gWakeCause = (__timeout ? RESETOK : RESETWD); // save reason for reset
    CLRWDT();               // clear watchdog timer
    WDTCON = 0x17;          // set watchdog timer to timeout after 2.1 seconds
    OPTION_REG = 0xF8;      // don't use timer0 prescaler

    // All I/O port pins must be set as output to prevent floating
    // high-impedance inputs from drawing too much current in sleep mode.
    // Note that the serial setup routine will set SREN = 1 and make the
    // serial receive pin RC7 an input. The timer setup routine will set
    // T1OSCEN = 1 and make the timer1 oscillator bits RC0 and RC1 inputs.
    TRISA = 0x01;           // make all porta pins except RA0 outputs
    ANSEL = 0x01;           // make all porta pins except RA0 digital
    TRISB = 0x00;           // make all portb pins outputs
    ANSELH = 0x00;          // make all portb pins digital
    TRISC = 0x00;           // make all portc pins outputs
    PORTA = 0;              // set all pins low
    PORTB = 0;
    PORTC = 0;

    INHIBIT5V = FALSE;      // turn 5V power supply ON
    __delay_ms(100);        // wait for power supply to stabilize
    INHIBIT12V = TRUE;      // turn 12V power supply OFF
    g12VOn = FALSE;

    di();                   // disable all interrupts
    timer_setup();          // set up timer
    serial_setup();         // set up the USART - settings defined in intserial.h
    adc_setup();            // set up ADC
    ei();                   // enable all interrupts

    printf("\r\nWakey v1.2.0\r\n");
    while(1) { // main loop
        i = 0;
        inChar = NULL;
        printf("\r\n> ");           // print prompt

        do {                        // read chars until carriage return
            inChar = getch();       // get the char
            if(inChar == BS) {      // if char is a backspace
                if(i > 0) {         //  and not at beginning of line
                    i--;
                    putch(inChar);  // erase last char
                    putch(' ');
                    putch(inChar);
                } // end if
            } else {                // otherwise put in buffer
                cmdBuf[i++] = inChar;
                putch(inChar);      // and echo char
            } // end if
        } while((inChar != CR) && (i < SER_BUFSIZE - 1));
        cmdBuf[i-1] = NULL;         // get rid of CR

        // parse command
        if(!cmdBuf[0]);     // empty command, do nothing
        else if(!strcmp(cmdBuf, "sleep")) sleep();
        else if(!strcmp(cmdBuf, "setsleep")) setsleep();
        else if(!strcmp(cmdBuf, "setdelay")) setdelay();
        else if(!strcmp(cmdBuf, "readsleep")) readsleep();
        else if(!strcmp(cmdBuf, "readdelay")) readdelay();
        else if(!strcmp(cmdBuf, "set12von")) set12von();
        else if(!strcmp(cmdBuf, "set12voff")) set12voff();
        else if(!strcmp(cmdBuf, "read12v")) read12v();
        else if(!strcmp(cmdBuf, "setbatt")) setbatt();
        else if(!strcmp(cmdBuf, "readbatt")) readbatt();
        else if(!strcmp(cmdBuf, "readwake")) readwake();
        else if(!strcmp(cmdBuf, "setserno")) setserno();
        else if(!strcmp(cmdBuf, "readserno")) readserno();
        else if(!strcmp(cmdBuf, "help") || !strcmp(cmdBuf, "?")) help();
        else if(!strcmp(cmdBuf, "test")) test();
        else printf("\r\nInvalid command!");
    } // end while
} // end main


// Sleep in a low-power state
void sleep(void) {

    unsigned int secsElapsed;
    unsigned int secsToSleep;

    secsToSleep = (60 * gSleepMins);
    printf("\r\nSleeping %u %s...\r\n", gSleepMins,
            gSleepMins == 1 ? "minute" : "minutes");

    // delay before sleeping
    for(secsElapsed = 0; secsElapsed < gDelaySecs; secsElapsed++) {
        CLRWDT();
        __delay_ms(1000);
    } // end for
    
    while(!TRMT);   // wait for last printf to finish sending
    RCIE = 0;       // disable serial receive interrupts
    SPEN = 0;       // turn off TX output so it won't power MAX3232
    PORTA = 0;      // set all pins low (except for power supply inhibits)
    PORTB = 0x06;   // this turns off both power supplies
    PORTC = 0;
    g12VOn = FALSE; // update status of 12V power supply
    TMR1ON = 0;     // stop timer1
    TMR1 = TMR1DLY; // load the timeout value
    TMR1ON = 1;     // start timer1
    TMR1IE = 1;     // enable timer1 interrupts
    GIE = 0;        // disable global interrupts
    // sleep until time elapsed or watchdog times out
    while((secsElapsed++ <= secsToSleep) && nTO) {
        SLEEP();
        // sleep here until timer1 interrupt after 1 second
        // entering sleep will clear the watchdog timer
        // as soon as awake, reload timer1 and start counting the next second
        TMR1ON = 0;     // stop timer1
        TMR1 = TMR1DLY; // load the timeout value
        TMR1ON = 1;     // start timer1
        TMR1IF = 0;     // clear the timer1 interrupt flag
    }; // end while
    TMR1ON = 0;     // stop timer1
    TMR1IE = 0;     // disable timer1 interrupts
    GIE = 1;        // enable global interrupts

    // save the reason for wakeup here before the next CLRWDT() erases it
    if(!nPD && nTO) gWakeCause = WAKEOK;    // if power-down sleep flag but not WD time-out
    else if(!nPD && !nTO) gWakeCause = WAKEWD;  // or power-down sleep flag and WD time-out

    INHIBIT5V = FALSE;  // turn 5V power supply ON
    __delay_ms(100);    // wait for power supply to stabilize
    serial_setup(); // reinitialize the USART
    printf("Awake!");
} // end sleep()


// Set the number of minutes to sleep
void setsleep(void) {

    unsigned int input;
    char inbuf[10];

    printf("\r\nEnter sleep time in minutes (1 - 1092): ");
    gets(inbuf);
    input = atoi(inbuf);
    if(input > 0 && input <= 1092) {
        gSleepMins = input;
        printf("\r\nSleep time set to %d %s\r\n", gSleepMins,
                gSleepMins == 1 ? "minute" : "minutes");
    } else {
        printf("\r\nInvalid time!\r\n");
    } // end if
} // end setsleep()


// Set the number of seconds to delay before sleeping
void setdelay(void) {

    unsigned int input;
    char inbuf[10];

    printf("\r\nEnter delay time in seconds (0 - 60): ");
    gets(inbuf);
    input = atoi(inbuf);
    if(input >= 0 && input <= 60) {
        gDelaySecs = input;
        printf("\r\nDelay time set to %d %s\r\n", gDelaySecs,
                gDelaySecs == 1 ? "second" : "seconds");
    } else {
        printf("\r\nInvalid time!\r\n");
    } // end if
} // end setdelay()


// Show how many minutes Wakey will sleep
void readsleep(void) {

    printf("\r\nSleep time is %u %s\r\n",gSleepMins,
            gSleepMins == 1 ? "minute" : "minutes");
} // end readsleep()


// Show how many seconds Wakey will delay before sleeping
void readdelay(void) {

    printf("\r\nDelay time is %u %s\r\n", gDelaySecs,
                gDelaySecs == 1 ? "second" : "seconds");
} // end readdelay()


// Turn on the 12 V power supply
void set12von(void) {

    INHIBIT12V = FALSE;      // turn 12V power supply ON
    g12VOn = TRUE;
    printf("\r\n12V is ON\r\n");
} // end set12von


// Turn off the 12 V power supply
void set12voff(void) {

    INHIBIT12V = TRUE;      // turn 12V power supply OFF
    g12VOn = FALSE;
    printf("\r\n12V is OFF\r\n");
} // end set12von


// Show the current state of the 12 V power supply
void read12v(void) {

    printf("\r\n12V is %s\r\n", (g12VOn ? "ON" : "OFF"));
} // end read12v()


// Set the calibration for reading battery voltage
void setbatt(void) {

    unsigned int temp;
    unsigned int volts;
    unsigned int input;
    unsigned int gain;
    char inbuf[10];

    printf("\r\nEnter actual input voltage in decivolts (200 - 320, 0 to abort): ");
    gets(inbuf);
    input = atoi(inbuf);
    if(input >= 200 && input <= 320) {
        volts = input;          // save the actual voltage
        TRISA = 0x01;           // set up ADC input pin to read battery voltage
        ANSEL = 0x01;
        ENABLEDIV = TRUE;       // turn on voltage divider
        __delay_ms(100);        // wait for divider capacitor to charge
        temp = adc_read(BATTV); // read the ADC value
        ENABLEDIV = FALSE;      // turn off voltage divider
        gain = volts * 1000L / temp;   // calculate the gain

        // save gain calibration to EEPROM
        eeprom_write(VCALADD, (unsigned char) (gain >> 8));
        eeprom_write(VCALADD+1, (unsigned char) (gain & 0x00ff));
        printf("\r\nGain value of %u saved to EEPROM\r\n", gain);
    } else {
        printf("\r\nInvalid voltage!\r\n");
    } // end if

} // end setbatt()


// Read the battery voltage on Wakey's power input
void readbatt(void) {

    unsigned int temp;
    unsigned int volts;
    unsigned long gain;

    // retrieve the voltage calibration from EEPROM
    gain = (eeprom_read(VCALADD) << 8) + eeprom_read(VCALADD+1);

    TRISA = 0x01;           // set up the ADC input pin
    ANSEL = 0x01;
    ENABLEDIV = TRUE;       // turn on voltage divider
    __delay_ms(100);        // wait for divider capacitor to charge
    temp = adc_read(BATTV); // read the divider voltage
    ENABLEDIV = FALSE;      // turn off voltage divider
    
    // This calculation will result in decivolts, or volts x 10
    volts = (unsigned int) (((temp * gain) + 500) / 1000); // convert to voltage
    printf("\r\n%u.%u\r\n", volts / 10, volts % 10);
} // end readbatt()


// Read the reason for the last wakeup
void readwake(void) {

    switch(gWakeCause) {
        case RESETOK:
            printf("\r\nnormal reset\r\n");
            break;
        case RESETWD:
            printf("\r\nwatchdog reset\r\n");
            break;
        case WAKEOK:
            printf("\r\nnormal wakeup\r\n");
            break;
        case WAKEWD:
            printf("\r\nwatchdog wakeup\r\n");
            break;
        default:
            printf("\r\nunknown\r\n");
            break;
    } // end switch
} // end readwake()


// Set the board serial number and save it to EEPROM memory
void setserno(void) {

    unsigned char serno;
    unsigned int input;
    char inbuf[10];

    printf("\r\nEnter serial number (1 - 255, 0 to abort): ");
    gets(inbuf);
    input = atoi(inbuf);
    if(input > 0 && input <= 255) {
        serno = input;
        eeprom_write(SERNOADD, serno);
        printf("\r\nSerial number %u saved to EEPROM\r\n", serno);
    } else {
        printf("\r\nInvalid serial number!\r\n");
    } // end if

}; // end setserno()


// Read the board serial number
void readserno(void) {

    unsigned char serno;

    serno = eeprom_read(SERNOADD);
    printf("\r\n%u\r\n", serno);

}; // end readserno()


// Print help text
void help(void) {
    printf("\r\nValid commands are:\r\n");
    printf("sleep\t\tsleep after delay\r\n");
    printf("setsleep\tset sleep time in minutes\r\n");
    printf("setdelay\tset delay time in seconds\r\n");
    printf("readsleep\tread sleep time\r\n");
    printf("readdelay\tread delay time\r\n");
    printf("set12von\tturn 12V on\r\n");
    printf("set12voff\tturn 12V off\r\n");
    printf("read12v\t\tread state of 12V\r\n");
    printf("setbatt\t\tset battery voltage calibration\r\n");
    printf("readbatt\tread battery voltage\r\n");
    printf("readwake\tread cause of last wakeup\r\n");
    printf("setserno\tset serial number\r\n");
    printf("readserno\tread serial number\r\n");
    printf("help or ?\tprint command list\r\n");
} // end help()


// Test command for testing various functions during development
void test(void) {
    while(1);   // cause a watchdog reset
} // end test()


// Set up Timer1 to be used as the sleep timer
void timer_setup(void) {

    // Timer1 initialization. Timer1 runs at 32,768 Hz.

    T1CON =     0b00001111;
    //            \/\/||||_ power on timer
    //             | ||||__ select external clock source
    //             | |||___ don't synchronize external clock input
    //             | ||____ power up oscillator
    //             | |_____ divide by 1 prescaler
    //             |_______ not used

    TMR1IE = 0;         // don't enable Timer1 interrupt yet
    PEIE = 1;           // enable peripheral interrupts
} // end timer_setup()


// Interrupt service routine. Every interrupt will call this routine.
void interrupt isr(void) {

    ser_int();      // service serial port (this macro defined in intserial.h)

    // This timer1 code should never execute because timer1 interrupts are only
    // used with global interrupts disabled in order to awaken from sleep.
    // This handler is here just in case a timer1 interrupt occurs with global
    // interrupts enabled.
    if ((TMR1IE) && (TMR1IF)) {
        TMR1IF = 0; // clear timer1 interrupt flag
    } // end if

} // end isr()
