#include "lcdthermo.h"

#use fast_io(A)
#use fast_io(B)
#use fast_io(C)
#use fast_io(D)
#use fast_io(E)
#use fast_io(F)
#use fast_io(G)


// DS1821 pins
#define TOUCH_PIN PIN_A3
#define TOUCH_VDD PIN_A2

// for temperature measuring
#define TEMP_TIME 10  /* seconds */

// for keyboard handling
#define KBDCNT_BNC 1
#define KBDCNT_LNG 16
#define KBDCNT_RPT 20

#define KEY_NRM 0
#define KEY_LNG 3
#define KEY_RPT 6
#define KEY1 1
#define KEY2 2
#define KEY3 3

static int lastkbd = 0b11100000;
static int kbdcnt = 0;


// for RTC
static int tick = 0;
static int sec = 0;
static int min = 0;
static int hour = 0;
static int day = 1;
static int month = 1;
const  int days_per_month[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};


// for LCD
static int1 redraw = true;
static int1 blanking = false;
static int dig0 = 0xFF;
static int dig1 = 0xFF;
static int dig2 = 0xFF;
static int dig3 = 0xFF;

// for temperature
static signed int16 temperature = 0;
static int temp_delay = TEMP_TIME;

//   digit0 map
//   bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0
//    -    +    <    :                   ab
#define DIGIT_0_SEG COM0+02,COM0+24,COM0+25,COM0+30,\
                    COM0+01,COM0+01,COM0+01,COM0+01

#define SIGN_COL 0x10
#define SIGN_PM  0x20
#define SIGN_POS 0x40
#define SIGN_NEG 0x80

//   digit1..digit3 maps
//   bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0
//    dp   a    b    c    d    e    f    g
#define DIGIT_1_SEG COM0+00,COM0+28,COM0+29,COM0+33,\
                    COM0+34,COM0+35,COM0+27,COM0+26
#define DIGIT_2_SEG COM0+32,COM0+16,COM0+11,COM0+39,\
                    COM0+40,COM0+41,COM0+06,COM0+31
#define DIGIT_3_SEG COM0+38,COM0+18,COM0+19,COM0+20,\
                    COM0+36,COM0+37,COM0+17,COM0+10

#define SIGN_DP  0x80

// character patterns on 7-seg
byte const digit_map[16] = {0b01111110,0b00110000,0b01101101,0b01111001,
                            0b00110011,0b01011011,0b01011111,0b01110000,
                            0b01111111,0b01111011,0b01110111,0b00011111,
                            0b01001110,0b00111101,0b01001111,0b01000111};


// state machine

enum states {ST01,ST02,ST03,ST04,ST05,ST06,ST07,ST08,STT1};

enum events {EV_KEYN1=KEY_NRM+KEY1,EV_KEYN2=KEY_NRM+KEY2,EV_KEYN3=KEY_NRM+KEY3,
             EV_KEYL1=KEY_LNG+KEY1,EV_KEYL2=KEY_LNG+KEY2,EV_KEYL3=KEY_LNG+KEY3,
             EV_KEYR1=KEY_RPT+KEY1,EV_KEYR2=KEY_RPT+KEY2,EV_KEYR3=KEY_RPT+KEY3,
             EV_TIMER};
             
enum tasks  {TK_DUMMY=0,
             TK_INCMIN,TK_DECMIN,
             TK_INCHOUR,TK_DECHOUR,
             TK_INCDAY,TK_DECDAY,
             TK_INCMON,TK_DECMON,
             TK_RESSEC};

#define START_STATE ST01
#define INIT_TIMER 48

typedef struct {
  states from_state;
  events event;
  states to_state;
  tasks task;
  int timerval;
} state_jump;

const state_jump statetab[] ={
                              // walkthrough display modes using normal keypresses
                              {ST01,EV_KEYN1,ST02,TK_DUMMY,80},
                              {ST02,EV_KEYN1,ST03,TK_DUMMY,80},
                              {ST03,EV_KEYN1,ST04,TK_DUMMY,80},
                              {ST04,EV_KEYN1,ST01,TK_DUMMY,0},

							  // jump to thermometer display
                              {ST01,EV_KEYN3,STT1,TK_DUMMY,0},

                              // automatic return to base state
                              {ST02,EV_TIMER,ST01,TK_DUMMY,0},
                              {ST03,EV_TIMER,ST01,TK_DUMMY,0},
                              {ST04,EV_TIMER,ST01,TK_DUMMY,0},

                              // get into setting states using a long press
                              {ST01,EV_KEYL1,ST05,TK_DUMMY,160},

                              // walkthrough setting modes using normal presses
                              {ST05,EV_KEYN1,ST06,TK_DUMMY,160},
                              {ST06,EV_KEYN1,ST07,TK_DUMMY,160},
                              {ST07,EV_KEYN1,ST08,TK_DUMMY,160},
                              {ST08,EV_KEYN1,ST01,TK_DUMMY,0},
                              
                              // automatic return to base state
                              {ST05,EV_TIMER,ST01,TK_DUMMY,0},
                              {ST06,EV_TIMER,ST01,TK_DUMMY,0},
                              {ST07,EV_TIMER,ST01,TK_DUMMY,0},
                              {ST08,EV_TIMER,ST01,TK_DUMMY,0},

                              // setting actions, HH:MM
                              {ST05,EV_KEYN2,ST05,TK_INCHOUR,160},
                              {ST05,EV_KEYL2,ST05,TK_INCHOUR,160},
                              {ST05,EV_KEYR2,ST05,TK_INCHOUR,160},
                              {ST05,EV_KEYN3,ST05,TK_INCMIN,160},
                              {ST05,EV_KEYL3,ST05,TK_INCMIN,160},
                              {ST05,EV_KEYR3,ST05,TK_INCMIN,160},

                              // setting actions, mm:dd
                              {ST06,EV_KEYN2,ST06,TK_INCMON,160},
                              {ST06,EV_KEYL2,ST06,TK_INCMON,160},
                              {ST06,EV_KEYR2,ST06,TK_INCMON,160},
                              {ST06,EV_KEYN3,ST06,TK_INCDAY,160},
                              {ST06,EV_KEYL3,ST06,TK_INCDAY,160},
                              {ST06,EV_KEYR3,ST06,TK_INCDAY,160},

                              // setting actions, :ss
                              {ST07,EV_KEYN2,ST07,TK_RESSEC,160},
                              {ST07,EV_KEYL2,ST07,TK_RESSEC,160},
                              {ST07,EV_KEYR2,ST07,TK_RESSEC,160},
                              {ST07,EV_KEYN3,ST07,TK_RESSEC,160},
                              {ST07,EV_KEYL3,ST07,TK_RESSEC,160},
                              {ST07,EV_KEYR3,ST07,TK_RESSEC,160},

                              // thermometer states
                              {STT1,EV_KEYN3,ST01,TK_DUMMY,0},

                                                                };

static states state = START_STATE;
static int iatimer = INIT_TIMER;


void DQ_L() {
  output_low(TOUCH_PIN);
  TRISA &= 0b11110111;
}

void DQ_H() {
  TRISA |= 0b00001000;
}

void VDD_L() {
  output_low(TOUCH_VDD);
  TRISA &= 0b11111011;
}

void VDD_H() {
  output_high(TOUCH_VDD);
  TRISA &= 0b11111011;
}

// touch routines
BYTE touch_read_byte() {
   BYTE i,data;

	data = 0;

   for(i=1;i<=8;++i) {
     DQ_L();
     delay_us(10);
	 DQ_H();
//     delay_us(5);
     shift_right(&data,1,input(TOUCH_PIN));
//     data += input(TOUCH_PIN);
     delay_us(60);
   }
   return(data);
}

int16 touch_read_byte9() {
   BYTE i;
   int16 data = 0;
   int16 mask = 1;

   data = 0;

   for(i=1;i<=9;++i) {
     DQ_L();
     delay_us(10);
	 DQ_H();
     if (input(TOUCH_PIN)) data |= mask;
	 mask <<= 1;
     delay_us(60);
   }
   return(data);
}

BYTE touch_write_byte(BYTE data) {
   BYTE i;
   BYTE m = 0b00000001;

   for(i=1;i<=8;++i) {
     DQ_L();
     if (data & m) {
       DQ_H();
       delay_us(90);
     }
     else {
       delay_us(60);
       DQ_H();
       delay_us(30);
     }
     m <<= 1;
   }
   return(TRUE);
}

BYTE touch_present() {
    BOOLEAN present;

	DQ_L();
    delay_ms(1);
	DQ_H();

    delay_us(60);

    present = !input(TOUCH_PIN);
    delay_ms(2);
    return(present);
}

void get_temp() {

signed int16 temp, cnt_perc, cnt_remn;


  if ( touch_present() ) {
    touch_write_byte(0xAA); // read temp
    temp = touch_read_byte();
  }

  if ( touch_present() ) {
    touch_write_byte(0xA0); // read counter
    cnt_remn = touch_read_byte9();
  }

  if ( touch_present() ) touch_write_byte(0x41); // load counter

  if ( touch_present() ) {
    touch_write_byte(0xA0); // read counter
    cnt_perc = touch_read_byte9();
  }

  if ( touch_present() ) {
    touch_write_byte(0xEE); // start convert T
  }

  if (temp > 128) temp -= 256;
  temp *= 10;
  temperature = temp + 10 * (cnt_perc - cnt_remn) / cnt_perc - 5 ;

//  temperature = cnt_remn;

}

// atomic routines
boolean inc_sec() { if (++sec == 60) sec = 0; return (sec == 0); }
boolean inc_min() { if (++min == 60) min = 0; return (min == 0); }
boolean inc_hour() { if (++hour == 24) hour = 0; return (hour == 0); }
boolean inc_day() { if (day++ == days_per_month[month]) day=1; return (day == 1); }
boolean inc_month() { if (month++ == 12) month = 1; return (month == 1); }

void dec_min() { if (min-- == 0) min = 59; }
void dec_hour() { if (hour-- == 0) hour = 23; }
void dec_day() { if (--day == 0) day = days_per_month[month]; }
void dec_month() { if (--month == 0) month = 12; }
void res_sec() { sec = 0; }


void inc_time() {
  if (inc_sec())
    if (inc_min())
      if (inc_hour())
        if (inc_day())
          inc_month();
}


// state machine routines
void dotask(tasks task) {
  if (task == TK_DUMMY) ;

  if (task == TK_RESSEC) res_sec();

  if (task == TK_INCMIN) inc_min();
  if (task == TK_DECMIN) dec_min();
  if (task == TK_INCHOUR) inc_hour();
  if (task == TK_DECHOUR) dec_hour();
  if (task == TK_INCDAY) inc_day();
  if (task == TK_DECDAY) dec_day();
  if (task == TK_INCMON) inc_month();
  if (task == TK_DECMON) dec_month();
}

void doevent(events event) {
  int i;
  int1 found = false;
  
  for (i=0; (i < (sizeof(statetab) / sizeof(statetab[0]))) && !found; i++) {
    if ((statetab[i].from_state == state) & (statetab[i].event == event)) {
      found = true;
      dotask(statetab[i].task);
      iatimer = statetab[i].timerval;
      state = statetab[i].to_state;
      blanking = false;
      redraw = true;
    }
  }
}

int keycode(int bincode) {
  if (bincode & 0b10000000) return KEY1;
  if (bincode & 0b01000000) return KEY2;
  if (bincode & 0b00100000) return KEY3;
  return 0;
}

#int_RB
void  RB_isr(void) 
{
  int p;
  
  p = input_b();
  
  sec += 10;
}

#int_TIMER1
void  TIMER1_isr(void) 
{
  int t1l;
  int kbd;
  int kbdup, kbddn;
  int keypress = 0;

  for (t1l = TMR1L; !(t1l^TMR1L); ) ;
  TMR1H |= 0b11111000;

  if (tick == 8) {
    blanking = true;
    redraw = true;
  }
  
  if ((tick = ((tick + 1) & (0xF))) == 0) {
    inc_time();
    blanking = false;
    redraw = true;
    if (--temp_delay == 0) {
      temp_delay = TEMP_TIME;
	  get_temp();
    }
  }

  kbd = ~input_b() & 0b11100000;
  
  kbdup = (kbd ^ 0b11100000) & lastkbd; // just released key
  kbddn = lastkbd & kbd; // key held down

  if ((kbdcnt > KBDCNT_BNC) && kbdup) {
    if (kbdcnt <= KBDCNT_LNG) {
      keypress = KEY_NRM + keycode(kbdup);
    }
  } else {
    if ((kbdcnt == KBDCNT_LNG) && kbddn) {
      keypress = KEY_LNG + keycode(kbddn);
    }
    if ((kbdcnt == KBDCNT_RPT) && kbddn) {
      keypress = KEY_RPT + keycode(kbddn);
      kbdcnt = KBDCNT_LNG;
    }
  }

  if (kbd) kbdcnt++; else kbdcnt = 0;
  
  if (keypress) doevent(keypress);
  if (iatimer) if (--iatimer == 0) doevent(EV_TIMER);
  
  lastkbd = kbd;
}


void calcdisp() {
int hr;
int16 abstemp;

  switch (state) {
    case ST01:
    case ST05:
      if ((hr = hour % 12) == 0) hr = 12;
      dig0 = hr / 10;
      dig1 = digit_map[(hr % 10)];
      dig2 = digit_map[(min / 10)];
      dig3 = digit_map[(min % 10)];
      if (hour >= 12) dig0 |= SIGN_PM;
      if (!blanking)  dig0 |= SIGN_COL;
      break;

    case ST02:
    case ST06:
      dig0 = month / 10;
      dig1 = digit_map[month % 10];
      dig2 = digit_map[day / 10] | SIGN_DP;
      dig3 = digit_map[day % 10];
      break;

    case ST03:
    case ST07:
      dig0 = SIGN_COL;
      dig1 = 0;
      dig2 = digit_map[(sec / 10)];
      dig3 = digit_map[(sec % 10)];
      break;

    case ST04:
    case ST08:
      dig0 = 0xFF;
      dig1 = 0xFF;
      dig2 = 0xFF;
      dig3 = 0xFF;
      break;

    case STT1:
      abstemp = abs(temperature);
      dig0 = abstemp / 1000 + ( temperature < 0 ? SIGN_NEG : SIGN_NEG + SIGN_POS );
      dig1 = digit_map[( abstemp % 1000) / 100];
      dig2 = digit_map[( abstemp % 100) / 10];
      dig3 = digit_map[( abstemp % 10) / 1] | SIGN_DP;
/*
      dig0 = 0;
      dig1 = digit_map[ temperature / 16 ];
      dig2 = digit_map[ temperature % 16 ];
      dig3 = digit_map[ temperaturehalf ? 5 : 0 ] | SIGN_DP;
*/
      break;
  }

  switch (state) {
    case ST05:
    case ST06:
    case ST07:
    case ST08:
      if (blanking) {
        dig0 = 0;
        dig1 = 0;
        dig2 = 0;
        dig3 = 0;
      }
  }
}



void main()
{

  int i;
 
  // setup CPU speed
  setup_oscillator(OSC_8MHZ);

  // setup I/O ports
  set_tris_a(0b00001000);
  set_tris_b(0b11100000);
  set_tris_c(0b00000000);
  set_tris_d(0b00000010);
  set_tris_e(0b00000000);
  set_tris_f(0b00000000);
  set_tris_g(0b00000000);
  port_b_pullups(0b11100000);

  output_a(0b00000000);
  output_b(0b00000000);
  output_c(0b00000000);
  output_d(0b00000000);
  output_e(0b00000000);
  output_f(0b00000000);
  output_g(0b00000000);

  // setup other peripherals
  setup_adc_ports(NO_ANALOGS|VSS_VDD);
  setup_adc(ADC_OFF);
  setup_spi(SPI_SS_DISABLED);
  setup_ccp1(CCP_OFF);
  setup_ccp2(CCP_OFF);

  // setup not used timers
  setup_timer_0(RTCC_INTERNAL|RTCC_DIV_1);
  setup_timer_2(T2_DISABLED,0,1);

  VDD_H();
  DQ_H();

  delay_ms(2);


  // setup TMR1 for LP crystal osc timer
  setup_timer_1(T1_EXTERNAL|T1_DIV_BY_1|T1_CLK_OUT);

  // setup LCD module
  LCDSE0 = 0b01000111;
  LCDSE1 = 0b00001100;
  LCDSE2 = 0b00011111;
  LCDSE3 = 0b11111111;
  LCDSE4 = 0b11111111;
  LCDSE5 = 0b00000011;
  setup_lcd(LCD_STATIC | LCD_TYPE_B_WAVE | 
            LCD_USE_TIMER_1 | LCD_BIAS_PINS,7);

//  VRCON = 0;
//  ADCON0 = 0;
//  CMCON0 = 0b00000111;
//  LVDCON = 0;

//  IOCB = 0b11100000;
//  enable_interrupts(INT_RB);

  // enable interrupts
  enable_interrupts(INT_TIMER1);
  enable_interrupts(GLOBAL);

  // TODO: USER CODE!!

  while (true) {
    if (redraw) {
      redraw = false;
      calcdisp();
      LCD_SYMBOL(dig0,DIGIT_0_SEG);
      LCD_SYMBOL(dig1,DIGIT_1_SEG);
      LCD_SYMBOL(dig2,DIGIT_2_SEG);
      LCD_SYMBOL(dig3,DIGIT_3_SEG);
    }

    sleep();

  }

}
