/*---------------------------------------------------------------*/
/* GPS data logger R0.02 (C)ChaN, 2008                           */
/*---------------------------------------------------------------*/

#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <string.h>
#include "tff.h"
#include "diskio.h"


#define SYSCLK 10000000UL

#define BEEP_ON()       TCCR0B=0b011
#define BEEP_OFF()      TCCR0B=0b000
#define GPS_ON()        PORTB|=0x02
#define GPS_OFF()       PORTB&=0xFD
#define DELAY(dly)      for(Timer=dly;Timer;)

#define GPS_BAUDRATE    4800 // Enable USRAT0 in N81,9600bps

#define VTH_LOW         (WORD)(8000UL*100/3838)
#define VTH_HIGH        (WORD)(11500UL*100/3838)
#define POWER_check     0b01000000 | 1
#define ANALOG_IN1      0b01000000 | 2


FATFS fatfs;                            /* File system object for each logical drive */
FIL file1;                                      /* File object */
BYTE Buff[82];                          /* File/Console buffer */

BYTE Value1[4];                         // logged values
BYTE Value2[4];

uint16_t battery;                       // battery voltage
uint16_t intensity;                     // radiation intensity

volatile BYTE Timer;            /* 100Hz decrement timer */
volatile BYTE Stat;                     /* Status */


typedef struct _fifo {
        uint8_t idx_w;
        uint8_t idx_r;
        uint8_t count;
        uint8_t buff[150];
} FIFO;
volatile FIFO rxfifo;



/*---------------------------------------------------------*/
/* ADC interrupt                                           */
/*---------------------------------------------------------*/

ISR(ADC_vect)
{
WORD n;
static BYTE l, h;

    n = ADC;

    if(ADMUX == POWER_check)
    {
        if (n < VTH_LOW) {
                if (l >= 15) {
                        Stat |= 0x01;
                } 
                else {l++;}
        } 
        else {l = 0;}

        if (n > VTH_HIGH) {
                if (h >= 15) {
                        Stat &= 0xFE;
                } 
                else {h++;}
        } 
        else {h = 0;}

        battery = n;
        ADMUX = ANALOG_IN1;
    }

    if(ADMUX == ANALOG_IN1)
    {
        intensity = n;
        ADMUX = POWER_check;
    }

//!!!!
//Stat &= 0xFE;

        ADCSRA = _BV(ADEN)|_BV(ADSC)|_BV(ADIF)|_BV(ADIE)|0b111;
}


/*---------------------------------------------------------*/
/* 100Hz timer interrupt generated by OC1A                 */
/*---------------------------------------------------------*/


ISR(TIMER1_COMPA_vect)
{
        BYTE n;
        static WORD ivt_sync;


        n = Timer;
        if (n) Timer = n - 1;

        if (++ivt_sync >= 180 * 100) {
                ivt_sync = 0;
                Stat |= 4;
        }

        disk_timerproc();       /* Drive timer procedure of low level disk I/O module */

}



/*---------------------------------------------------------*/
/* User Provided Timer Function for FatFs module           */
/*---------------------------------------------------------*/
/* This is a real time clock service to be called from     */
/* FatFs module. Any valid time must be returned even if   */
/* the system does not support a real time clock.          */


DWORD get_fattime ()
{
        return    ((2007UL - 1980) << 25)       /* Fixed to 2007.5.1, 00:00:00 */
                        | ((5UL) << 21)
                        | ((1UL) << 16)
                        | (0 << 11)
                        | (0 << 5)
                        | (0 >> 1);
}


/*--------------------------------------------------------------------------*/
/* UART control */


static
void uart_init (void)
{
        cli();
        UCSR0B = 0;
        rxfifo.idx_r = 0;
        rxfifo.idx_w = 0;
        rxfifo.count = 0;
        UBRR0L = SYSCLK/16/GPS_BAUDRATE;        
        UCSR0B = _BV(RXCIE0)|_BV(RXEN0)|_BV(TXEN0);
        Stat &= 0xFD;   // Clear overflow flag
        sei();
}


static
void uart_stop (void)
{
        UCSR0B = 0;
}


/* Get a received character */
static
uint8_t uart_get ()
{
        uint8_t d, i;


        i = rxfifo.idx_r;
        if (rxfifo.count == 0) return 0;
        d = rxfifo.buff[i++];
        cli();
        rxfifo.count--;
        sei();
        if(i >= sizeof(rxfifo.buff))
                i = 0;
        rxfifo.idx_r = i;

        return d;
}

/* USART0 RXC interrupt */
ISR(USART_RX_vect)
{
        uint8_t d, n, i;


        d = UDR0;
        n = rxfifo.count;
        if(n < sizeof(rxfifo.buff)) {
                rxfifo.count = ++n;
                i = rxfifo.idx_w;
                rxfifo.buff[i++] = d;
                if(i >= sizeof(rxfifo.buff))
                        i = 0;
                rxfifo.idx_w = i;
        } else {
                Stat |= 2;
        }
}



/*----------------------------------------------------*/
/*  Get a line received from GPS module               */
/*----------------------------------------------------*/

static
BYTE get_line (void)    // 0: Power fail occured, >0: Number of bytes received.
{
        BYTE c, i = 0;


        for (;;) {
                if (Stat & 1) return 0; // When power fail is detected, return with zero.
                c = uart_get();
                if (Stat & 2) {                 // When buffer overflow has occured, restart to receive line.
                        uart_init();
                        i = 0; c = 0;
                }
                if (!c || (i == 0 && c != '$')) continue;
                Buff[i++] = c;
                if (c == '\n') break;
                if (i >= sizeof(Buff)) i = 0;
        }
        return i;
}



/*--------------------------------------------------------------------------*/
/* Controls                                                                 */

static
void beep (BYTE len, BYTE cnt)
{
        while (cnt--) {
                BEEP_ON();
                DELAY(len);
                BEEP_OFF();
                DELAY(len);
        }
}




/* Compare sentence header string */
static
BYTE gp_comp (BYTE *str1, const prog_uint8_t *str2)
{
        BYTE c;

        do {
                c = pgm_read_byte(str2++);
        } while (c && c == *str1++);
        return c;
}

/* Get a column item */
static
BYTE* gp_col (                  /* Returns pointer to the item (returns a NULL when not found) */
        const BYTE* buf,        /* Pointer to the sentence */
        BYTE col                        /* Column number (0 is the 1st item) */
) {
        BYTE c;


        while (col) {
                do {
                        c = *buf++;
                        if (c <= ' ') return NULL;
                } while (c != ',');
                col--;
        }
        return (BYTE*)buf;
}



static
void ioinit (void)
{
        PORTB = 0b00001101;             // Port B
        DDRB  = 0b00101110;
        PORTC = 0b00111111;             // Port C
        DDRC  = 0b00000000;
        PORTD = 0b10101110;             // Port D
        DDRD  = 0b01010010;

        SPCR = 0b01010000;                      /* Initialize SPI port (Mode 0) */
        SPSR = 0b00000001;

        OCR1A = SYSCLK/8/100-1;         // Timer1: 100Hz interval (OC1A)
        TCCR1B = 0b00001010;
        TIMSK1 = _BV(OCIE1A);           // Enable TC1.oca interrupt

        OCR0A = SYSCLK/64/4000/2-1;     // Timer0: 4kHz sound (OC0A)
        TCCR0A = 0b01000010;

        ADMUX = POWER_check;            // Select ADC input
        ADCSRA = _BV(ADEN)|_BV(ADSC)|_BV(ADIF)|_BV(ADIE)|0b111;

        sei();
}



/*-----------------------------------------------------------------------*/
/* Main                                                                  */


int main ()
{
        BYTE b, err, *p = NULL;
        WORD s;


        ioinit();
        f_mount(0, &fatfs);                                     /* Enable file I/O layer */

        for (;;) {
                uart_stop();
                GPS_OFF();
                Timer = 100;
                do {
                        if (Stat & 1) Timer = 100;
                } while (Timer);

                GPS_ON();
                Timer = 255;
                do {
                        if ((Stat & 1) || (disk_status(0) & STA_NODISK))  Timer = 255;
                } while (Timer);

                beep(5, 1);             // Single beep. Start to get current time.
                uart_init();
                do {                    // Wait for valid RMC sentence.
                        b = get_line();
                        if (!b) break;
                        if (gp_comp(Buff, PSTR("$GPRMC"))) continue;
                        p = gp_col(Buff,2);
                } while (!p || *p != 'A');
                if (!b) continue;
                p = gp_col(Buff,9);             // Open log file with the name of current date (YYMMDD.log in UTC).
                
                if (!p) {err = 3; break;}

                memcpy(&Buff[0], p+4, 2);
                memcpy(&Buff[2], p+2, 2);
                memcpy(&Buff[4], p+0, 2);
                strcpy_P(&Buff[6], PSTR(".log"));
                if (f_open(&file1, Buff, FA_OPEN_ALWAYS | FA_WRITE) || f_lseek(&file1, file1.fsize)) { err = 4; break; }

                beep(5, 2);             // Two beeps. Start logging.
                err = 0;
                while ((b = get_line()) > 0) {
                        if (   !gp_comp(Buff, PSTR("$GPGGA"))   // Which sentence is logged?
                                || !gp_comp(Buff, PSTR("$GPRMC"))
                        //      || !gp_comp(Buff, PSTR("$GPGSA"))
                        //      || !gp_comp(Buff, PSTR("$GPGLL"))
                        //      || !gp_comp(Buff, PSTR("$GPGSV"))
                        //      || !gp_comp(Buff, PSTR("$GPZDA"))
                        //      || !gp_comp(Buff, PSTR("$GPVTG"))
                        ) 
                        {
                                if (f_write(&file1, Buff, b, &s) || b != s) { err = 5; break; };
                                itoa(battery,&Value1,10);               // convert number to character
                                itoa(intensity,&Value2,10);
                                sprintf(Buff,"%d,%d\n",Value1,Value2);
                                if (f_write(&file1, Buff,strlen(Buff), &s) || (strlen(Buff) != s)) { err = 8; break; };
                        }
                        if ((Stat & 4) == 0) continue;
                        if (f_sync(&file1)) { err = 6; break; };// Synchronize the file in interval of 300 sec.
                        cli(); Stat &= 0xFB; sei();                             // Clear sync request
                }
                if (err) break;

                // Turn-off GPS power and close the log file by power supply is discharged.
                uart_stop();
                GPS_OFF();
                if (f_close(&file1)) { err = 7; break; };

                // When a long beep is sounded, the shutdoun process has been succeeded.
                beep(50, 1);
        }

        // Unrecoverble error. Enter shutdown state.
        uart_stop();
        GPS_OFF();
        beep(25, err);
        for (;;);
}