/* 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); }