#include <c:\program files\picc\devices\16F877.h>
#include <math.h>
#fuses HS,WDT,NOPROTECT,PUT	// high speed osc, watchdog on, no code protect, power up timer on
//#use delay (clock=4000000) 		// 4MHz clock
#use delay (clock=16000000) 		// 16MHz clock
#use RS232(baud=19200,xmit=PIN_C6,rcv=PIN_C7)	// 19.2K baud (LCD default)
#use FAST_IO(A)				// I/O dir doesn't change when
#use FAST_IO(B)				// reading and writing to ports
#use FAST_IO(C)
#use FAST_IO(D)

// global variables
int8 h_cnt;		// loop counter for heaters 0-3
int8 h_io[10];		// heater output bits (0-3)
int8 c_cnt;		// counter for each time the outputs change
unsigned int16 pwm[4];	// heaters are on for this long (0-2^16) TIMER1
signed int32 cnt[10];	// timer value when heaters turn on or off
signed int32 sum;	// used to sum pwm #'s
float tp1_flt;		// temp float variables

int8 tmp1;		// temp variable
int8 tmp2;		// temp variable
int8 tmp3;		// temp variable

int8 lcd;		// temparary int of for lcd keypad interface
int8 i_temp;		// temparary int for desired temp

float temp[5];		// temperature (in deg C)
float d_temp;		// desired temperature (in deg C)
signed int32 value;	// temp var used to read A/D
int8 chan;		// A/D channel # 0-4
int8 ptavg;		// # of points to average
int8 value1;		// temp var (4, 8 bit words -> 24 bit temp)
int8 value2;
int8 value3;
int8 value4;
int8 byte1;		// temp variable
int8 tmp;		// temp variable
int8 color;		// controls red, yellow, green LED's

//const float A = 3.9083E-3;	// RTD constants (A)
//const float AA = 1.5275E-5;	// A^2
//const float B = -5.775E-7;	// B
//const float B2 = -1.155E-6;	// 2*B
//const float B4 = -2.31E-6;	// 4*B
////const float R0 = 2139951;	// A/D reading at 0 C (100 ohms)
				// Rref=392, RTD=100@0C box_tc = (100/392)*2^23
const float box_tc = 9.346E-6;	// Used to convert the box A/D to temp
const int16 lp_time = 40000;	// timer 1 count to 40000 before continue loop
const signed int32 max = 65000;	// max count for PWM period 130ms
const float i_max = 50;		// max for int error

// note: lost sign taking negative number inside sqrt (2*B is neg)
// solution: first term should be pos # - sqrt # (so change sign of C)
//const float C = -3383.8095;	// Roko simplified EQ to
const float C = 3383.8095;	// Roko simplified EQ to
const float C21B = 13181769;	// T = -C + SQRT(C^2 - 1/B + Rt/(BR0))
//const float R0 = -0.80544186;	// where C = A/(2*B)
//const float R1 = -0.80545909;	// and R0-R3 are the specific constants
//const float R2 = -0.80541339;	// 1/(BR0-3) for each RTD and
//const float R3 = -0.80585980;	// C21B = C^2 - 1/B
const float R0 = -0.80544186;	// where C = A/(2*B)
//const float R1 = -0.80544186;	// and R0-R3 are the specific constants
//const float R2 = -0.80544186;	// 1/(BR0-3) for each RTD and
//const float R3 = -0.80544186;	// C21B = C^2 - 1/B
// the original EQ was T = (-A + SQRT(A^2 - 4B(1 - Rt/R0)))/(2B)

float int_val[3];		// value of integral
//const float p_gain = 12000;	// proportional gain (oscillation at gain=4)
const float p_gain = 25000;	// proportional gain (oscillation at gain=4)
const float i_gain = 500;	// integral gain

//#INT_TIMER1	// enable timer1 interrupt
//void timer1_isr(){
//  tmp3 = 8;
//}

void key_lcd(void){
	restart_wdt();		// reset watch dog timer
 	if (lcd == 0){		// if first time through lcd routine
	  i_temp = d_temp;	// set i_temp to desired temp
	  putc(254);
	  putc(88);		// clear display, cursor to upper left
	  printf("Set pt  : %3u.00\n\r", i_temp);
	  printf("<- esc, -> enter");
	  lcd = 1;		// not 1st time through loop
	}else{
	  if (!bit_test(tmp1,6)){	// if up arrow
	    ++i_temp;		// increment temporary temp
	    if (i_temp > 180)	// check max allowed temperature
	      i_temp = 180;
	    lcd = 1;		// not 1st time through lcd loop
	  }else if (!bit_test(tmp1,5)){	// if down arrow
	    --i_temp;		// decrement temporary temp
	    if (i_temp < 30)	// check min allowed temperature
	      i_temp = 30;
	    lcd = 1;		// not 1st time through lcd loop
	  }else if (!bit_test(tmp1,7)){	// if right arrow
	    d_temp = i_temp + 0.001;	// update desired temperature (round off error)
	    lcd = 0;		// next time 1st time through lcd loop
	  }else{			// if here then left arrow
	    lcd = 0;		// tag as last time through loop
	  }
	  putc(254);		// move cursor to col 11 row 1
	  putc(71);
	  putc(11);
	  putc(1);
	  if (lcd == 0)
	    printf("%3.2f  ", d_temp);
	  else
	    printf("%3u.00 ", i_temp);
	}
}

void pwm_calc(void){		// calculate pwm[0-2], 3 unused
	for (tmp3 = 0; tmp3 <= 2; ++tmp3){
	  tp1_flt = d_temp - temp[tmp3];	// dif between set pt & sample temp
	  int_val[tmp3] = int_val[tmp3] + tp1_flt;	// integral part
	  if (int_val[tmp3] > i_max)	// if integral has grown too large
	    int_val[tmp3] = i_max;	// integral = d_max
	  if (int_val[tmp3] < -i_max)	// if integral has grown too large
	    int_val[tmp3] = -i_max;	// integral = d_max
//	  sum = tp1_flt * p_gain + int_val[tmp3] * i_gain;
	  if (tmp3 == 0)	// add the proportional and integral parts
//	     sum = sum + 30000;	// add offset 100C
//	     sum = sum + 60000;	// add offset 150C
//	     sum = sum + 16000;	// add offset 80C
//	     sum = sum + 45000;	// add offset 110C
//	     sum = sum + 50000;	// add offset 120C
//	     sum = sum + 25000;	// add offset 90C
//	     sum = sum + 35000;	// add offset 95C
//	     sum = sum + 60000;	// add offset 140C
//	     sum = sum + (d_temp - (float)57) * 643;
//	     sum = tp1_flt * p_gain + int_val[tmp3] * i_gain + (d_temp - 57) * 643;
	     sum = tp1_flt * p_gain + int_val[tmp3] * i_gain + (d_temp - 40) * 643;
	  else if (tmp3 == 1)
	     sum = tp1_flt * p_gain + int_val[tmp3] * i_gain;
//	     sum = sum + 0;	// add offset
	  else
//	     sum = sum + 10000;	// add offset 100C
//	     sum = sum + 20000;	// add offset 150C
//	     sum = sum + 5000;	// add offset 80C
//	     sum = sum + 12000;	// add offset 110C
//	     sum = sum + 14000;	// add offset 120C
//	     sum = sum + 8000;	// add offset 90C
//	     sum = sum + 8000;	// add offset 95C
//	     sum = sum + 18000;	// add offset 140C
//	     sum = sum + (d_temp - (float)60) * 214;
//	     sum = tp1_flt * p_gain + int_val[tmp3] * i_gain + (d_temp - 60) * 214;
	     sum = tp1_flt * p_gain + int_val[tmp3] * i_gain + (d_temp - 58) * 214;
	  if (sum < 11)		// if negative
	    sum = 11;		// PWM = 11
	  if (sum > max)	// if too big
	    sum = max;		// PWM = max
	  pwm[tmp3] = sum;	// set PWM value
	}
	pwm[3] = 11;	// heater 3 unused
}

void read_chan(void){
	    restart_wdt();		// reset watch dog timer
	    output_low(pin_C2);		// make CS low to get data

	    // read differential channel
 	    byte1 = 160 + chan;		// byte1 = "101,SGL=0,0,A2,A1,A0"

	    value1 = spi_read(byte1);	// assign info from AD to valueX
	    value2 = spi_read(0);
	    value3 = spi_read(0);
	    value4 = spi_read(0);

	    restart_wdt();		// reset watch dog timer
	    if ((d_temp - temp[0]) < (float)1){	// if T < 1C from setpt normal drive
	    // ---------------------------------------------------------
	      set_timer1(0);	// clear the timer
	      c_cnt = 1;		// first heater change
	      output_b(h_io[0]);	// start proper heaters at T=0
	      do{
		value = get_timer1();	// convert to 32 bit int
		if(value > cnt[c_cnt]){	// if time to change heater settings
	          output_b(h_io[c_cnt]);	// change heater outputs
	          ++c_cnt;			// point to next heater I/O change
	        }
	      }while(get_timer1() < max);	// stay in loop until right time
	      //output_b(h_io[c_cnt]);	// all heaters off
	      output_b(0x00);		// all heaters off
	      set_timer1(0);		// clear the timer
	      // ---------------------------------------------------------
	    } else{		// if PWM = max all heaters on
	      set_timer1(0);	// clear the timer

	      int_val[0] = 0;	// zero out integral during ramp
	      int_val[1] = 0;
	      int_val[2] = 0;

	      // take care of the differences in the heaters to heat evenly
	      //  H3 = 1, H2 = 0.96, H1 = 0.894, Period = 1.125 Sec
	      output_high(pin_b0);	// heater #3 on
	      delay_ms(40);
	      output_high(pin_b2);	// heater #2 on
	      delay_ms(80);
	      output_high(pin_b1);	// heater #1 on
	      do{		// stay in loop until right time
	      }while(get_timer1() < max);
	      set_timer1(0);	// clear the timer
	    }

	    delay_ms(5);		// make sure A/D has time for conversion
	    output_high(pin_C2);	// set CS high

	    tmp1 = (input_b() & 0xF0);	// read keypad (internal pullups)
	    if (tmp1 != 0xF0)		// if key is pressed pin low
	      key_lcd();		// goto lcd routine

	    restart_wdt();		// reset watch dog timer
	    //group all sets of data together into one integer
	    value = make32(value1 & 0x1F, value2, value3, value4 & 0xC0);
	    for(tmp1 = 0; tmp1 <= 5; ++tmp1)	// remove 6 LSB's
	      rotate_right(&value,4);	// one bit at a time

	    // could comment out portion below (Resistance always > 0)
	    if ((value1 & 0x20) == 0){	// check sign bit
	      value = value - 8388608;	// convert two's compliment (2^23)
	    }

//	    if (chan == 1)	// RTD coeficients for
//	      tp1_flt = R0;	// chan - 1 (from A/D)
//	    if (chan == 2)
//	      tp1_flt = R1;
//	    if (chan == 3)
//	      tp1_flt = R2;
//	    if (chan == 4)
//	      tp1_flt = R3;

	    tp1_flt = R0;	// R0 same for all RTD's
	    if (chan == 0)
	      tp1_flt = box_tc * (float)value;	// box temp
	    else
	      tp1_flt = C - sqrt(C21B + tp1_flt * (float)value);//RTD temp

	    if (chan == 2)	// chan 1
	      tp1_flt = tp1_flt * 1.000814 + 0.135147;
	    if (chan == 3)	// chan 2
	      tp1_flt = tp1_flt * 1.000778 + 0.005017;
//	      tp1_flt = tp1_flt * 1.000778 + 0.145017;
	    if (chan == 4)	// chan 3
	      tp1_flt = tp1_flt * 1.001005 + 0.048235;

	    if (chan == 0)	// A/D gives result from previous conversion
	      temp[4] = 0.75 * temp[4] + 0.25 * tp1_flt;
	    else{
	      if (temp[chan-1] > 20){	// average if not first sample
		// swap channels 2 & 3 (2 not used)
	     	if (chan == 4){	// put chan 3 into chan 2
		  temp[2] = 0.75 * temp[2] + 0.25 * tp1_flt;
		}
	     	else if (chan == 3){	// put chan 2 into chan 3
		  temp[3] = 0.75 * temp[3] + 0.25 * tp1_flt;
		}
		else{		// if chan 0 don't swap
	     	  temp[chan-1] = 0.75 * temp[chan-1] + 0.25 * tp1_flt;
		}
	      }else{		// if first sample don't average
	     	  temp[chan-1] = tp1_flt;
	      }

//	      if (temp[chan-1] > 20){	// average if not first sample
//		// swap channels 2 & 3 (2 not used)
//	     	if (chan == 4){	// put chan 3 into chan 2
//		  temp[2] = tp1_flt;
//		}
//	     	else if (chan == 3){	// put chan 2 into chan 3
//		  temp[3] = tp1_flt;
//		}
//		else{		// if chan 0 don't swap
//	     	  temp[chan-1] = tp1_flt;
//		}
//	      }else{		// if first sample don't average
//	     	  temp[chan-1] = tp1_flt;
//	      }
	    }

	    restart_wdt();			// restart watch dog timer
	    if (chan != 4){	// if not last time through the loop
	      do{				// wait here until time to start next loop
	      }while(get_timer1() < lp_time);
	    }
	    output_b(0x00);		// all heaters off
}

void h_drive(void){	// drive heaters during 135ms delay while reading A/D
  restart_wdt();	// restart watch dog timer
  c_cnt = 1;		// first heater change
  set_timer1(0);	// clear the timer
  output_b(h_io[0]);	// start proper heaters at T=0
  do{
    value = get_timer1();	// convert to 32 bit int
    if(value > cnt[c_cnt]){	// if time to change heater settings
      output_b(h_io[c_cnt]);	// change heater outputs
      ++c_cnt;			// point to next heater I/O change
    }
  }while(get_timer1() < max);
  output_b(0x00);		// all heaters off
  set_timer1(0);		// clear the timer
}

void h_calc(void){	// converts PWM values to start & end time for the heaters

  restart_wdt();	// restart watch dog timer
  for (c_cnt = 0; c_cnt <= 9; ++ c_cnt){
    h_io[c_cnt] = 0;	// zero out all heaters
    cnt[c_cnt] = max;	// when pwm = max cnt doesn't get set so pre-set to max
  }
  h_cnt = 0;		// start with the first heater
  c_cnt = 0;		// start with the first output change
  cnt[c_cnt] = 0;	// first heater turns on at T=0
  sum = pwm[0];		// start with the first heater time
  bit_set(h_io[c_cnt],h_cnt);	// heater 0 starts at T=0
  ++c_cnt;		// increment the number of times the outputs change

  do{			// loop through all the heaters
    if (sum < max){	// if the current heater finishes before the max time
      cnt[c_cnt] = sum;
      bit_clear(h_io[c_cnt],h_cnt);	// turn off heater # h_cnt
      ++h_cnt;
      bit_set(h_io[c_cnt],h_cnt);	// turn on the next heater
      ++c_cnt;	// increment the number of times the outputs change
      if (h_cnt < 4)	// pwm array index 0-3 (keep from hitting 4)
        sum = sum + pwm[h_cnt];	//  total of all pwm's up to now
    }else{		// if heater past max, run it at the start for a while
      if (sum == max){	// if sum = max and not wrap around
	++h_cnt;	// skip to next heater
	sum = pwm[h_cnt];	// skip to next I/O
      }else
        sum = sum - max;	// reset to difference (time heater should be on)
      bit_set(h_io[0],h_cnt);	// turn on the heater at T=0
    }
  }while(h_cnt < 4);	// keep looping until 0-3 are done

  cnt[c_cnt] = max;	// set last time to maximum
  h_io[c_cnt] = 0;	// clear last I/O

  // sort the list in ascending order
  tmp1 = c_cnt;		// save the number of I/O heater changes
  for (tmp3 = 1; tmp3 <= (tmp1-1); ++tmp3){
    sum = max;		// use sum to hold the max timer value
    for (c_cnt = tmp3; c_cnt <= tmp1; ++c_cnt){
      if (cnt[c_cnt] < sum){
        tmp2 = c_cnt;	// get index of lowest timer value
        sum = cnt[c_cnt];	// reset min value
      }
    }
    sum = cnt[tmp3];	// swap timer entries
    cnt[tmp3] = cnt[tmp2];
    cnt[tmp2] = sum;
    c_cnt = h_io[tmp3];	// swap I/O entries (use c_cnt because it's 8 bits)
    h_io[tmp3] = h_io[tmp2];
    h_io[tmp2] = c_cnt;
  }

  // remove doubles (i.e. I/O changes at the same time)
  for (c_cnt = 1; c_cnt <= tmp1-1; ++c_cnt){	// loop through all counts
    if (cnt[c_cnt] == cnt[c_cnt+1]){		// if times are the same
      h_io[c_cnt] = h_io[c_cnt] | h_io[c_cnt+1];	// OR I/O and
      ++c_cnt;					// skip this one
      for (tmp3 = c_cnt; tmp3 <= (tmp1-1); ++tmp3){	// move rest down one
	cnt[tmp3] = cnt[tmp3+1];		// move times down
	h_io[tmp3] = h_io[tmp3+1];		// move I/O down
      }
      --tmp1;		// decrease the total number of I/O changes by one
    }
  }

  // fill in ones for overlapping heaters (keep on between start and end times)
  for (c_cnt = 0; c_cnt <= tmp1-2; ++c_cnt){	// loop through all I/O changes
    for (h_cnt = 0; h_cnt <= 3; ++h_cnt){	// loop through all heaters
      if (bit_test(h_io[c_cnt],h_cnt)){		// if have a 1
        if (bit_test(h_io[c_cnt+1],h_cnt+1)==0)	// and 0 down and to the right
	  bit_set(h_io[c_cnt+1],h_cnt);		// then put a 1 to the right of first 1
      }
    }
  }

}


main(){
	restart_wdt();			// reset watch dog timer
	setup_wdt(WDT_576MS);		// watch dog timer overflow in 576ms
	setup_psp(PSP_DISABLED);	// disable parallel slave port
	setup_adc(ADC_OFF);		// disable ADC
	setup_adc_ports(NO_ANALOGS);	// all ADC I/O are digital

	// use timer 1 (16 bits) to time heater I/O PWM
//	setup_timer_1(T1_INTERNAL|T1_DIV_BY_2);	// ~131ms with 4MHz xtal
	setup_timer_1(T1_INTERNAL|T1_DIV_BY_8);	// ~131ms with 16MHz xtal

	// enable TMR2 prescaler /1 prescaller, cnt to 255 before ovfl
	// used to set I2C clock rate when talking to A/D
//	setup_timer_2(T2_DIV_BY_1,255,1);	// use with 4MHz xtal
	setup_timer_2(T2_DIV_BY_1,255,4);	// use with 16MHz xtal

	// use TMR2 ovfl to clock spi (few KHz)
	// spi_L_to_H idle state for clock is low
	// SPI_SAMPLE_AT_END clock in SDO on rising edge of clock
//     	setup_spi(spi_master|spi_L_to_H|spi_clk_t2|SPI_SAMPLE_AT_END);
     	setup_spi(spi_master|spi_L_to_H|spi_clk_t2);	// sample incomming data in middle of output bit

	#asm		// start assembly code
	  movlw 0x40	// clock data out on falling edge of clock (PIC datasheet wrong)
	  movwf	0x94	// sspstat in bank 1
	#endasm		// end assembly code

	set_tris_a(0xFF);		// port a inputs (unused)
	set_tris_b(0xF0);		// RB7-4 in (keypad), RB3-0 out (FETs)
	set_tris_c(0x90);		// RC4 input (SPI SDI) SCK & SDO out
					// RC7 input (usart RX) RC6 (TX) out
					// RC2 output (A/D Chip Select)
	set_tris_d(0x00);		// port d outputs (bi-color LEDs)

	output_b(0x00);			// all FET's off at power up
	output_d(0x0F);			// Red LED's on at power up
	port_b_pullups(TRUE);		// enable port b internal pullups

	for (chan = 0; chan <= 2; ++chan)	// loop through integrals
	  int_val[chan] = 0;	// initially integrals are zero

	pwm[0] = 11;	// heater 99.9% off (quick fix in PWM calc section)
	pwm[1] = 11;
	pwm[2] = 11;
	pwm[3] = 11;

	// set desired temperature
	d_temp = 30.001;	// desired temperature in deg C (+ roundoff error)
	tp1_flt = d_temp;	// so heaters don't turn on first time

//	temp[0] = 30.05;
//	temp[1] = 30.5;
//	temp[2] = 31.5;
//	temp[3] = 32.05;
//	goto buba;

	for (chan = 0; chan <= 4; ++chan)	// loop through all channels
	  temp[chan] = 0;		// zero out sums for averaging

	// LCD setup
	restart_wdt();		// reset watch dog timer
	delay_ms(250);	// delay needed otherwise text doesn't show up
	putc(254);
	putc(70);	// backlight off
	putc(254);
	putc(72);	// move cursor home (upper left)
	putc(254);
	putc(68);	// Auto line wrap on
	putc(254);	// move cursor to col 11 row 1
	putc(71);
	putc(11);
	putc(1);
	printf("%3.2f  ", d_temp);
	lcd = 0;	// when change set pt will be 1st time in loop
loop:

	for (chan = 0; chan <= 4; ++chan){	// loop through all channels
	  read_chan();
	}

	    // conversion starts when previous data read out (now)

	    // wait at least 133ms before reading the result
	    // if opto-isolation doesn't work put pic to sleep for 133ms

	    // bit 31 goes low when coversion complete
	    // bit 30 always 0
	    // bit 29 = sign bit (1 = POS, 0 = NEG)
	    // bit 28-6 = MSB - LSB (if bit 28 = 29 = 1 then Vin over range)
	    //			(if bit 28 = 29 = 0 then Vin under range)
	    // 			(if bit 28 <> 29 then OK)
	    // bit 5 = single/differential (0 = dif, 1 = singl)
	    // bit 4-1 = channel address 0000-1111
	    // bit 0 = parity bit (total # of ones including parity should be even)

	restart_wdt();	// restart watch dog timer
	if (lcd == 0){	// if not changing the set point print temps
	  putc(254);
	  putc(72);	// move cursor home (upper left)
	  printf(" %3.3f ", temp[3]);
	  for (tmp1 = 0; tmp1 <= 2; ++ tmp1){
	    putc(254);	// move cursor to col 1 row 2 + tmp1
	    putc(71);
	    putc(1);
	    putc(2 + tmp1);
	    printf(" %3.3f %5lu %2.0f ",temp[tmp1], pwm[tmp1], int_val[tmp1]);
	  }
	  printf("\n\r");

	  color = 0x0f;		// assume red LED's need to be on
	  for (tmp1 = 0; tmp1 <= 3; ++ tmp1){
	    tp1_flt = abs(temp[tmp1] - d_temp);
	    if (tp1_flt < 0.1){
	      bit_set(color,tmp1+4);	// green LED on
	      bit_clear(color,tmp1);	// red LED off
//	      output_bit(PIN_D0,1);	// green LED on
//	      output_bit(PIN_D4,0);	// red LED off
	    }else if (tp1_flt < 1){
	      bit_set(color,tmp1+4);	// green LED on (red is already on = yellow)
//	      output_bit(PIN_D0,1);	// green LED on (red is already on = yellow)
	    }
	  }
	  output_d(color);		// update LED's
	}

	restart_wdt();	// restart watch dog timer
	pwm_calc();	// calc pwm values from temperatures
	h_calc();	// calc heater on/off times from PWM values
	restart_wdt();		// restart watch dog timer
	do{			// wait here until time to start next loop
	}while(get_timer1() < lp_time);

	goto loop;
}

