/*
Power Monitor Node
Edward Cheung, Ph.D.
mailto:edward.b.cheung.1@gsfc.nasa.gov
http://cheung.place.cc

Usage Policy
Available for non-commercial usage as long as my name and email are
included in the source file.  I would appreciate your writing me if
you build your own version of this project.

Compiled with CCS's PCM C Compiler.
Tested with TechTools' IDE and PIC Emulator.

Design Decisions
Code supports analog samples that are synced with the zero crossings
of the power line.  This removes 60hz ripple, and leads to cleaner data.

Modification History
- 7/4/99 Created.
- 7/7/99 v1.0 completed.

PCM Lessons learned
- Major troubles with port C.  Although the code ran fine on the emulator,
programming into a chip caused problems with port C pins.  It turns out that
after sending a character, it reverts to an input byte.  #use FAST_IO(C) was 
used, but since put_char is probably precompiled, it didn't matter.  
Pins on port C were moved to port D, and the problem went away.  Future
code should revert port C to intended direction after EACH serial byte send.
The original problem occurred both with code burned into a '74 and a '77.
- For low baud rates, do not use BRGH=1 on '74.  There is a bug in that mode
with the '74 that has been fixed with the '77.
USE:
#use rs232(baud=9600, bits=8, parity = N, xmit=PIN_C6, rcv=PIN_C7, enable=PIN_C5
AVOID:
#use rs232(baud=9600, bits=8, parity = N, xmit=PIN_C6, rcv=PIN_C7, enable=PIN_C5, brgh1ok,ERRORS

Development info
- To test with emulator, change to '77 type and compile and run from 
Techtool's IDE.  For '74 chip, verify #include statement is '74 and compile
from inside PCM.  Then program with ITUs programmer ("74prg power.hex" & "74cnf").
*/

#include <16c74.h>
#use delay(clock=20000000)
#use rs232(baud=9600, bits=8, parity = N, xmit=PIN_C6, rcv=PIN_C7, enable=PIN_C5
#define SAMPLE_SYNC   1  // if true, analog samples synced to 60hz
#define NODE_ADDRESS  'R'
#use FAST_IO(A)
#use FAST_IO(B)
#use FAST_IO(C)
#use FAST_IO(D)
#use FAST_IO(E)
// A/D
#define SDATA      PIN_D0
#define SCLK       PIN_D1
#define AD_SDATA   PIN_D2
#define AD_TFS     PIN_D3
#define AD_CLKIN   PIN_D4
#define AD_RFS     PIN_D5
#define POWER 0    // a/d channels
#define VOLTS 6
#define AMPS  5
// prototypes
void ad_init(void);                    // init A/D interface
long ad_get(byte channel);             // get analog reading
void msg_reply (long data);
// globals
int cmd_in;
short cmd_flag;
int sample_phase;
long count_60;
long hertz_60;
long pow_sample;
long vol_sample;
long amp_sample;

// Analog to Digital Converter
void ad_init(void){
  set_tris_d(0x04);    // bit 2 input
  output_high(AD_TFS);
  output_low(AD_CLKIN);
  set_tris_a(0x10);
  set_tris_b(0xff);
  set_tris_c(0x80);    // only serial rx is input
}
long ad_get(byte channel){
/*
  76543210   bits of channel
  ^^^^^^^^
  |||||||+-- LSB of mux (which one on the chip)
  ||||||+--- mux addressing data (3 bits total)
  |||||+---- MSB of mux (which one on the chip)
  ||||+----- LSB of chip select mux ('A' on 74154's)
  |+++------ chip select data (5 bits total)
  +--------- MSB of chip select mux ('G' on 74154's)
*/
  byte temp;
  // inits
  long reading = 0;
  byte x = 0;
  byte readata[2];
  output_high(SCLK);

  // write mux setting to chip, no conversion start
  output_low(AD_TFS);  // start register write operation
  output_bit(SDATA,BIT_TEST(channel,2));
  output_low(SCLK);
  output_high(SCLK);
  output_bit(SDATA,BIT_TEST(channel,1));
  output_low(SCLK);
  output_high(SCLK);
  output_bit(SDATA,BIT_TEST(channel,0));
  output_low(SCLK);
  output_high(SCLK);
  output_low(SDATA);  // convert command
  output_low(SCLK);
  output_high(SCLK);
  output_low(SDATA);   // standby command
  output_low(SCLK);
  output_high(SCLK);
  output_low(SCLK);    // one extra clock
  output_high(SCLK);
  output_high(AD_TFS); // end register write operation
  delay_us(700);       // allow mux to settle
  // initiate conversion
  output_low(AD_TFS);  // start register write operation
  output_bit(SDATA,BIT_TEST(channel,2));
  output_low(SCLK);
  output_high(SCLK);
  output_bit(SDATA,BIT_TEST(channel,1));
  output_low(SCLK);
  output_high(SCLK);
  output_bit(SDATA,BIT_TEST(channel,0));
  output_low(SCLK);
  output_high(SCLK);
  output_high(SDATA);  // convert command
  output_low(SCLK);
  output_high(SCLK);
  output_low(SDATA);   // standby command
  output_low(SCLK);
  output_high(SCLK);
  output_low(SCLK);    // one extra clock
  output_high(SCLK);
  output_high(AD_TFS); // end register write operation
  // drive conversion
  for (x=0;x<14;x++) { // send clocks to drive conversion
    output_high(AD_CLKIN);
    output_low(AD_CLKIN);
  }
  // get reading
  output_low(AD_RFS);
  output_low(SCLK);
  output_high(SCLK);   // clock MSB of address out of A/D
  output_low(SCLK);
  output_high(SCLK);   // clock address out of A/D
  output_low(SCLK);
  output_high(SCLK);   // clock LSB of address out of A/D
  output_low(SCLK);
  output_high(SCLK);   // clock MSB of data out
  output_low(SCLK);
  readata[0] = 0;
  readata[1] = 0;
  for (x=0;x<12;x++) {
    shift_left(&reading,2,input(AD_SDATA));
    output_high(SCLK); // clock next bit of data out
    output_low(SCLK);
  }
  output_high(AD_RFS);
  return reading;
}

#define INTS_PER_FIRE 7632  // 76.29 for one second interval and 20Mhz
#int_rtcc
void clock_timer(){
  static long int_count = 0;
  int_count ++;
  if (int_count >= INTS_PER_FIRE) {
    int_count = 0;
    hertz_60 = count_60;
    count_60 = 0;
  }
}

// Sum of following two must be 1.0
#define FIL_REC 0.01  // smaller for slower filter
#define FIL_FOR 0.99
#int_ext
void power_timer() {
  // count zero crossings
  #if SAMPLE_SYNC==1
  switch (sample_phase) {
  case 0:
    pow_sample = ad_get(POWER)*FIL_FOR + pow_sample*FIL_REC;
    sample_phase++;
    break;
  case 1:
    vol_sample = ad_get(VOLTS)*FIL_FOR + vol_sample*FIL_REC;
    sample_phase++;
    break;
  case 2:
    amp_sample = ad_get(AMPS)*FIL_FOR + amp_sample*FIL_REC;
    sample_phase = 0;
    break;
  default:
    sample_phase = 0;
    break;
  }
  #ENDIF
  count_60++;
}

#int_rda
void serial_isr() {
  byte byte_in;
  static byte com_state=0;
  // process new received character
  byte_in=getc();
  //putchar(byte_in);  // echo back out
  switch (com_state) {
  case 0:
    if (byte_in==NODE_ADDRESS)
      // message for this node
      com_state++;
    break;
  case 1:
    // check if from good source
    if (byte_in>='A' || byte_in<='Z')
      com_state++;
    else
      com_state==0;
    break;
  case 2:
    // check delimiter
    if (byte_in=='_')
      com_state++;
    else
      com_state=0;
    break;
  case 3:
    // get command type
    if (byte_in=='Q')
      com_state++;
    else
      com_state= 0;
    break;
  case 4:
    // get command argument
    cmd_in = byte_in;
    com_state ++;
    break;
  case 5:
    // if (byte_in == 13)
      cmd_flag = TRUE;
    com_state = 0;
    break;
  default:
    com_state = 0;
    break;
  }
}
void test_out(int cmd_in) {
  output_bit(AD_CLKIN,bit_test(cmd_in,0));
  output_bit(SCLK,bit_test(cmd_in,1));
  output_bit(AD_TFS,bit_test(cmd_in,2));
  output_bit(PIN_A0,bit_test(cmd_in,3));
  output_bit(SDATA,bit_test(cmd_in,5));
}

void main(void) {
  long data;

  // init
  cmd_flag = FALSE;
  sample_phase = 0;
  ad_init();
  enable_interrupts(int_rda);   // serial port
  enable_interrupts(int_ext);   // 60 Hz
  setup_counters( RTCC_INTERNAL, RTCC_DIV_256);
  enable_interrupts(RTCC_ZERO); // rtcc
  enable_interrupts(global);
  cmd_in = 0;
  pow_sample = 0;
  vol_sample = 0;
  amp_sample = 0;
  count_60 = 0;
  hertz_60 = 0;
  printf("PN");
  // main loop
  while (1) {
    restart_wdt();
    if (cmd_flag) {
      // process new command
      cmd_flag = FALSE;
      data = 0;
      //test_out(cmd_in);
      switch (cmd_in) {
      case 'R':
        ad_init();
        break;
      case 'P':
        #IF SAMPLE_SYNC==1
          data = pow_sample;  // 0.146 counts/watt
        #ELSE
          data = ad_get(POWER);
        #ENDIF
        break;
      case 'V':
        #IF SAMPLE_SYNC==1
          data = vol_sample;  // 29.34 counts/volt rms
        #ELSE
          data = ad_get(VOLTS);
        #ENDIF
        break;
      case 'I':
        #IF SAMPLE_SYNC==1
          data = amp_sample;   // 14.83 counts/amps rms
        #ELSE
          data = ad_get(AMPS);
        #ENDIF
        break;
      case 'F':
        data = hertz_60;     // line frequency
        break;
      default:
        break;
      }      
      msg_reply(data);
    }
  }
}
void msg_reply (long data) {
  byte output;
  putchar('C');
  putchar(NODE_ADDRESS);
  putchar('_');
  output = ((data>>8)&0xf)|0x30;
  putchar(output);
  output = ((data>>4)&0xf)|0x30;
  putchar(output);
  output = (data&0xf)|0x30;
  putchar(output);
  putchar(13);
}
