/***
 * Binary clock, code example for the usage of the MiniFlex-USB
 * 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: binclk.c,v 1.1 2005/01/27 18:14:09 jensi Exp $ *
 *
 * new 2. Feb. 04 - added mem protection for the bootloader
 */

#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>
#include "../rtc4513/rtc4513.c"

/* 7-bit data to setup the digit LEDs */
#define D0 PIN_C0
#define D1 PIN_C1
#define D2 PIN_C2
#define D3 PIN_C5
#define D4 PIN_B1
#define D5 PIN_B2
#define D6 PIN_B3

/* multiplexer pins */
#define ENA_H PIN_A1
#define ENA_M PIN_A2
#define ENA_S PIN_A3

/* reserve space for the bootstrap loader */
#ORG 0x1F00,0x1FFF //for the 8k 16F876/7
void loader() { }

#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)

/***
 * 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

    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;
    }
}

/***
 * 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;
}

/***
 * parse and execute a command
 */
#separate
int parse_cmd(char *cptr)
{
    signed long int i1=0, i2=0, i3=0;
    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') {
        // 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;
            }
        } 
    }
    return ret;
}

/***
 * retrieve a command from rcvbuf
 */
int rcv_timecmd(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;
}

void switch_leds(int time)
{
    output_bit(D0, time & 1);
    output_bit(D1, time & (1 << 1));
    output_bit(D2, time & (1 << 2));
    output_bit(D3, time & (1 << 3));
    output_bit(D4, time & (1 << 4));
    output_bit(D5, time & (1 << 5));
    output_bit(D6, time & (1 << 6));
}

void set_clock_leds(char type, int time)
{
    int tl, th;

    tl = time%10;
    th = time/10;
    time = th << 4 | tl;
    switch(type) {
        case 'h':
            output_low(ENA_M);
            output_low(ENA_S);
            delay_us(20);
            switch_leds(time);
            output_high(ENA_H);
            break;
        case 'm':
            output_low(ENA_H);
            output_low(ENA_S);
            delay_us(20);
            switch_leds(time);
            output_high(ENA_M);
            break;
        case 's':
            output_low(ENA_M);
            output_low(ENA_H);
            delay_us(20);
            switch_leds(time);
            output_high(ENA_S);
            break;
    }
}

void main(void)
{
    int h, m, s, i;

    set_tris_a(0xf1); 
    set_tris_b(0xf1);
    set_tris_c(0xd8); 

    enable_interrupts(int_rda);
    enable_interrupts(global);
    delay_ms(500);

    printf("\r\nStart binary clock ...\r\n");

/* 
    output_bit(D0, 1);
    output_bit(D1, 1);
    output_bit(D2, 1);
    output_bit(D3, 1);
    output_bit(D4, 1);
    output_bit(D5, 1);
    output_bit(D6, 1);

    output_bit(ENA_S, 1);
    output_bit(ENA_M, 1);
    output_bit(ENA_H, 1);
    for(;;);
 */
    while(1) {
        // lets check if we received some time setting command from
        // the PC
        rcv_timecmd();
        
        h = gettime('h', 1);
        m = gettime('m', 0);
        s = gettime('s', 0);
        
        // we have to multiplex the current time
        i=22;
        while(i>0) { // about 200ms until we leave and get the current time again
            set_clock_leds('h', h);
            delay_ms(3);
            set_clock_leds('m', m);
            delay_ms(3);
            set_clock_leds('s', s);
            delay_ms(3);
            i--;
        }
    }
}
