/* * wattmeter.c ... firmware for a wattmeter based on atmega MCU * * Copyright (C) 2012 Jiri Pittner * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * */ // // //ATmega644-20PU@24MHz (this slight overclocking should work well) //we need some 32-bit integer calculations inside interrupt and a lot of floating point in non-realtime routines, so the higher frequency is useful //at 20Mhz it did not make sig_adc interrupt at 19200hz sampling freq with scope-samples function on // // //Usage of control buttons: // //In default mode: // 1. left press rotates range // 2. right press rotates displayed quantities // 3. left press while right being hold down forces hardware offset recalibration // 4. right press while left being hold down toggles software offset autozeroing //In menu mode: // 1. left press rotates menu items // 2. right press performs action of the selected menu item // 3. right press at empty menu item escapes from menu back to the default screen // //improvements if I would make it next time: schematics in eagle & design a PCB, use atxmega which offers 2 synchronized 12-bit ADCs or perhaps ARM STM32F4 with 12-bit ADCs and hardware floating point support; better some variable gain amplifier better than PGA202 particularly concerning the offset compensation (AD624?), or >8bit potentiometers for it; at least two different shunt resistors switchable by optotriacs to get better accuracy in the 1W and below range //PUT THIS TO MAKEFILE FOR LINKING: LIB=-lm -Wl,-u,vfprintf -Wl,-u,vfscanf -lprintf_flt -lscanf_flt //with DEBUG will not fit into atmega32 #undef DEBUG //#define DEBUG #define BAUD 115200 #define DO_OSCILLOSCOPE #include #include #include #include #include #include #include #include #include #include #include #include "atmega168compat.h" #define INTERRUPT ISR #ifndef GIMSK #define GIMSK GICR #endif #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) /* * PINOUT LCD display: PC0-3=DB4-7 PC4=E PC5=RW PC6=RS PC7=light (via NPN transistor) ADC0=PA0= voltage ADC1=PA1= current (from PGA202) ADC2=PA2= current*2 ADC3=PA3= current*5 ADC4=PA4= voltage*2 ADC5=PA5= Vcc5/3 ADC6=PA6= V1.28 after follower ADC7=PA7= V1.28 before follower PB0 = softI2C SDA PB1 = softI2C SCL PB2 = high for offset potentiometer PB3 = low for offset potentiometer PB4 = unused PB5-7 = ISP PD0,1 = UART comm PD2 = INT0 = right button with external pullup and capacitor PD3 = INT1 = left button with external pullup and capacitor PD4 = unused PD5,6 = gain select bits of PGA202 PD7 = pulses generated by ADC interrupt routine for debug * */ // //VALUES TO BE CALIBRATED // #define Vref 2.5252 #define Vccdivisor (11.028/(11.028+22.00)) #define Rsense 0.049466 #define Rdivisor1 3.3093e6 #define Rdivisor2 10.995e3 #define PHI_OFFSET (1.9/180.*3.1415926535); static const double gainv[2] = {1.,2.000}; //calibrate this against an ACCURATE ampermeter with 500W,50W,5W,0.5W loads, comparing Ieff on ranges 0,3,6,9 static const double gain1[4] = {1.001,10.01,99.9,1000.}; //PGA202 has 0.1% gain accuracy according to the datasheet, so calibration of this might be skipped //after updating the above values, with the 500W load toggle to ranges 1 and 2 and calibrate these - NECESSARY if not extra low tolerance resistors have been used static const double gain2[3] = {1.,2.002,4.942}; //if the variable calibrated is not 1, calibration will be done after reset static uint8_t calibrated __attribute__ ((section(".eeprom"))) = 1; static uint8_t cal_offset[2][4][3][2] __attribute__ ((section(".eeprom"))) = { {//no light {{86,126},{90,131},{91,134}}, {{98,139},{99,138},{95,137}}, {{98,139},{99,139},{99,139}}, {{115,139},{117,140},{117,140}} }, {//light {{84,124},{89,131},{91,134}}, {{98,139},{99,138},{95,137}}, {{98,139},{99,139},{99,139}}, {{115,139},{117,140},{117,140}} }, }; #ifdef CAL_DIRECTION static int8_t cal_direction[2][4] __attribute__ ((section(".eeprom"))) = { {//no light -1,1,1,1 }, {//light -1,1,1,1 }, }; #endif //I2C : hardware pullups assumed #define I2C_SDA PB0 #define I2C_SCL PB1 #define I2C_PORT PORTB #define I2C_PIN PINB #define I2C_DDR DDRB #define I2C_DELAY_USEC 10 #if defined(at90s2313) || defined(at90s8535) #else #define ATmega #endif #ifdef ATmega #define USR UCSRA #endif #if ( MCU != atmega644 && MCU != atmega1284 ) #define WDTO_8S WDTO_2S #endif watchdog(uint8_t onoff) { if(onoff) {wdt_enable(WDTO_8S); wdt_reset();} else {wdt_reset();wdt_disable();} } void xputchar(char z) { loop_until_bit_is_set(USR, UDRE); UDR =z; } void printP (const PGM_P string){ char c; c=pgm_read_byte(string); while (c) { loop_until_bit_is_set(USR, UDRE); UDR = c; c=pgm_read_byte(++string); } return; } void print (const char *string){ while (*string) { loop_until_bit_is_set(USR, UDRE); UDR = *string++; } return; } void scan(char *string){ char c; do { do { loop_until_bit_is_set(USR, RXC); c =UDR; } while bit_is_set(USR, FE); *string++ = c; //echo the character loop_until_bit_is_set(USR, UDRE); UDR = c; } while ( c != '\r' ); loop_until_bit_is_set(USR, UDRE); UDR = '\n'; string[-1]=0; } //UART initialize #ifdef ATmega #define UCR UCSRB #define UART_INIT(baud) { \ UBRRH=0; \ UBRRL= (XTAL/baud+15)/16-1; \ UCSRB=(1<>= 1) { delay_us(I2C_DELAY_USEC); if (m & data) sbi(I2C_PORT,I2C_SDA); else cbi(I2C_PORT,I2C_SDA); delay_us(I2C_DELAY_USEC); sbi(I2C_PORT,I2C_SCL); delay_us(I2C_DELAY_USEC); cbi(I2C_PORT,I2C_SCL); } // get Ack cbi(I2C_DDR,I2C_SDA); cbi(I2C_PORT,I2C_SDA); delay_us(I2C_DELAY_USEC); sbi(I2C_PORT,I2C_SCL); delay_us(I2C_DELAY_USEC); uint8_t r = bit_is_set(I2C_PIN, I2C_SDA); delay_us(I2C_DELAY_USEC); cbi(I2C_PORT,I2C_SCL); sbi(I2C_DDR,I2C_SDA); return r; } void i2c_start(void) { sbi(I2C_DDR,I2C_SDA); sbi(I2C_DDR,I2C_SCL); sbi(I2C_PORT,I2C_SCL); sbi(I2C_PORT,I2C_SDA); delay_us(I2C_DELAY_USEC); cbi(I2C_PORT,I2C_SDA); delay_us(I2C_DELAY_USEC); cbi(I2C_PORT,I2C_SCL); } void i2c_stop(void) { cbi(I2C_PORT,I2C_SDA); delay_us(I2C_DELAY_USEC); sbi(I2C_PORT,I2C_SCL); delay_us(I2C_DELAY_USEC); sbi(I2C_PORT,I2C_SDA); delay_us(I2C_DELAY_USEC); } uint8_t i2c_setpot(uint8_t adr,uint8_t pot,uint8_t value) { uint8_t r=0; i2c_start(); r+= i2c_wbyte(0x50|(adr<<1)); r<<=1; r+= i2c_wbyte(0xa8|(1+pot)); r<<=1; r+= i2c_wbyte(value); i2c_stop(); if(r) {printP(PSTR("I2C error ")); xputchar('0'+r); printP(PSTR("\n"));} return r; } //real time routines volatile uint32_t seconds=0; volatile int8_t centiseconds=0; double time_correction; //correction factor #if ( MCU == atmega644 || MCU == atmega1284 ) INTERRUPT(SIG_OUTPUT_COMPARE0A) #else INTERRUPT(SIG_OUTPUT_COMPARE0) #endif { if(++centiseconds == 100) {centiseconds=0; ++seconds;} } //UART routines volatile uint8_t inuartlen=0; volatile uint8_t uartline=0; #define MAXUART 64 volatile char inuart[MAXUART]; INTERRUPT(SIG_UART_RECV) { char c=UDR; if(uartline) return; //ignore until line is processed UDR = c; //echo if(c=='\n' || c== '\r' || inuartlen == MAXUART-1) {uartline=1; inuart[inuartlen]=0; inuartlen=0;} else inuart[inuartlen++]=c; } //display routines #define lcd_delay 50 volatile uint8_t light= 0; void lcd_w4bit(uint8_t rs, uint8_t x) { PORTC= 1<>4); lcd_w4bit(rs,x); } void lcd_clear(uint16_t delay) { lcd_wbyte(0,0x01); delay_xs(delay); } void lcd_init(void) { lcd_init4bit(); lcd_wbyte(0,0x28); lcd_wbyte(0,0x08); lcd_clear(30000); lcd_wbyte(0,0x06); lcd_wbyte(0,0x0c); } void lcd_print(const char *t) { while(*t) if(*t=='\n') {++t; lcd_wbyte(0,0xc0);} else lcd_wbyte(1,*t++); } void lcd_print8(const char *t) { uint8_t l=0; while(*t) {lcd_wbyte(1,*t++); ++l;} while(l<8) {lcd_wbyte(1,' '); ++l;} } void lcd_cursor(uint8_t r, uint8_t c) { lcd_wbyte(0,0x80+c+(r<<6)); } volatile uint16_t zero_offset; volatile int32_t sprod[2]; volatile int32_t sprodshifted[2]; volatile uint32_t sum2shifted[2]; volatile uint32_t sum2[2]; volatile uint32_t sum2x[2]; volatile uint32_t sum2xmax[2]; volatile uint32_t sprod2x[2]; volatile uint32_t sprod2xmax[2]; volatile uint16_t numx[2]; volatile uint16_t samplesx; volatile int32_t sum[2]; volatile int32_t sumshifted[2]; volatile int32_t sum1old; int32_t sum1initial; volatile uint16_t num[2]; volatile int16_t maxi[2]; volatile int16_t mini[2]; volatile int16_t maxix[2]; volatile int16_t minix[2]; volatile int16_t maxixsum[2]; volatile int16_t minixsum[2]; volatile uint16_t numxsum[2]; volatile int16_t last[2]; volatile int16_t lastshifted[2]; volatile uint16_t freq[2]; volatile uint8_t channel; volatile uint16_t forbid[2]; volatile uint16_t hz; uint16_t mains_hz=0; volatile uint16_t samples; volatile uint8_t overflowshift; volatile uint8_t autozero=1; volatile uint8_t fine_recal=1; volatile uint8_t fine_recal_steps=0; volatile uint8_t allownegative=0; volatile uint8_t adcchannel[2]; volatile uint8_t do_measure=0; volatile uint16_t period4,circptr; volatile int16_t *circbuf=NULL; volatile uint16_t period; volatile uint16_t sampleptr; volatile int16_t *samplebuf[2]; volatile uint16_t numper; typedef enum {R_OK=0, R_UNDERFLOW=1, R_OVERFLOW=2, R_TIMEOUT=3, R_DCOVERFLOW=4, R_FREQFAIL=5} R_ERR; #if ( MCU != atmega644 && MCU != atmega1284 ) #define TIFR1 TIFR #endif INTERRUPT(SIG_ADC) { sbi(PORTD,PD7); //hw debug to check measurement frequency and the dead times ADMUX= (1<=period4) circptr=0; } if(!do_measure) {cbi(PORTD,PD7);return;} //unfortunately power is not really measured during this time, just interpolated //accumulate samples for external scope plot; average over 2 periods to smooth it a bit if(samplebuf[0] && numper <2) { samplebuf[channel][sampleptr] += u.v; if(channel==1) { ++sampleptr; if(sampleptr >= period) {sampleptr=0; ++numper;} //inc. number of finished periods } } int32_t tmp =(int32_t) u.v * u.v; int32_t tmp2 = (int32_t) u.v * last[1-channel]; int32_t tmpshifted =(int32_t) u_v_shifted * u_v_shifted; int32_t tmp2shifted = (int32_t) u_v_shifted * lastshifted[1-channel]; //maximum effective values accumulation if(numx[channel] >= samplesx) { if(sum2x[channel] > sum2xmax[channel]) sum2xmax[channel]=sum2x[channel]; if(sprod2x[channel] > sprod2xmax[channel]) sprod2xmax[channel]=sprod2x[channel]; sum2x[channel] = 0; sprod2x[channel] = 0; numx[channel] = 0; minixsum[channel] += minix[channel]; maxixsum[channel] += maxix[channel]; numxsum[channel]++; minix[channel]=513; maxix[channel]=-513; } else { sum2x[channel] += tmp; sprod2x[channel] = tmp2; if(u.vmaxix[channel]) maxix[channel]=u.v; ++numx[channel]; } //main measurement accumulation if(num[channel] >=samples) {cbi(PORTD,PD7); return;} sum[channel] += u.v; sum2[channel] += tmp >>overflowshift; //prevent overflow sprod[channel] += tmp2 >>overflowshift; //prevent overflow sumshifted[channel] += u_v_shifted; sum2shifted[channel] += tmpshifted >>overflowshift; sprodshifted[channel] += tmp2shifted >>overflowshift; if(u.vmaxi[channel]) maxi[channel]=u.v; if(num[channel]>1) { if(forbid[channel]>0) --forbid[channel]; if(u.v==0 || last[channel]<0 && u.v>0|| last[channel] >0 && u.v<0) { if(!forbid[channel]) { ++freq[channel]; forbid[channel] = 5; } } } last[channel]=u.v; lastshifted[channel]=u_v_shifted; num[channel]++; channel ^= 1; cbi(PORTD,PD7); return; } static const double conversion[2] = { Vref/1024.*(Rdivisor1+Rdivisor2)/Rdivisor2, Vref/1024./Rsense }; static const uint8_t channels[3] = {1,2,3}; //ADC channels for current sense according to g2 static const uint8_t channelsv[2] = {0,4}; //ADC channels for voltage sense for 220/110 V mains static uint8_t vchannel; static double gain[2] = {1., 1.}; static const char label[2][2] = {"U","I"}; volatile uint8_t gainsel=0; volatile uint8_t rangechanged=0; #define MAXGAIN 11 void eraseall(void) { freq[0]=freq[1]=0; num[0]=num[1]=0; last[0]=last[1]=0; lastshifted[0]=lastshifted[1]=0; sum[0]=sum[1]=0; sum2[0]=sum2[1]=0; numx[0]=numx[1]=0; sum2x[0]=sum2x[1]=0; sprod2x[0]=sprod2x[1]=0; sum2xmax[0]=sum2xmax[1]=0; sprod2xmax[0]=sprod2xmax[1]=0; sprod[0]=sprod[1]=0; sumshifted[0]=sumshifted[1]=0; sum2shifted[0]=sum2shifted[1]=0; sprodshifted[0]=sprodshifted[1]=0; mini[0]=mini[1]=513; maxi[0]=maxi[1]=-513; minix[0]=minix[1]=513; maxix[0]=maxix[1]=-513; maxixsum[0]=maxixsum[1]=0; minixsum[0]=minixsum[1]=0; numxsum[0]=numxsum[1]=0; forbid[0]=forbid[1]=0; uint8_t i; uint8_t j; if(samplebuf[0]) memset(samplebuf[0],0,period*2*sizeof(int16_t)); sampleptr=0; numper=0; } volatile uint8_t pot_offsets[2]; void set_pots(uint8_t g1, uint8_t g2) { if(eeprom_read_byte(&calibrated)==1) { uint8_t i; for(i=0; i<2; ++i) { pot_offsets[i] = eeprom_read_byte(&cal_offset[light?1:0][g1][g2][i]); i2c_setpot(0,i,pot_offsets[i]); } delay_ms(g1>2?250:150); } } void initmeasure(uint16_t hz0,uint16_t samples0, uint8_t g, uint8_t setpot, uint8_t gv) { do_measure=0; hz=hz0; samples=samples0; samplesx = mains_hz==0?500:(hz/mains_hz); overflowshift=0; if(samples0>3000) overflowshift=1; if(samples0>8000) overflowshift=2; if(samples0>20000) overflowshift=3; if(samples0>40000) overflowshift=4; uint8_t g1,g2; char c[16]; gainsel=g; g2=g%3; g1=g/3; if(setpot) set_pots(g1,g2); if(samplebuf[0]) {free(samplebuf[0]); samplebuf[0]=NULL; samplebuf[1]=NULL;} if(circbuf) { //this might be in use by interrupt uint16_t *tmp=circbuf; circbuf=NULL; free(tmp); } if(mains_hz) { period4= hz/mains_hz/8; // 1/4 period and factor 1/2 since we switch between 2 channels so hz is effectively half circbuf= malloc(period4*sizeof(int16_t)); if(circbuf) {memset(circbuf,0,period4*sizeof(int16_t));} else printP(PSTR("circbuf malloc failed!\n")); #ifdef DO_OSCILLOSCOPE period= hz/mains_hz/2; samplebuf[0]= malloc(period*2*sizeof(int16_t)); if(samplebuf[0]) { memset(samplebuf[0],0,period*2*sizeof(int16_t)); samplebuf[1] = samplebuf[0]+period; } else printP(PSTR("samplebuf malloc failed!\n")); #else samplebuf[0]=samplebuf[1]=NULL; #endif } else { circbuf=NULL; samplebuf[0]=samplebuf[1]=NULL; } sampleptr=0; numper=0; circptr=0; sum1old=0; sbi(DDRD,PD5);sbi(DDRD,PD6); if(g1&1) sbi(PORTD,PD5); else cbi(PORTD,PD5); if(g1&2) sbi(PORTD,PD6); else cbi(PORTD,PD6); adcchannel[0]=channelsv[gv]; adcchannel[1]=channels[g2]; gain[0]=gainv[gv]; gain[1]=gain1[g1]*gain2[g2]; delay_ms(1); eraseall(); channel=0; TCCR1A=(1<rpower); print(c);printP(PSTR("W\n")); printP(PSTR("Apparent power = ")); sprintf(c,"%g",m->apower); print(c);printP(PSTR("VA\n")); printP(PSTR("Cos phi = ")); sprintf(c,"%g",m->cfinal); print(c);printP(PSTR("\n")); printP(PSTR("Sin phi = ")); sprintf(c,"%g",m->sfinal); print(c);printP(PSTR("\n")); printP(PSTR("Phi = ")); sprintf(c,"%g",m->phi*180/3.1415926535); print(c);printP(PSTR("\n")); printP(PSTR("Voltage eff = ")); sprintf(c,"%g",m->veff[0]); print(c);printP(PSTR("V\n")); printP(PSTR("Current eff = ")); sprintf(c,"%g",m->veff[1]); print(c);printP(PSTR("A\n")); if(!autozero) {printP(PSTR("Current Asymmetry = ")); sprintf(c,"%g",m->asymmetry[1]); print(c);printP(PSTR("A\n"));} printP(PSTR("Mutual Anharmonicity (1-c^2-s^2) = ")); sprintf(c,"%g",m->anharm2); print(c);printP(PSTR("\n")); printP(PSTR("Current Anharmonicity (Ipp/Ieff/sqrt8) = ")); sprintf(c,"%g",m->anharm[1]); print(c);printP(PSTR("\n")); printP(PSTR("Voltage Anharmonicity (Vpp/Veff/sqrt8) = ")); sprintf(c,"%g",m->anharm[0]); print(c);printP(PSTR("\n")); printP(PSTR("Real equivalent resistance U^2/power = ")); sprintf(c,"%g",m->R); print(c);printP(PSTR(" ohm\n")); printP(PSTR("Complex impedance U/I = ")); sprintf(c,"%g",m->Z[0]); print(c); printP(PSTR(" ")); sprintf(c,"%g",m->Z[1]); print(c);printP(PSTR(" ohm\n")); printP(PSTR("Equivalent serial resistance = ")); sprintf(c,"%g",m->Rser); print(c);printP(PSTR(" ohm\n")); printP(PSTR("Equivalent serial capacitance/inductance = ")); sprintf(c,"%.3g%2s",(m->LCser>0.?1e3:1e9)*fabs(m->LCser),m->LCser>0.?"mH":"nF"); print(c); printP(PSTR("\n")); printP(PSTR("Equivalent parallel resistance = ")); sprintf(c,"%g",m->Rpar); print(c);printP(PSTR(" ohm\n")); printP(PSTR("Equivalent parallel capacitance/inductance = ")); sprintf(c,"%.3g%2s",(m->LCpar>0.?1e3:1e9)*fabs(m->LCpar),m->LCpar>0.?"mH":"nF"); print(c); printP(PSTR("\n")); printP(PSTR("Voltage local maximum = ")); sprintf(c,"%g",m->veffmax[0]); print(c);printP(PSTR("V\n")); printP(PSTR("Current local maximum = ")); sprintf(c,"%g",m->veffmax[1]); print(c);printP(PSTR("A\n")); printP(PSTR("Apparent power local maximum = ")); sprintf(c,"%g",m->veffmax[0]*m->veffmax[1]); print(c);printP(PSTR("VA\n")); printP(PSTR("Voltage total maximum = ")); sprintf(c,"%g",*Vmax); print(c);printP(PSTR("V\n")); printP(PSTR("Current total maximum = ")); sprintf(c,"%g",*Imax); print(c);printP(PSTR("A\n")); printP(PSTR("Apparent power total maximum = ")); sprintf(c,"%g",*VAmax); print(c);printP(PSTR("VA\n")); printP(PSTR("True power total maximum = ")); sprintf(c,"%g",*Wmax); print(c);printP(PSTR("W\n")); printP(PSTR("Cumulative energy = ")); sprintf(c,"%g",*j/3.6e6); print(c);printP(PSTR("kWh\n")); } #ifdef DO_OSCILLOSCOPE void printsamples(const uint16_t nsamples, const int16_t *buf[2], uint16_t np, uint16_t hz) { if(!buf[0]) {printP(PSTR("not enough memory for samples\n")); return;} watchdog(1); char c[16]; printP(PSTR("BEGIN SAMPLE POINTS ")); sprintf(c,"%u",nsamples); print(c); printP(PSTR("\n")); uint16_t j; for(j=0; j= 128) return R_TIMEOUT; } } do_measure=0; if(printsampl) //this has to be done while measurement is stopped { printsamples(period,samplebuf,numper,hz); } for(i=0; i<2; ++i) { m->n[i] = num[i]; x0[i] = sum[i]; x[i] = sum2[i]; y[i] = sprod[i]; x0sh[i] = sumshifted[i]; xsh[i] = sum2shifted[i]; ysh[i] = sprodshifted[i]; xm[i]= sum2xmax[i]; ym[i]=sprod2xmax[i]; #ifdef DEBUG printP(PSTR("CHANNEL=")); itoa(i,c,10); print(c); if(i==1) {printP(PSTR(" GSEL=")); itoa(gainsel,c,10); print(c);} printP(PSTR(" NUM=")); itoa(num[i],c,10); print(c); printP(PSTR(" SUM=")); sprintf(c,"%ld",sum[i]); print(c); printP(PSTR(" SUM2=")); sprintf(c,"%lu",sum2[i]); print(c); printP(PSTR(" SPROD=")); sprintf(c,"%ld",sprod[i]); print(c); printP(PSTR(" SHIFTSUM=")); sprintf(c,"%ld",sumshifted[i]); print(c); printP(PSTR(" SHIFTSUM2=")); sprintf(c,"%lu",sum2shifted[i]); print(c); printP(PSTR(" SHIFTSPROD=")); sprintf(c,"%ld",sprodshifted[i]); print(c); printP(PSTR(" MIN=")); itoa(mini[i],c,10); print(c); printP(PSTR(" MAX=")); itoa(maxi[i],c,10); print(c); printP(PSTR(" SUM2XMAX=")); sprintf(c,"%lu",sum2xmax[i]); print(c); printP(PSTR(" SPROD2XMAX=")); sprintf(c,"%lu",sprod2xmax[i]); print(c); printP(PSTR(" MINSUM=")); itoa(minixsum[i],c,10); print(c); printP(PSTR(" MAXSUM=")); itoa(maxixsum[i],c,10); print(c); printP(PSTR(" NUMXSUM=")); itoa(numxsum[i],c,10); print(c); #endif if(retstat==0) { if((mini[i] <= -510 || maxi[i] >=510) && (i==0 || gainsel>0)) retstat= R_OVERFLOW; //NOTICE: huge overflow can affect also the other channel when quickly switching like here if(i==1 && (mini[i]==maxi[i] || mini[i] <= -510 &&maxi[i]<0 || maxi[i] >=510 && mini[i]>0)) retstat=R_DCOVERFLOW; } if(retstat==0 && (mini[i] > -200 && maxi[i] <200) && gainsel vpp[i] = maxi[i]-mini[i]; m->vpp[i] = maxixsum[i]; m->vpp[i] -= minixsum[i]; //take the difference in floats m->vpp[i] /= numxsum[i]; m->vpp[i] *= conversion[i]/gain[i]; m->asymmetry[i] = sum[i]; m->asymmetry[i] /= m->n[i]; if(autozero) { x0[i] /= m->n[i]; if(m->n[i]*x0[i]*x0[i] > x[i]) //sum2 would be negative { x0[i] = sqrt(x[i]/m->n[i]); } m->asymmetry[i] -= x0[i]; #ifdef DEBUG printP(PSTR(" CORR=")); sprintf(c,"%f",-x0[i]); print(c); printP(PSTR(" MINCORR=")); sprintf(c,"%f",mini[i]-x0[i]); print(c); printP(PSTR(" MAXCORR=")); sprintf(c,"%f",maxi[i]-x0[i]); print(c); #endif x[i] -= m->n[i]*x0[i]*x0[i]; if(x[i]<0.) x[i]=0.; //still possibly negative due to roundoff xm[i] -= samplesx*x0[i]*x0[i]; if(xm[i]<0.) xm[i]=0.; //and the same for shifted ones x0sh[i] /= m->n[i]; if(m->n[i]*x0sh[i]*x0sh[i] > xsh[i]) //sum2 would be negative { x0sh[i] = sqrt(xsh[i]/m->n[i]); } xsh[i] -= m->n[i]*x0sh[i]*x0sh[i]; if(xsh[i]<0.) xsh[i]=0.; //still possible roundoff } m->asymmetry[i] *= conversion[i]/gain[i]; m->veff[i] = sqrt(x[i]*(1<n[i])*conversion[i]/gain[i]; m->veffmax[i] = sqrt(xm[i]/samplesx)*conversion[i]/gain[i]; m->anharm[i] = m->vpp[i]/(2.*sqrt(2.)*m->veff[i])-1.; m->f[i] = freq[i]; m->f[i] *= .25*hz/m->n[i]; #ifdef DEBUG printP(PSTR(" ")); print(label[i]); printP(PSTR("asym=")); sprintf(c,"%g",m->asymmetry[i]); print(c); printP(PSTR(" ")); print(label[i]); printP(PSTR("pp=")); sprintf(c,"%g",m->vpp[i]); print(c); printP(PSTR(" ")); print(label[i]); printP(PSTR("eff=")); sprintf(c,"%f",m->veff[i]); print(c); printP(PSTR(" ")); print(label[i]); printP(PSTR("effmax=")); sprintf(c,"%f",m->veffmax[i]); print(c); printP(PSTR(" anharm=")); sprintf(c,"%g",m->anharm[i]); print(c); printP(PSTR(" current_f=")); sprintf(c,"%f",m->f[i]); print(c); printP(PSTR("\n")); #endif } //end i loop if(mains_hz>0 && fabs(m->f[0]-mains_hz)>10) retstat = R_FREQFAIL; if(mains_hz) {m->f[0] = m->f[1]= mains_hz;} //is more accurate then when counted from a short sampling set if(autozero) { uint8_t i; for(i=0; i<2; ++i) { double tmp = x0[i]*x0[1-i]; y[i] -= (m->n[i]-i)* tmp; ym[i] -= (samplesx-i)* tmp; } for(i=0; i<2; ++i) ysh[i] -= (m->n[i]-i)*x0sh[i]*x0sh[1-i]; } sum1old = sum[1]; eraseall(); channel=0; //capture time since last measurement double dt = seconds; dt -= start_s; dt += .01*(centiseconds-start_dms); dt*= time_correction; start_s=seconds; start_dms=centiseconds; do_measure=1; if(retstat==R_OVERFLOW || retstat == R_DCOVERFLOW || retstat == R_FREQFAIL) { #ifdef DEBUG printP(PSTR(" ERROR TYPE ")); xputchar('0'+retstat); printP(PSTR("\n")); #endif return retstat; } double z=sqrt(x[0]*x[1]); double omega=2.*3.1415926535*m->f[0]; #ifdef DEBUG printP(PSTR(" omega=")); sprintf(c,"%f",omega); print(c); #endif double cosd = cos(omega/hz); w[0]=y[0]/z; w[1]=y[1]/z*m->n[1]/(m->n[1]-1.); m->cfinal=(w[0]+w[1])/(2.*cosd); if(circbuf) { z=sqrt(xsh[0]*xsh[1]); wsh[0]=ysh[0]/z; wsh[1]=ysh[1]/z*m->n[1]/(m->n[1]-1.); m->sfinal=(wsh[0]+wsh[1])/(2.*cosd); } #ifdef DEBUG printP(PSTR(" cos0=")); sprintf(c,"%f",w[0]); print(c); printP(PSTR(" cos1=")); sprintf(c,"%f",w[1]); print(c); printP(PSTR(" cosd=")); sprintf(c,"%f",cosd); print(c); printP(PSTR(" RAWcosphi=")); sprintf(c,"%f",m->cfinal); print(c); if(circbuf) {printP(PSTR(" RAWsinphi=")); sprintf(c,"%f",m->sfinal); print(c);} #endif double norm; if(circbuf) { norm=sqrt(m->sfinal*m->sfinal + m->cfinal*m->cfinal); m->anharm2 = 1.-norm; if(norm>1.) norm=1.; } else //ignore sign of sin phi { m->anharm2 = sqrt(-1.); norm=1.; m->sfinal=sqrt(1.-m->cfinal*m->cfinal); } m->phi = atan2(m->sfinal,m->cfinal); #ifdef DEBUG printP(PSTR(" RAWphi=")); sprintf(c,"%f",m->phi*180./3.1415926535); print(c); #endif //adjust of phase angle (empirical shift, calibrated to get exactly cos=1 for resistive loads) m->phi += PHI_OFFSET; double cospure=cos(m->phi); double sinpure=sin(m->phi); m->cfinal = norm * cospure; m->sfinal = norm * sinpure; //final check of cosphi if(!allownegative && m->cfinal<0.) m->cfinal= 0.; //we never inject power into mains //compute impedance m->Z[0] = cospure*m->veff[0]/m->veff[1]; m->Z[1] = sinpure*m->veff[0]/m->veff[1]; #ifdef DEBUG printP(PSTR(" cosphi=")); sprintf(c,"%f",m->cfinal); print(c); printP(PSTR(" sinphi=")); sprintf(c,"%f",m->sfinal); print(c); printP(PSTR(" phi=")); sprintf(c,"%f",m->phi*180./3.1415926535); print(c); printP(PSTR(" ANHARM2=")); sprintf(c,"%f",m->anharm2); print(c); printP(PSTR(" Z=")); sprintf(c,"%f",m->Z[0]); print(c); printP(PSTR(" + J")); sprintf(c,"%f",m->Z[1]); print(c); #endif m->Rser = m->Z[0]; double Zsqr = m->Z[0]*m->Z[0]+m->Z[1]*m->Z[1]; m->Rpar = Zsqr/m->Z[0]; if(m->Z[1] >=0.) { m->LCser = m->Z[1]/omega; //inductor m->LCpar = Zsqr/m->Z[1]/omega; //inductor #ifdef DEBUG printP(PSTR(" Lser=")); sprintf(c,"%g",m->LCser); print(c); printP(PSTR(" Lpar=")); sprintf(c,"%g",m->LCpar); print(c); #endif } else { m->LCser = 1./(omega*m->Z[1]); //capacitor m->LCpar = m->Z[1]/Zsqr/omega;//capacitor #ifdef DEBUG printP(PSTR(" Cser=")); sprintf(c,"%g",-m->LCser); print(c); printP(PSTR(" Cpar=")); sprintf(c,"%g",-m->LCpar); print(c); #endif } m->apower = m->veff[0]*m->veff[1]; m->rpower = m->apower*m->cfinal; m->R = m->veff[0]*m->veff[0]/m->rpower; #ifdef DEBUG printP(PSTR("\n")); printP(PSTR("apparent power = ")); sprintf(c,"%f",m->apower); print(c);printP(PSTR("VA; ")); printP(PSTR("true power = ")); sprintf(c,"%f",m->rpower); print(c);printP(PSTR("W\n")); printP(PSTR("resistance = ")); sprintf(c,"%f",m->R); print(c);printP(PSTR("W\n")); #endif if(isfinite(m->rpower)) joules += m->rpower * dt; #ifdef DEBUG printP(PSTR("dt = ")); sprintf(c,"%f",dt); print(c);printP(PSTR("s ")); printP(PSTR("Energy = ")); sprintf(c,"%f",joules/3.6e6); print(c);printP(PSTR("kWh\n")); printP(PSTR("RETSTAT = ")); xputchar('0'+retstat); printP(PSTR("\n")); #endif return retstat; } //sampling_num should be integer fraction of sampling_hz and integer multiple of #of samples per mains period //number of samples per channel per quarter period must be integer (sampling_hz/8/mains_hz) uint16_t ee_sampling_hz __attribute__ ((section(".eeprom"))) = 19200; //default good for both 50 and 60 Hz, changeable uint16_t ee_sampling_num __attribute__ ((section(".eeprom"))) = 9600; //default good for both 50 and 60 Hz, changeable volatile uint16_t sampling_hz; volatile uint16_t sampling_num; volatile uint8_t sampling_hz_menu=8; //8 max volatile uint8_t sampling_num_menu=2; //16 max, for 4=sampling_hz int32_t cal_test0(uint8_t g1, uint8_t g2, uint8_t x) { i2c_setpot(0,0,x); delay_ms(200); watchdog(1); initmeasure(sampling_hz,sampling_num/5,3*g1+g2,0,vchannel); while(num[1]=0?(i):(-(i))) uint32_t cal_test(uint8_t g1, uint8_t g2, uint8_t *x) { i2c_setpot(0,0,x[0]); i2c_setpot(0,1,x[1]); delay_ms(200); watchdog(1); initmeasure(sampling_hz,sampling_num/2,3*g1+g2,0,vchannel); while(num[1]1) { watchdog(1); mi = ((uint16_t)hi+lo)>>1; s_mi = (*cal)(g1,g2,mi); { char c[64]; sprintf(c,"%d %ld; ",lo,s_lo); print(c); sprintf(c,"%d %ld; ",mi,s_mi); print(c); sprintf(c,"%d %ld; ",hi,s_hi); print(c); print("\n"); } if(s_mi==0) break; if(s_mi>0) { if(s_hi>0) { hi=mi; s_hi=s_mi; } else { lo=mi; s_lo=s_mi; } } else { if(s_hi<0) { hi=mi; s_hi=s_mi; } else { lo=mi; s_lo=s_mi; } } } if(ABS(s_mi)(y)?(x):(y)) void calibrate_one(uint8_t g1, uint8_t g2) { printP(PSTR("gains ")); xputchar('0'+g1); xputchar(' '); xputchar('0'+g2); xputchar('\n'); //first calibrate the pulldown (potentiometer 1) printP(PSTR("Potentiometer 1\n")); int16_t mi1=halfinterval(g1,g2,cal_test1); i2c_setpot(0,1,mi1); delay_ms(200); //the find root across pullup (potentiometer 0) printP(PSTR("Potentiometer 0\n")); int16_t mi0=halfinterval(g1,g2,cal_test0); uint8_t xmi[2]; #if 0 //fine resolution 2D search uint32_t ymi=0xffffffff; { uint8_t x[2]; for(x[1]=MAX(mi1-1,0); x[1]testm?1:-1); } #endif char c[32]; printP(PSTR("Calibrated: ")); #ifdef CAL_DIRECTION sprintf(c,"%d %d: %d %d %d\n",g1,g2,xmi[0],xmi[1],testp>testm?1:-1); #else sprintf(c,"%d %d: %d %d\n",g1,g2,xmi[0],xmi[1]); #endif print(c); } void calibrate_offsets(void) { uint8_t g1,g2; watchdog(1); lcd_clear(30000); lcd_print("Calibration"); printP(PSTR("\nBEGIN CALIBRATION\n")); for(g1=0; g1<4; ++g1) for(g2=0; g2<3; ++g2) { lcd_cursor(1,0); char c[16]="gains 0 0"; c[6] = '0'+g1; c[8] = '0'+g2; lcd_print(c); calibrate_one(g1,g2); } } volatile uint8_t autoranging=1; volatile uint8_t force_recalibrate=0; volatile uint8_t old_gainsel=0xff; #define MAXDISPLAYTYPE 14 volatile uint8_t displaytype=1; volatile uint8_t displaychanged; volatile uint8_t menuselect=0; volatile double totVmax; volatile double totImax; volatile double totWmax; volatile double totVAmax; void erasetots(void) { joules=totVmax=totImax=totWmax=totVAmax=0.; } volatile uint8_t rightcount=0; volatile uint8_t leftcount=0; volatile uint8_t rightwaspressed=0; volatile uint8_t leftwaspressed=0; #define N_MENU 8 INTERRUPT(SIG_INTERRUPT0) //right button { #if ( MCU == atmega644 || MCU == atmega1284 ) cbi(EIMSK,INT0); #else cbi(GIMSK,INT0); #endif delay_ms(1);//soft debounce #if ( MCU == atmega644 || MCU == atmega1284 ) sbi(EIMSK,INT0); #else sbi(GIMSK,INT0); #endif if(bit_is_clear(PIND,PD2)) //pressed { rightwaspressed=1; leftcount=0; return; } if(!rightwaspressed) return; rightwaspressed=0; if(bit_is_clear(PIND,PD3)) {++rightcount; return;} //right was now released while left not active switch(leftcount) //how many times left was pressed while right being held down { case 1: force_recalibrate=1; break; case 0: default: switch(menuselect) //change screens or perform actions in menu { case 0: displaytype = (displaytype+1)%MAXDISPLAYTYPE; displaychanged=1; break; case 1: //light light ^= 1<8) sampling_hz_menu=2; sampling_hz = 2400*sampling_hz_menu; break; case 6: //sampling_num_menu if(++sampling_num_menu > 12) sampling_num_menu=2; sampling_num= (sampling_hz/4) * sampling_num_menu; break; case 7: //force recalibrate force_recalibrate=1; break; } break; } } INTERRUPT(SIG_INTERRUPT1) //left button { #if ( MCU == atmega644 || MCU == atmega1284 ) cbi(EIMSK,INT1); #else cbi(GIMSK,INT1); #endif delay_ms(1);//soft debounce #if ( MCU == atmega644 || MCU == atmega1284 ) sbi(EIMSK,INT1); #else sbi(GIMSK,INT1); #endif if(bit_is_clear(PIND,PD3)) //pressed { rightcount=0; leftwaspressed=1; return; } if(!leftwaspressed) return; leftwaspressed=0; if(bit_is_clear(PIND,PD2)) {++leftcount; return;} //right is being pressed now //left was now released switch(rightcount) //how many times right button was pressed/released while left being pressed and held { case 1: //right inside left autozero ^= 1; //if(autozero) fine_recal=1; break; case 0: //simple button press default: { if(displaytype==0) //rotate menu { menuselect++; if(menuselect >= N_MENU ) menuselect=0; return; } //normal operation - rotate ranges if(autoranging) {autoranging=0; old_gainsel=gainsel;} else { if(old_gainsel!=0xff) {gainsel=0; old_gainsel=0xff;} else ++gainsel; rangechanged=2; if(gainsel>MAXGAIN) { gainsel=0; autoranging=1; rangechanged=1; } } break; } } } typedef char MENULABEL[17]; static const MENULABEL menulabel[N_MENU] = {" ","LIGHT ","AUTOZERO ","ALLOW NEGATIVE ","ZERO TOTALS ","S.RATE ","S.NUMBER ","RECALIBRATE "}; int main(void) { R_ERR r; MEASUREMENT meas; erasetots(); sbi(SREG,7); watchdog(1); UART_INIT(BAUD); printP(PSTR("Reset!\n")); uint8_t autoprint=0; sampling_hz = eeprom_read_word(&ee_sampling_hz); sampling_num = eeprom_read_word(&ee_sampling_num); //setup timer0 as realtime clock TCNT0=0; #if ( MCU == atmega644 || MCU == atmega1284 ) sbi(TIMSK0,OCIE0A); OCR0A = XTAL/1024/100; TCCR0A=(1<=16) //discard first few values until ADC stabilizes { if(i<64) vcctest +=v; if(vvccmax) vccmax=v; } } #ifdef DEBUG { char c[16]; printP(PSTR("Vcc average = ")); sprintf(c,"%.3f",vcctest/48.*Vref/Vccdivisor/1024.); print(c); printP(PSTR("V; Ripple Vpp = ")); sprintf(c,"%.3f",(vccmax - vccmin) *Vref/Vccdivisor/1024.); print(c); printP(PSTR("V\n")); } #endif } //calibrate zero offset #ifdef DEBUG printP(PSTR("Zero offset calibration: ")); #endif for(i=0; i<32; ++i) { watchdog(1); #if ( MCU == atmega644 || MCU == atmega1284 ) ADCSRB= 0; //free run ADC #else SFIOR &= 0x0f; #endif ADMUX= (1<=16) //discard first few values until ADC stabilizes { #ifdef DEBUG char c[8]; itoa(v,c,10); print(c); xputchar(' '); #endif zero_offset+=v; } } zero_offset >>= 4; #ifdef DEBUG printP(PSTR("\n")); #endif //setup up and down for offset i2c potentiometers sbi(DDRB,PB2); sbi(PORTB,PB2); cbi(PORTB,PB3); sbi(DDRB,PB3); if(eeprom_read_byte(&calibrated)!=1) { light=0; cbi(PORTC,PC7); calibrate_offsets(); light=1<totVmax) totVmax=meas.veffmax[0]; if(meas.veffmax[1]>totImax) totImax=meas.veffmax[1]; if(meas.rpower>totWmax) totWmax=meas.rpower; if(meas.apower>totVAmax) totVAmax=meas.apower; } watchdog(1); if(autoprint&1) printall(r,&meas,&joules,autozero,&totVmax,&totImax,&totVAmax,&totWmax,seconds+0.01*centiseconds); char form1[6], form2[6],form3[6]; sprintf(form1,"%%.%df",1+gainsel/3); sprintf(form2,"%%.%df",2+gainsel/3); sprintf(form3,"%%.%df",3+gainsel/3); if(rangechanged||displaychanged||r==R_OVERFLOW) lcd_clear(5000); //shorter delay argument since the counter is slowed substantially by running interrupts if(displaytype) { if(r==R_OVERFLOW) { lcd_cursor(0,0); lcd_print("OVERFLOW "); } if(r==R_DCOVERFLOW) { lcd_cursor(0,0); lcd_print("DC OVERFLOW "); } if(r==R_TIMEOUT) { lcd_cursor(0,0); lcd_print("TIMEOUT "); } if(r==R_FREQFAIL) { lcd_cursor(0,0); lcd_print("FREQFAIL "); } } if(displaytype==0||r==R_OK||r==R_UNDERFLOW) switch(displaytype) { case 0: //menu { lcd_cursor(0,0); lcd_print("MENU "); lcd_cursor(0,6); lcd_print(menulabel[menuselect]); lcd_cursor(1,0); switch(menuselect) { case 0: //to return to other displays lcd_print(" "); break; case 1: //light lcd_print(" "); break; case 2: //autozero lcd_print(autozero?"ON ":"OFF "); break; case 3: //allownegative lcd_print(allownegative?"ON ":"OFF "); break; case 4: //zero totals case 7: //force recalibrate lcd_print("PRESS RIGHT! "); break; case 5: //sampling_hz_menu { char c[17]; sprintf(c,"S.freq.= %d",sampling_hz); lcd_print(c); lcd_print(" "); } break; case 6: //sampling_num_menu { char c[17]; sprintf(c,"#samples= %d",sampling_num); lcd_print(c); lcd_print(" "); } break; } } break; case 1: //show apparent and true power and cos phi { char c[18]; lcd_cursor(0,0); sprintf(c,form1,meas.apower); lcd_print(c); lcd_print("VA "); lcd_cursor(0,9); sprintf(c,form1,meas.rpower); lcd_print(c); lcd_print("W "); lcd_cursor(1,2); lcd_print("cosphi="); if(fabs(meas.apower)>.9991e-4) {sprintf(c,"%.4f",meas.cfinal); lcd_print(c);} else lcd_print(" "); } break; case 3: //show anharmonicity { char c[18]; lcd_cursor(0,0); sprintf(c,"Anharm=%.3f",meas.anharm2); lcd_print(c); lcd_cursor(1,2); sprintf(c,"A2%.3f%.3f",meas.anharm[0],meas.anharm[1]); lcd_print(c); } break; case 2: //show Ipeak, VA peak { char c[18]; lcd_cursor(0,0); sprintf(c,form1,meas.veffmax[0]*meas.veffmax[1]); lcd_print(c); lcd_print("VA"); lcd_cursor(1,2); lcd_print("Imax= "); sprintf(c,form3,meas.veffmax[1]); lcd_print(c); lcd_print("A "); } break; case 4: //show impedance { char c[18]; lcd_cursor(0,0); sprintf(c," Zr=%.3f",meas.Z[0]); lcd_print(c); lcd_print(" "); lcd_cursor(1,2); sprintf(c,"Zi=%.3f",meas.Z[1]); lcd_print(c); lcd_print(" "); } break; case 5: //show resistance { char c[18]; lcd_cursor(0,0); sprintf(c,"Re=%.4gOhm",meas.R); lcd_print(c);lcd_print(" "); lcd_cursor(1,2); lcd_print(" "); } break; case 6: //show LCser { char c[18]; lcd_cursor(0,0); sprintf(c,"Rs= %.4gOhm",meas.Rser); lcd_print(c);lcd_print(" "); lcd_cursor(1,2); sprintf(c,"%1ss= %.3g%2s",(meas.LCser>0.?"L":"C"),(meas.LCser>0.?1e3:1e9)*fabs(meas.LCser),meas.LCser>0.?"mH":"nF"); lcd_print(c);lcd_print(" "); } break; case 7: //show LCpar { char c[18]; lcd_cursor(0,0); sprintf(c,"Rp= %.4gOhm",meas.Rpar); lcd_print(c);lcd_print(" "); lcd_cursor(1,2); sprintf(c,"%1sp= %.3g%2s",(meas.LCpar>0.?"L":"C"),(meas.LCpar>0.?1e3:1e9)*fabs(meas.LCpar),meas.LCpar>0.?"mH":"nF"); lcd_print(c);lcd_print(" "); } break; case 9: //show asymmetry { char c[18]; lcd_cursor(0,0); sprintf(c,"DC I=%.3f",meas.asymmetry[1]); lcd_print(c);lcd_print("A "); lcd_cursor(1,2); lcd_print(autozero?"AUTOZERO ON!!!":"(AUTOZERO OFF)"); } break; case 10: //show Veff, Ieff { char c[18]; lcd_cursor(0,0); sprintf(c," Veff=%.1f",meas.veff[0]); lcd_print(c);lcd_print("V "); lcd_cursor(1,2); sprintf(c,"Ieff=%.4f",meas.veff[1]); lcd_print(c);lcd_print("A "); } break; case 8: //show phi { char c[18]; lcd_cursor(0,0); sprintf(c,"Phi=%.2f",meas.phi*180./3.1415926535); lcd_print(c); lcd_cursor(1,2); lcd_print(" "); } break; case 13: //show energy { char c[18]; lcd_cursor(0,0); sprintf(c,"E=%.6f",joules/3.6e6); lcd_print(c);lcd_print("kWh "); lcd_cursor(1,2); lcd_print(" "); } break; case 11: //maxima VI { char c[18]; lcd_cursor(0,0); sprintf(c," Vmax=%.1f",totVmax); lcd_print(c);lcd_print("V "); lcd_cursor(1,2); sprintf(c,"Imax=%.4f",totImax); lcd_print(c);lcd_print("A "); } break; case 12: //maxima power { char c[18]; lcd_cursor(0,0); sprintf(c,"%.1f",totVAmax); lcd_print(c); lcd_print("VA "); lcd_cursor(0,9); sprintf(c,"%.1f",totWmax); lcd_print(c); lcd_print("W "); lcd_cursor(1,2); lcd_print("MAXIMUM "); } break; } displaychanged=0; //cannot recalibrate when we expect DC current component if(!autozero) {fine_recal=0; z[1]='.';} else z[1]= (fine_recal?'!':' '); if(autoranging) z[0]='A'; else z[0]= gainsel<10?'0'+gainsel : gainsel-10+'a'; if(displaytype) {lcd_cursor(1,0); lcd_print(z);} if(autoranging && r==R_UNDERFLOW) { ++gainsel; if(gainsel>=MAXGAIN+1) gainsel=MAXGAIN; rangechanged=1; fine_recal=1; } if(autoranging && r==R_OVERFLOW) { if(gainsel>0) --gainsel; rangechanged=1; fine_recal=1; } if(rangechanged==0) //perform fine recalibration on the fly (joules accumulation is spoiled by this - should be done only one recalibration is finished) { if(ABS(sum1old)0) pot_dir = -pot_dir; if(ABS(sum1old) > ABS(sum1initial) && fine_recal_steps>10 ) //the direction is wrong { sum1initial=sum1old; fine_recal_steps=0; pot_dir = -pot_dir; init_pot_dir= -1; } //allow faster steps at lower gains { if(gainsel<10) if(ABS(mini[1]+maxi[1])>50) pot_dir<<= 1; if(gainsel<9) if(mini[1]>0 || maxi[1]<0) pot_dir <<= 2; if(mini[1]>200 || maxi[1]<-200) pot_dir <<= 1; } //step in the direction if possible ++fine_recal_steps; if(pot_offsets[0]+pot_dir>=0 && pot_offsets[0]+pot_dir<256) pot_offsets[0]+= pot_dir; else {pot_dir= pot_dir>0?1:-1; if(pot_offsets[0]>0 && pot_offsets[0]<255) pot_offsets[0]+= pot_dir;} #ifdef DEBUG printP(PSTR("fine recal. ")); char c[8]; itoa(pot_offsets[0],c,10); print(c); printP(PSTR("\n")); #endif //unsuccessful termination i2c_setpot(0,0,pot_offsets[0]); if(pot_offset_last2 == pot_offsets[0] || fine_recal_steps == 255) fine_recal=0; pot_offset_last2= pot_offset_last; pot_offset_last=pot_offsets[0]; do_measure=0; delay_ms(gainsel>8?300:100); eraseall(); do_measure=1; } } if(rangechanged==2) { init_pot_dir = 1; sum1initial = sum1old; fine_recal_steps=0; initmeasure(sampling_hz,sampling_num,gainsel,1,vchannel); rangechanged=0; fine_recal=1; } if(rangechanged==1) { initmeasure(sampling_hz,sampling_num/10,gainsel,1,vchannel); rangechanged=2; fine_recal=1; } //process UART commands if(uartline) { watchdog(1); switch(inuart[0]) {//process uart commands case 'S': //change sampling frequency and number persistently case 's': //and nonpersistently sscanf(inuart+2,"%u %u",&sampling_hz, &sampling_num); if(inuart[0] == 'S') { eeprom_write_word(&ee_sampling_hz,sampling_hz); eeprom_write_word(&ee_sampling_num,sampling_num); } initmeasure(sampling_hz,sampling_num,gainsel,1,vchannel); rangechanged=1; break; case 'd': //set displaytype { uint16_t tmp; sscanf(inuart+2,"%u",&tmp); displaytype = tmp%MAXDISPLAYTYPE; } break; case 'v': //toggle voltage gain vchannel ^= 1; break; case 'a': //toggle autozero autozero ^= 1; //if(autozero) fine_recal=1; printP(PSTR("Autozero is now ")); if(autozero) printP(PSTR("ON\n")); else printP(PSTR("OFF\n")); break; case 'N': //allow negative power allownegative ^= 1; printP(PSTR("Allownegative is now ")); if(allownegative) printP(PSTR("ON\n")); else printP(PSTR("OFF\n")); break; case 'z': //erase totals erasetots(); break; case 'g': //set gain { int16_t g; sscanf(inuart+2,"%d",&g); if(g>0 && g <=MAXGAIN) { gainsel=g; rangechanged=2; } else { gainsel=0; autoranging=1; rangechanged=1; } } break; case 'R': //set calibration pots { uint16_t p[2]; uint8_t i; sscanf(inuart+2,"%d %d",p,p+1); for(i=0; i<2; ++i) { pot_offsets[i] = p[i]; i2c_setpot(0,i,pot_offsets[i]); } do_measure=0; delay_ms(gainsel>8?250:150); eraseall(); do_measure=1; fine_recal=0; } break; case 'Q': //query calibration pots { char c[16]; printP(PSTR("Potentiometers: ")); sprintf(c,"%d %d",pot_offsets[0],pot_offsets[1]); print(c); printP(PSTR("\n")); } break; case 'c': force_recalibrate=1; break; case 'p': printall(r,&meas,&joules,autozero,&totVmax,&totImax,&totVAmax,&totWmax,seconds+0.01*centiseconds); break; case 'P': //toggle print of measurement autoprint ^= 1; break; case 'o': //data for oscilloscope plot autoprint |=4; break; case 'O': //toggle permanent print of scope samples autoprint ^= 2; break; default: printP(PSTR("\nUnknown command\n")); } uartline=0; } if(force_recalibrate) { watchdog(1); pot_offsets[0] = halfinterval(gainsel/3,gainsel%3,cal_test0); printP(PSTR("Calibrated POT0 to: ")); char c[6]; itoa(pot_offsets[0],c,10); print(c);printP(PSTR("\n")); do_measure=0; eraseall(); initmeasure(sampling_hz,sampling_num,gainsel,1,vchannel); rangechanged=0; fine_recal=0; force_recalibrate=0; } } }