// Controller for dual power supply // Monitor voltage and current (and temperature) for both // channels and display V / I on the 4-digit LEDs // also send the data to the RS232 port every 1 second #include __CONFIG(HS & WDTDIS & WRTDIS & LVPDIS & BORDIS & UNPROTECT); #define _XTAL_FREQ 20000000 #define TxBUF_SIZE 32 #define TxBUF_MASK (TxBUF_SIZE-1) #define RxBUF_SIZE 16 #define RxBUF_MASK (RxBUF_SIZE-1) unsigned int adc(unsigned char); void dly_us(unsigned char); void SendStr(const char *); void pr_crlf(void); void putch(char); void bin2bcd(unsigned int); volatile unsigned int msec; volatile unsigned char tenths; volatile unsigned int sec; volatile unsigned char sec_timer; volatile unsigned char digit; // which LED to display // map the various types of display (voltage/current, supply 1/2) // to a liinear array for the interrupt routine to select the // next digit in logical order typedef union { unsigned char d[16]; struct { unsigned char v1[4]; unsigned char i1[4]; unsigned char v2[4]; unsigned char i2[4]; }; } display_t; display_t display; // values to display typedef union { volatile unsigned char ALL; struct { unsigned D2S:1; /* 1 or more char to send */ unsigned FULL:1; /* buffer empty */ unsigned NEW:1; /* A character has been received */ unsigned EOL:1; /* Received a NL character */ }; } commflag_bits_t; commflag_bits_t CommFlags; unsigned char all_displayed; volatile char TxBuf[TxBUF_SIZE]; volatile char RxBuf[RxBUF_SIZE]; volatile char RxIPtr; volatile char RxOPtr; volatile char TxIPtr; volatile char TxOPtr; unsigned char NumH, NumL; unsigned char TenK, Thou, Hund, Tens, Ones; void main() { unsigned int res; INTCON = 0; OPTION = 0b00000011; // TMR0 at 3.2uSec per tick TRISA = 0b00101111; // RA0, 1, 2, 3, 4 inputs, RA4 = output ADCON1 = 0b10000000; // ADRESH data in LSBs AN0-4 enabled TRISB = 0b00000000; // all outputs for 7-seg displays TRISC = 0b10000000; // RxD input, TxD output // others select multiplexed LED T1CON = 0; RC4 = 1; // Use timer2 for a 1mSec interrupt source. // At 20Mhz Fosc/4 = 200nSec so employ a prescale of 4 // a postscale of 5 and a counter value of 250 T2CON = (0x4 << 3) | 0x4 | 0x1; PR2 = 249; // count up to overflow == 250 counts TMR2IE = 1; // will interrupt when they're enabled // Run the UART at 9600 Baud, async TXSTA = 0b00100000; // async, low BRG RCSTA = 0b10010000; // enable receiver SPBRG = 31; // 9600Baud at 20MHz clock speed msec = 0; tenths = 0; sec = 0; digit = 0; sec_timer = 0; PIE1 = 0b00000010; // Tx/Rx and TMR2 interrupts only PEIE = 1; // allow peripheral interrupts GIE = 1; // enable all interrupts // In the main part of the program, read the A-D channels, // convert then from 10-bit to BCD and set up the data so // the interrupt routine can convert the values to 7-seg // and display them SendStr("\nDual power supply\n"); loop: if(msec > 50) RC4 = 1; // flash an LED else RC4 = 0; if(all_displayed) { res = (adc(0) * 10) / 4; // supply #1, voltage 0 - 25.6.48V bin2bcd(res); display.v1[0] = Thou; display.v1[1] = Hund; display.v1[2] = Tens; display.v1[3] = Ones; res = adc(1) * 4; // supply #1, current 0 - 2.56A bin2bcd(res); display.i1[0] = Thou; display.i1[1] = Hund; display.i1[2] = Tens; display.i1[3] = Ones; res = (adc(2) * 10) / 4; // supply #2, voltage bin2bcd(res); display.v2[0] = Thou; display.v2[1] = Hund; display.v2[2] = Tens; display.v2[3] = Ones; res = adc(3) * 4; // supply #2, current bin2bcd(res); display.i2[0] = Thou; display.i2[1] = Hund; display.i2[2] = Tens; display.i2[3] = Ones; res = (adc(4) - 682) * 4; // heatsink temperature sensor // 10mV / degree K, 273K (0 Celcius) // = 2.730V == 682 counts // we want the temp in Celcius, so // subtract this offset, then *4 // to get degrees and tenths all_displayed = 0; } // every 1/10 second, send the data down the RS232 line if(sec_timer) { sec_timer = 0; // start next time round putch('0'+display.v1[0]); putch('0'+display.v1[1]); putch('.'); putch('0'+display.v1[2]); putch('0'+display.v1[3]); putch(' '); putch('0'+display.i1[0]); putch('.'); putch('0'+display.i1[1]); putch('0'+display.i1[2]); putch('0'+display.i1[3]); putch(' '); putch('0'+display.v2[0]); putch('0'+display.v2[1]); putch('.'); putch('0'+display.v2[2]); putch('0'+display.v2[3]); putch(' '); putch('0'+display.i2[0]); putch('.'); putch('0'+display.i2[1]); putch('0'+display.i2[2]); putch('0'+display.i2[3]); putch(' '); bin2bcd(res); if(Thou) putch('0'+Thou); // temp > 99 degrees, wow! // putch('0'+Thou); putch('0'+Hund); putch('0'+Tens); putch('.'); putch('0'+Ones); pr_crlf(); } goto loop; } // utility routine to loop for a given number of microseconds // calling overheads are 4.4uSec // each tick takes 3.2uSec void dly_us(unsigned char d) { TMR0 = 0; while(TMR0 < d) { // wait for timer to overflow asm("nop"); } } // routine to read the ADC for the given channel unsigned int adc(unsigned char chan) { unsigned int res; GIE = 0; // prevent display updates // during conversion, as the // noise can disturb the result ADCON0 = 0b10000000 | (chan << 3) | 0x1; // Fosc/32, A-D on // for given channel // (start aquisition) dly_us(7); // aquisition time ADIF = 0; GODONE = 1; // start conversion dly_us(2); // wait for conversion to finish while(!ADIF) { asm("nop"); } res = (ADRESH << 8) + ADRESL; GIE = 1; return(res); } /* Enqueue data in the transmit buffer to send down the serial line */ /* This routine adds one character at a time to the 32 byte buffer */ /* (the size is constrained b the compiler and RAM limitations of the */ /* processor). When a character has been added, the "new data" flag is */ /* set to tell the interrupt routine to restart sending data. */ /* Note: data can be added to the buffer faster than the interrupt */ /* routine can transmit it */ void putch(char c) { if(CommFlags.FULL) { /* Tx buffer full */ dly_us(250); /* wait 1.5 times the time needed to */ dly_us(250); /* send 1 char, should be enough */ // rt_ticks(250); } TXIE = 0; /* disable interrupts to avoid */ /* a race condition while we diddle */ /* the queue pointers */ TxBuf[TxIPtr++] = c & 0x7f; TxIPtr = TxIPtr & TxBUF_MASK; CommFlags.D2S = 1; /* new data to send */ if(TxOPtr == TxIPtr) { CommFlags.FULL = 1; } /* buffer now full */ TXIE = 1; /* safe to restart them now */ } /* print a CR LF pair for the end of each line */ void pr_crlf(void) { putch(13); putch(10); } /* Routine to send a string */ /* Assume normal "C" style string, terminated by a NULL char */ void SendStr(const char *p) { while(*p) { if(*p == '\n') { pr_crlf(); p++; } else { putch(*p++); } } } // translate the 4 bit value into a 7 segment pattern // Segment A -> RB0 A // B RB1 ... F B // G RB6 G // DP RB7 E C // D dp // To light a segment, the bit must be 0 const unsigned char bin7seg[16] = { 0b11000000, // 0 0b11111001, // 1 0b10100100, // 2 0b10110000, // 3 0b10011001, // 4 0b10010010, // 5 0b10000010, // 6 0b11111000, // 7 0b10000000, // 8 0b10010000, // 9 0b10010000, // A 0b10000011, // b 0b11001100, // C 0b10100001, // d 0b10000110, // E 0b10001110 // F }; void interrupt isr (void) { unsigned char c; // The 1mSec interrupt drives the 7 segment LED update. // each tick, display a different digit of the 16 we control if(TMR2IF) { TMR2IF = 0; // clear interrupt msec++; if(msec > 99) { sec_timer = 1; msec = 0; tenths++; if(tenths > 9) { tenths = 0; sec++; } } // do the display update here. // turn off the current digit, select the next one, // load it's contents and then turn it on PORTB = 0b11111111; // stop sinking current (turns display off) digit++; if(digit == 16) { all_displayed = 1; // get new round of measurements digit = -1; } else { // digit = digit & 0xf; if(digit&0b00001000) { RC5 = 1; RC3 = 0; // enable display #2 // for digits 8 - 15 } else { RC5 = 0; RC3 = 1; // enable display #1 } // for digits 0 - 7 if(digit&0b00000100) RC2 = 1; else RC2 = 0; if(digit&0b00000010) RC1 = 1; else RC1 = 0; if(digit&0b00000001) RC0 = 1; else RC0 = 0; c = bin7seg[display.d[digit]]; // output the new pattern /* The decimal points are at fixed locations, to give Voltage readings of XX.YY and current readings of A.BBB. We also want to supress the 10's digit of voltage displays if that value is zero */ if((digit == 1) || (digit == 4) || (digit == 9) || (digit == 12)) c &= 0x7f; // set the decimal point on, too if((digit == 0) && (c == 0b11000000)) c = 0b11111111; if((digit == 8) && (c == 0b11000000)) c = 0b11111111; PORTB = c; } // end of 1mSec } /* This code handle RS232 I-O, it processes inteerupts when the */ /* the transmit buffer has been emtied (by sending a char) */ /* and whent he receive buffer reads is loaded with an incoming char */ if(TXIF) { /* transmit buffer empty interrupt */ if(CommFlags.D2S) { TXREG = TxBuf[TxOPtr++]; TxOPtr = TxOPtr & TxBUF_MASK; CommFlags.FULL = 0; if(TxOPtr == TxIPtr) { /* buffer empty, turn off interrrupts */ CommFlags.D2S = 0; } } else { /* no data ready */ TXIE = 0; } } if(RCIF) { /* received character interrupt */ /* Note: we only want to fill the */ /* input buffer if this message is */ /* addressed to us. */ c = RCREG; RxBuf[RxIPtr++] = c; CommFlags.NEW = 1; if(c == '\n') CommFlags.EOL = 1; RxIPtr = RxIPtr & RxBUF_MASK; } }