/***
 * Main program to evaluate the various functions of the MiniFlex-PIC
 * Copyright (c) 2003, Inventronik
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the Inventronik nor the names of its contributors may be
 * used to endorse or promote products derived from this software without specific
 * prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * $Id: appl.c,v 1.1 2005/01/27 18:14:08 jensi Exp $ *
 *
 * new: 2. Dec. 03 add led on/off for testing purposes
 *      2. Feb. 04 added mem protection for the bootloader
 *      25. Jan. 05 added compiler flag to distinguish between PCM/PCH compilers (16f/18f devices)
 *
 */

#if defined(__PCM__)
#include <16f876.h>
#fuses XT, NOWDT, NOPROTECT, PUT, NOBROWNOUT, NOLVP
#elif defined(__PCH__)
#include <18f252.h>
#fuses EC, NOWDT, NOPROTECT, PUT, NOBROWNOUT, NOLVP
#endif

#zero_ram // erase all used ram cells at initialisation.
#device adc=10

#use delay(clock=6000000)
#use rs232(baud=19200, xmit=PIN_C6, rcv=PIN_C7, ERRORS)

#include <ctype.h>
#include <stddef.h>
#include <string.h>

#define SDA_LINE PIN_C4
#define SCL_LINE PIN_C3
#include "../eep2465/eep2465.c"
#include "../rtc4513/rtc4513.c"

#define ADC_CHANNEL 0 // 0..4 channels available

/* reserve space for the bootstrap loader */
#ifdef(__PCM__)
#ORG 0x1F00,0x1FFF //for the 8k 16F876/7
void loader() { }
#endif

// buffer and pointers to the rs232 rcv queue
#define BUFFER_SIZE 32
byte rcvbuf[BUFFER_SIZE], cmdbuf[BUFFER_SIZE];
int inPtr=0, outPtr=0;
int cmdRcv=0;
short ignoreCRLF=0; // used to handle 2-byte delimiter (cr/lf)
short echo=1; // echo each char back to the sender

// variables used for cyclic ad conversion
long int schedule=0,   //  
    shrs, smin, ssec;  // time when the ad logging has to be launched
unsigned long timer_counter=0L, adc=0L;
short new_adc_avail=0;

// logging
#define NOLOG 0 // no logging
#define ADLOG 1 // normal logging
#define STLOG 2 // scheduled logging

#define EEPROM_START_ADDRESS 0
#define WRITE_INT_EEPROM_CYCLE 100
int logMode = NOLOG;
unsigned long logEntries=0L, writeIntEEPROM=0L;

// prototypes
void serial_isr(void);
void clock(void);
unsigned long get_adc(int channel);
int calc_checksum(int *bPtr, int len);
int print_log(void);
void print_time(void);
void print_date(void);
char *get_int(char *cptr, signed long *retval);
#separate int rcv_cmd(void);
void execute_scheduled(void);
#separate void print_log_content(void);
#separate int parse_cmd(char *cptr);
#separate void start_log(int reset);
void setbus(int addr, int data);
void main(void);

// internal eeprom structure
struct int_eep_struct {
    int dd, dm, dy;  // start date;
    int sh, sm, ss;  // start time;
    int eh, em, es;  // end time
    long int numlog; // number of logged entries
    int crc;         // simple crc
} int_eep;

/***
 * interrupt routine to receive a character from the serial port
 * (the usb port is handled through a usb<->rs232 converter)
 */
#int_rda
void serial_isr(void)
{
    byte c, idx;

    c = getc();
    if(c==0x11) // cntl char XON (ASCII 17) ignore
        return; // for modem emultion often times XON/XOFF is on by default

    if(echo) // local echo
        putc(c);

    idx = inPtr;
    inPtr = (inPtr+1) % BUFFER_SIZE;
    if(inPtr==outPtr) { // buffer overflow
        // buffer full - reset inPtr, outPtr
        ignoreCRLF = inPtr = outPtr = cmdRcv = 0; 
    } 
    else if(c=='\n' || c=='\r') {
        if(!ignoreCRLF) {
            cmdRcv++; // indicates a command has been received
            rcvbuf[idx] = '\0';
            ignoreCRLF = 1;
        } 
        else {
            ignoreCRLF = 0;
            inPtr = idx;
        }
    } 
    else {
        rcvbuf[idx] = c;
        ignoreCRLF = 0;
    }
}

#int_rtcc
void clock(void)
{
    if(schedule && logMode!=STLOG) { // global int that indicates cycle operations
        ++timer_counter;
        if(timer_counter >= (183L*schedule)) {
            timer_counter = 0;
            adc = get_adc(ADC_CHANNEL);
            new_adc_avail = 1;
        }
    }
}

/***
 * retrieves a adc value from defined channel
 */
unsigned long get_adc(int channel)
{
    set_adc_channel(channel);
    return read_adc();
}

/***
 * easy checksum to check the eeprom content
 */
int calc_checksum(int *bPtr, int len)
{
    int sum=0, i;

    for(i=0; i<len; i++)
        sum += *bPtr++;
    return sum;
}

/***
 * print all eeprom infos 
 */
#separate
void print_log_content(void)
{
    unsigned long int i;

    printf("Start date: %d.%d.%ld\r\n", int_eep.dd, 
        int_eep.dm, int_eep.dy+2000);
    printf("Start time: %02d:%02d:%02d\r\n", int_eep.sh, 
        int_eep.sm, int_eep.ss);
    printf("End time: %02d:%02d:%02d\r\n", int_eep.eh, 
        int_eep.em, int_eep.es);
    printf("Number of logged entries: %ld\r\n", int_eep.numlog);

    for(i=0; i<int_eep.numlog; i++) {
        printf("ad: %ld\r\n", read_longint_ext_eeprom(i));
    }
}

/***
 * print content of the external eeprom + the timestamp values
 */
int print_log(void)
{
    char *bPtr = (char *) &int_eep;
    unsigned long i;
    int crc, ret=1;

    disable_interrupts(GLOBAL);
    delay_us(100);

    // read internal eeprom    
    for(i=0; i<sizeof(struct int_eep_struct); i++) {
        *bPtr++ = read_eeprom(i+EEPROM_START_ADDRESS);
    }

    // check crc
    bPtr = (char *) &int_eep;
    crc=calc_checksum(bPtr, sizeof(struct int_eep_struct)-sizeof(int));

    if(int_eep.crc==crc) {
        print_log_content();
    }
    else {
        printf("CRC-Error while reading internal eeprom (%d!=%d)\r\n",
            crc, int_eep.crc);
        ret=0;
    }
    enable_interrupts(GLOBAL);
    return ret;
}


/***
 * print current time/date
 */
void print_time(void)
{
    int i1, i2, i3;
    i1 = gettime('h', 1);
    i2 = gettime('m', 0);
    i3 = gettime('s', 0);
    printf("%02d:%02d:%02d\r\n", i1, i2, i3);
}

void print_date(void)
{
    int i1, i2, i3;
    i1 = getdate('d', 1);
    i2 = getdate('m', 0);
    i3 = getdate('y', 0);
    printf("%02d.%02d.%ld\r\n", i1, i2, i3+2000);
}


/***
 * Start logging (setup internal eeprom structure)
 */
#separate
void start_log(short reset)
{
    char *bPtr = (char *) &int_eep;
    int i;

    // initalize internal eeprom    
    for(i=0; i<sizeof(struct int_eep_struct); i++) {
        *bPtr++ = 0;
    }

    // save start date and time
    if(!reset) {
        int_eep.sh = gettime('h', 1);
        int_eep.sm = gettime('m', 0);
        int_eep.ss = gettime('s', 0);
        int_eep.dd = getdate('d', 1);
        int_eep.dm = getdate('m', 0);
        int_eep.dy = getdate('y', 0);
    }

    bPtr = (char *) &int_eep;
    int_eep.crc = calc_checksum(bPtr, sizeof(struct int_eep_struct)-sizeof(int));

    disable_interrupts(GLOBAL);
    delay_us(100);

    // write internal eeprom    
    bPtr = (char *) &int_eep;
    for(i=0; i<sizeof(struct int_eep_struct); i++) {
        write_eeprom(i+EEPROM_START_ADDRESS, *bPtr++);
    }
    enable_interrupts(GLOBAL);
}

/***
 * Finish logging (write endtime and crc)
 */
void finish_log(void)
{
    char *bPtr = (char *) &int_eep;
    int i;

    int_eep.eh = gettime('h', 1);
    int_eep.em = gettime('m', 0);
    int_eep.es = gettime('s', 0);

    bPtr = (char *) &int_eep;
    int_eep.crc = calc_checksum(bPtr, sizeof(struct int_eep_struct)-sizeof(int));
    disable_interrupts(GLOBAL);
    delay_us(100);

    // jump over 6 int values
    for(i=sizeof(int)*6; i<sizeof(struct int_eep_struct); i++) {
        write_eeprom(EEPROM_START_ADDRESS+i, *(bPtr+i));
    }
    enable_interrupts(GLOBAL);
}
/***
 * extracts an integer from a string
 * the function also moves the cptr to the next possible location
 */
char *get_int(char *cptr, signed long *retval)
{
    long mult=1;
    int i=0, j=2;

    *retval = 0;
    while(*cptr && !isdigit(*cptr)) // override whitespace and non digits
        cptr++;
    while(isdigit(*cptr++)) {
        i++;
    }

    if(i>0) {
        for(; i>0; i--, j++) {
           *retval += (*(cptr-j)-'0')*mult;
           mult *= 10;
        }
    } 
    else {
        *retval = (long)-1; // not a valid int
    }
    return cptr;
}

/***
 * print some instructions
 */
#separate
void help(void)
{
    printf("Available commands [optional] <needed>\r\n");
    printf("st <time>   - set time: format hh:mm:ss\r\n");
    printf("gt          - get time\r\n");
    printf("sd <date>   - set date: format dd.mm.yy\r\n");
    printf("gd          - get date\r\n");
    printf("ad [delay]  - read from ad-converter (channel 0): delay in s\r\n");
    printf("              without delay it returns a single value\r\n");
    printf("la <delay> <#entries> - log adc values into external eeprom\r\n");
    printf("              delay in sec, number of #entries before it stops\r\n");
    printf("tl <time> <delay> <#entries> - log adc values into external\r\n");
    printf("              eeprom starting at time: delay in sec, number of\r\n");
    printf("              #entries before it stops.\r\n");
    printf("sl          - stop logging (stops ad, la, tl commands)\r\n"); 
    printf("pe          - prints previously logged entries\r\n");
    printf("de          - delete entire eeprom\r\n");
    printf("le <int>,<bool> - switch led (0..3) on/off<1/0>\r\n");
    printf("ec <bool>   - echo <1/0> (default on=1)\r\n");
    printf("sb <addr>,<data> - set bus address (a0-a5:ra0-ra5)\r\n");
    printf("                 data (d0-d7:rb0-rb3,rc0-rc2,rc5)\r\n");
    printf("he          - this help\r\n");
}

/***
 * parse and execute a command
 */
#separate
int parse_cmd(char *cptr)
{
    signed long int i1=0, i2=0, i3=0, cycle=0, tmp;
    int ret=1;
    char c1, c2;

    #ifdef(__DEBUG)
        printf("cmd: '%s'\r\n", cptr);
    #endif 
    c1=*cptr; c2=*(cptr+1);

    // set time/date (st time - time format hh:mm:ss or sd date day.mon.year)
    if(c1=='s' && (c2=='t' || c2=='d')) {
        // try to get h:m:s or d.m.y
        cptr += 2;
        cptr = get_int(cptr, &i1);
        cptr = get_int(cptr, &i2);
        get_int(cptr, &i3);

        // time setting
        if(c2=='t') {
            if(i1>=0 && i1<24 && i2>=0 && i2<60 && i3>=0 && i3<60) {
                settime(i1, i2, i3);
            } 
            else {
                printf("Error: unknown time format\r\n");
                ret = 2;
            }
        } 
        else { // date setting
            if(i1>=0 && i1<=31 && i2>=0 && i2<=12 && i3>=0 && i3<=99) {
                setdate(i1, i2, i3);
            } 
            else {
                printf("Error: unknown date format\r\n");
                ret = 2;
            }
        }
    }
    // get time/date (gt returns time format: hh:mm:ss)
    else if(c1=='g' && (c2=='t' || c2=='d')) {
        if(c2=='t') {
            print_time();
        } 
        else { // (gd return dd.mm.yyy)
            print_date();
        }
    } 
    // get A/D-value (ad [cycle in sec] optional (without arg stops cycle)
    else if(c1=='a' && c2=='d') {
        get_int(cptr+2, &cycle);
        printf("ad: %ld\r\n", get_adc(ADC_CHANNEL));
        logMode = NOLOG;
        timer_counter = 0;
        schedule = (cycle<0) ? 0 : cycle;
        ret = 2; // suppress OK or error msg
    }
    // log A/D-values: la [cycle in sec] [num logs] (without arg stops cycle)
    else if(c1=='l' && c2=='a') {
        cptr = get_int(cptr+2, &cycle);
        timer_counter = 0;
        get_int(cptr, &logEntries);
        schedule = (cycle<0) ? 0 : cycle;

        if(logEntries>0 && logEntries<=4096) {
            logMode = ADLOG;
            printf("Logging %ld values ...\r\n", logEntries);
            start_log(0);
        } 
        else {
            logMode = NOLOG;
            printf("Max. 4096 logging values allowed!\r\n");
            ret = 2;
        }
    }
    // timed logging: tl <time> <cycle in sec> <num logs> 
    else if(c1=='t' && c2=='l') {
        cptr = get_int(cptr+2, &shrs);
        cptr = get_int(cptr, &smin);
        cptr = get_int(cptr, &ssec);
        cptr = get_int(cptr, &cycle);
        get_int(cptr, &logEntries);

        if(shrs<0 && shrs>=24 && 
           smin<0 && smin>=60 && 
           ssec<0 && ssec>=60) {
            printf("Error: unknown time format\r\n");
            ret = 2;
        }
        if(logEntries<0 && logEntries>4096) {
            printf("Max. 4096 logging values allowed!\r\n");
            ret = 2;
        }
        if(ret!=2) {
            printf("Schedule logging at %02ld:%02ld:%02ld, %ld values (%ld sec delay)\r\n", 
                shrs, smin, ssec, logEntries, cycle);
            schedule = (cycle<0) ? 0 : cycle;
            logMode = STLOG;
        }
    } 
    else if(c1=='s' && c2=='l') { // stop logging
        printf("Stop logging\r\n");
        logMode = NOLOG;
        schedule = 0;
    }
    else if(c1=='e' && c2=='c') { // echo on/off
        cptr = get_int(cptr+2, &tmp);
        echo = (short)tmp;
        printf("Echo: %d\r\n", (int)echo);
    }
    // delete external eeprom (de)
    else if(c1=='d' && c2=='e') {
        printf("Deleting external eeprom; this may take a while ...\r\n");
        delete_ext_eeprom();
        start_log(1); // reset internal eeprom
    } 
    // prints all values of the external eeprom (pe)
    else if(c1=='p' && c2=='e') {
        ret = print_log();
    }
    // switch leds on/off
    else if(c1=='l' && c2=='e') { 
        cptr = get_int(cptr+2, &i1);
        cptr = get_int(cptr, &i2);
        printf("LED %d: %d\r\n", (int)i1, (int)i2);
        switch(i1) {
            case 0: output_bit(PIN_B0, i2); break;
            case 1: output_bit(PIN_B7, i2); break;
#ifdef NO_RS232
            case 2: output_bit(PIN_C7, i2); break;
#else
            case 2: printf("not available\r\n"); break;
#endif
            case 3: output_bit(PIN_A0, i2); break;
        }
    }
    else if(c1=='s' && c2=='b') { // set bus
        int i;
        cptr = get_int(cptr+2, &i1);
        cptr = get_int(cptr, &i2);
        printf("addr: 0x%x, data: 0x%x\r\n", i1, i2);
        setbus(i1, i2);
    }
    // print usage of the program
    else if(c1=='h' && c2=='e') {
        help();
    } 
    else {
        ret = 0;
    }
    return ret;
}

/**
 * Set a few bits for a simple bus interface
 */
void setbus(int addr, int data)
{
    int i;
    output_a(addr);
    for(i=0; i<8; i++) {
        switch(i) {
            case 0: output_bit(PIN_B0, data&(1<<i)); break;
            case 1: output_bit(PIN_B1, data&(1<<i)); break;
            case 2: output_bit(PIN_B2, data&(1<<i)); break;
            case 3: output_bit(PIN_B3, data&(1<<i)); break;
            case 4: output_bit(PIN_C0, data&(1<<i)); break;
            case 5: output_bit(PIN_C1, data&(1<<i)); break;
            case 6: output_bit(PIN_C2, data&(1<<i)); break;
            case 7: output_bit(PIN_C5, data&(1<<i)); break;
        }
    }
}



/***
 * retrieve a command from rcvbuf
 */
#separate
int rcv_cmd(void)
{
    byte c=0;
    int i=0, ret;

    if(inPtr!=outPtr && cmdRcv) {
        do {
            c = cmdbuf[i++] = rcvbuf[outPtr];
            outPtr=(outPtr+1) % BUFFER_SIZE;
            if(c=='\0')
                cmdRcv--;
        } while(inPtr!=outPtr && c!='\0');

        ret = parse_cmd(cmdbuf);
        // no echo if ret > 1
        if(ret==1) {
            printf("OK\r\n");
        }
        else if(ret<=0) {
            printf("Unknown or wrong command\r\n");
        }
    }
    return 0;
}


/***
 * execute scheduled tasks (a/d conversion)
 */
void execute_scheduled(void)
{
    int i;
    char *bPtr;
    static int timed=0;

    // check if we need to start the timed logging
    timed++;
    if(logMode==STLOG && timed==100) {
        timed=0;
        if(cmptime(shrs, smin, ssec)>=0) {
            printf("Start timed logging\r\n");
            logMode = ADLOG;
            start_log(0);
        }
    }

    if(schedule && new_adc_avail && logMode!=STLOG) {
        disable_interrupts(int_rtcc); // timer overflow interrupt.
        new_adc_avail = 0;              // reset adc value
        enable_interrupts(int_rtcc);  // timer overflow interrupt.
        switch(logMode) {
            case ADLOG:
                // 1st write logging info into internal eeprom (from time to time)
                if(!(writeIntEEPROM % WRITE_INT_EEPROM_CYCLE)) {
                    finish_log();
                    writeIntEEPROM++;
                }

                // 2nd write ad value into external eeprom
                logEntries--;
                write_longint_ext_eeprom(int_eep.numLog++, adc);
            default:
                printf("ad: %ld\r\n", adc);
                if(logMode==ADLOG && logEntries==0) {
                    schedule = 0; // stop scheduling
                    finish_log();
                    printf("Logging finished\r\nOK\r\n");
                }
        }
    }
}

/***
 * main function
 */
void main(void)
{
    enable_interrupts(int_rda);
    enable_interrupts(int_rtcc); // timer overflow interrupt.

    // see data sheet, 
    // timer irp all 6000000 (6MHz)/256/4/32
    // 5,451 ms
#if defined(__PCM__)
    setup_counters(rtcc_internal, rtcc_div_32);
#elif defined(__PCH__)
    setup_counters(rtcc_internal, rtcc_div_32 | rtcc_8_bit);
#endif
    setup_adc_ports(ALL_ANALOG);
    setup_adc(ADC_CLOCK_INTERNAL);
    rtc_init();
    enable_interrupts(global);
    delay_ms(500);

    printf("\r\nStart MiniFlex-USB-Test Application ...\r\n");
    printf("    try 'he' command to get an overview\r\n\r\n");
    while(1) {
        execute_scheduled();
        rcv_cmd();
        delay_ms(10); 
    }
}
