/************************************/ /*** Arduino Wattmeter v1.0 ***/ /*** Board: Arduino Nano 3.0 ***/ /*** By: Freddy Alferink ***/ /*** http://meettechniek.info ***/ /*** May 2014 ***/ /************************************/ /*********** Calibration & Hardware Data ***********/ float Vdiv = 101.0; // Voltage conversion factor float Cdiv = 10.0; // Current conversion factor const byte LCDlines = 2; // LCD: Number of lines const byte LCDwidth = 16; // LCD: Number of character per line /***************************************************/ const float adcSense = 1.074219e-3; // ADC conversion factor volt/bit (1.1V / 1024) float Vscale = 1.0; // Voltage scale float Cscale = 1.0; // Current scale byte muxCnt = 4; // Analog multiplexer start int nullVal = 512; // Measured null value boolean overFlowVolt = false; // Voltage overflow flag boolean overFlowCurr = false; // Current overflow flag unsigned int vCnt = 0; // Values counter /*** Voltage frequency measurement ***/ int vfmUpperTh = 10; // Upper threshold voltage int vfmLowerTh = -10; // Lower threshold voltage boolean vfmDir = false; // Direction voltage unsigned int vfmPeriods = 7; // Periods counter unsigned int vfmPeriodsTime = 1000; // Synchronized with periods time unsigned int vfmTime = 1000; // Time counter /*** Current frequency measurement ***/ int cfmUpperTh = 10; // Upper threshold current int cfmLowerTh = -10; // Lower threshold current boolean cfmDir = false; // Direction current unsigned int cfmPeriods = 5; // Periods counter unsigned int cfmPeriodsTime = 1000; // Synchronized with periods time unsigned int cfmTime = 1000; // Time counter /*** Primary averaging ***/ int primCnt = 0; // Primary averaging counter boolean primReady = false; // Primary averaging complete flag int adcVolt; // Copy ADC voltage const unsigned int primAvLength = 64; // Number of samples for primary averaging long primMeanVolt = 0; // Cumulative ADC mean voltage long primMeanVoltCopy = 0; // Averaged primary mean voltage long primSquaredVolt = 0; // Cumulative ADC squared voltage long primSquaredVoltCopy = 0; // Averaged primary squared voltage long primMaxVolt = 0; // Highest measured ADC voltage long primMinVolt = 0; // Lowest measured ADC voltage int adcCurr; // Copy ADC current long primMeanCurr = 0; // Cumulative ADC mean current long primMeanCurrCopy = 0; // Averaged primary current long primSquaredCurr = 0; // Cumulative ADC squared current long primSquaredCurrCopy = 0; // Averaged primary squared current long primMaxCurr = 0; // Highest measured ADC current long primMinCurr = 0; // Lowest measured ADC current long primMeanPow = 0; // Cumulative ADC mean power long primMeanPowCopy = 0; // Averaged primary mean power long primMaxPow = 0; // Highest measured ADC power long primMinPow = 0; // Lowest measured ADC power /*** Secondary averaging ***/ unsigned int secArrCnt = 0; const unsigned int secAvLength = 50; long secMeanVoltArr[secAvLength]; // Mean Voltage secondary averageing array long secSquaredVoltArr[secAvLength]; // Squared Voltage secondary averageing array long secMeanCurrArr[secAvLength]; // Mean Current secondary averageing array long secSquaredCurrArr[secAvLength]; // Squared Current secondary averageing array long secMeanPowArr[secAvLength]; // Real Power secondary averageing array long secMeanVolt = 0; // Result secondary averaging mean voltage long secSquaredVolt = 0; // Result secondary averaging squared voltage long secMeanCurr = 0; // Result secondary averaging mean current long secSquaredCurr = 0; // Result secondary averaging squared current long secMeanPow = 0; // Result secondary averaging mean power /*** Cumulative values ***/ byte tFlux[8]; // 64 bits integrated ADC voltage byte tCharge[8]; // 64 bits integrated ADC current byte tEnergy[8]; // 64 bits integrated ADC power unsigned int preTimeCnt = 0; // Time prescaler unsigned long timeCnt = 0; // Time seconds counter /*** ***/ float totAverage = 10.0; // Total averaging length (primary * secondary) float fSample = 10.0; // ADC channel sample frequency const byte LCDlinePos[4] = {0x80,0xC0,0x94,0xD4}; // First char position for each line (as DDRAM instruction) byte lineSelect = 0; // The selected dsplay line byte paramPointers[4] = {0,7,13,18}; // Parameter pointer for each display line char* paramLabels[] = {"Vmean ","Vrms ","Vsdev ","Vmax ","Vmin ","Flux ","f (V)","Imean ","Irms ","Isdev ","Imax ","Imin ","Charge","f (I) ","Preal ","S * ","Q * ","Pmax ","Pmin ","Energy","\x01 * ","time "}; char* paramUnits[] = {"V","V","V","V","V","Vs","Hz","A","A","A","A","A","C","Hz","W","VA","var","W","W","J","\x02","s"}; float paramValues[] = {0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0}; int paramRange[] = {0,0,0,0,0,99,99,0,0,0,0,0,99,99,99,0,0,0,0,99,99,100}; // 99=autorange, 100=int-number int paramDigits[] = {4,4,4,3,3,5,4,4,4,4,3,3,5,4,4,4,4,3,3,5,3,7}; // number of digits byte antiBounceCnt = 0; // Buttons anti bounce counter boolean buttonState = false; // Button is pressed unsigned int buttonPushed = 0; // Pressed buttons void setup() { MCUCR &= 0xEF; // Enable pull-up resistors DDRB &= 0xE1; // Buttons on D9...12 are inputs DDRB |= 0x20; // Overflow LED on D13 is output PORTB |= 0x1E; // Pull-up resistor for the buttons bitClear(PORTB, 5); // Overflow LED off initiateADC(); // initiate AD converter lcdInitiate(); // initiate LCD lcdCustomChars(); // Make custom characters lcdWrite(true, LCDlinePos[0]); // LCD cursor first line position 0 lcdPrintString("Universal power"); lcdWrite(true, LCDlinePos[1]); // LCD cursor second line position 0 lcdPrintString("meter v1.0"); for(int md=0; md<100; md++) { // Wait 2 seconds muDelay(20000); } lcdWrite(true, LCDlinePos[0]); // LCD cursor first line position 0 lcdPrintString("meettechniek "); lcdWrite(true, LCDlinePos[1]); // LCD cursor second line position 0 lcdPrintString(" .info"); for(int md=0; md<100; md++) { // Wait 2 seconds muDelay(20000); } setProperties(); // Set properties and Clear measured values updateParamLabels(); // Write Parameter labels to LCD updateParamValues(); // Write parameter values to LCD sei(); // set interrupt flag } void loop() { if (primReady == true) { /*** Secondary avearaging Mean Volt ***/ secMeanVolt -= secMeanVoltArr[secArrCnt]; // Subtract oldest value strored in array from average secMeanVolt += primMeanVoltCopy; // Add newest value to average secMeanVoltArr[secArrCnt] = primMeanVoltCopy; // Store newest value in array /*** Secondary avearaging Mean Squared Volt ***/ secSquaredVolt -= secSquaredVoltArr[secArrCnt]; // the same for squared voltage secSquaredVolt += primSquaredVoltCopy; secSquaredVoltArr[secArrCnt] = primSquaredVoltCopy; /*** Secondary avearaging Mean Current ***/ secMeanCurr -= secMeanCurrArr[secArrCnt]; // and mean current secMeanCurr += primMeanCurrCopy; secMeanCurrArr[secArrCnt] = primMeanCurrCopy; /*** Secondary avearaging Mean Squared Current ***/ secSquaredCurr -= secSquaredCurrArr[secArrCnt]; // squared current secSquaredCurr += primSquaredCurrCopy; secSquaredCurrArr[secArrCnt] = primSquaredCurrCopy; /*** Secondary avearaging Mean Power ***/ secMeanPow -= secMeanPowArr[secArrCnt]; // and power secMeanPow += primMeanPowCopy; secMeanPowArr[secArrCnt] = primMeanPowCopy; /*** Array Pointer ***/ secArrCnt++; // Increase secondary averaging array pointer if(secArrCnt >= secAvLength) { secArrCnt = 0; } /*** Cumulative values ***/ extraLongAddLong(tFlux, primMeanVoltCopy); // Add primary averaged voltage to total Flux extraLongAddLong(tCharge, primMeanCurrCopy); // Add primary averaged current to total Charge extraLongAddLong(tEnergy, primMeanPowCopy); // Add primary averaged power to total Energy primReady = false; } /*** Update display ***/ if (vCnt > 1000) { // After 1000 channel samples refresh values on display vCnt = 0; /*** Calculate values Voltage ***/ paramValues[0] = float(secMeanVolt) * Vscale / totAverage; // calc. mean Voltage paramValues[1] = sqrt(float(secSquaredVolt) / totAverage) * Vscale; // calc. RMS Voltage paramValues[2] = sqrt((paramValues[1]*paramValues[1])-(paramValues[0]*paramValues[0])); // calc. standard deviation Voltage: Vsdev=sqrt(Vrms^2 - Vmean^2) paramValues[3] = float(primMaxVolt) * Vscale; // Max voltage paramValues[4] = float(primMinVolt) * Vscale; // Min voltage /*** Calculate values Current ***/ paramValues[7] = float(secMeanCurr) * Cscale / totAverage; // calc. mean Current paramValues[8] = sqrt(float(secSquaredCurr) / totAverage) * Cscale; // calc. RMS Current paramValues[9] = sqrt((paramValues[8]*paramValues[8])-(paramValues[7]*paramValues[7])); // calc. standard deviation Current: Csdev=sqrt(Crms^2 - Cmean^2) paramValues[10] = float(primMaxCurr) * Cscale; // Max current paramValues[11] = float(primMinCurr) * Cscale; // Min current /*** Calculate values Power ***/ paramValues[14] = float(secMeanPow) * Vscale * Cscale / totAverage; // Real Power paramValues[17] = float(primMaxPow) * Vscale * Cscale; // Max Power paramValues[18] = float(primMinPow) * Vscale * Cscale; // Min Power paramValues[15] = paramValues[1] * paramValues[8]; // Apparent Power = Urms * Irms paramValues[16] = sqrt((paramValues[15]*paramValues[15])-(paramValues[14]*paramValues[14])); // Reactive power /*** Flux, Charge, Energy ***/ paramValues[5] = extraLongToFloat(tFlux) * Vscale / fSample; // Flux paramValues[12] = extraLongToFloat(tCharge) * Cscale / fSample; // Charge paramValues[19] = extraLongToFloat(tEnergy) * Vscale * Cscale / fSample; // Energy /*** Phase & Time ***/ paramValues[20] = acos(paramValues[14]/paramValues[15]); // Phase = acos(P/S) paramValues[21] = (float) round(timeCnt); // Time /*** Voltage Frequency ***/ if(vfmPeriodsTime > 10000 && vfmPeriods > 3) { vfmUpperTh = 2000; // Prevent asynchronious reading paramValues[6] = float(vfmPeriods) * fSample / float(vfmPeriodsTime-2); // calc. voltage frequency if(pow(10, paramRange[2]) / paramValues[2] > 100) { paramValues[6] = 0.0; } // Make zero if signal is too small vfmTime = 0; // Start new voltage freqency measurement } vfmUpperTh = int((paramValues[0] + (paramValues[2] / 2)) / Vscale); // Upper voltage threshold vfmLowerTh = int((paramValues[0] - (paramValues[2] / 2)) / Vscale); // lower voltage threshold /*** Current Frequency ***/ if(cfmPeriodsTime > 10000 && cfmPeriods > 3) { cfmUpperTh = 2000; // Prevent asynchronious reading paramValues[13] = float(cfmPeriods) * fSample / float(cfmPeriodsTime-2); // calc. current frequency if(pow(10, paramRange[9]) / paramValues[9] > 100) { paramValues[13] = 0.0; } // Make zero if signal is too small cfmTime = 0; // Start new voltage freqency measurement } cfmUpperTh = int((paramValues[7] + (paramValues[9] / 2)) / Cscale); // Upper current threshold cfmLowerTh = int((paramValues[7] - (paramValues[9] / 2)) / Cscale); // Lower current threshold /*** Update LCD values ***/ updateParamValues(); // Write values to LCD /*** OverFlow ***/ if (overFlowVolt == true || overFlowCurr == true) { // If there was an overflow ... overFlowVolt = false; // ... clear overflow flags ... overFlowCurr = false; bitSet(PORTB, 5); // ... turn overflow LED on. } else { bitClear(PORTB, 5); // Else overflow LED off. } } /*** Button Nulling values ***/ if (buttonPushed == 0x10) { // If "clear values" button is pressed ... if (~PINB & 0x02) { // ... and "line select" button ... buttonPushed = 0; setProperties(); // ... set parametes to zero. } } /*** Button Parameter Select Down ***/ else if (buttonPushed == 0x04) { // If "parameter select down" button is pressed ... buttonPushed = 0; changeParamPointer(false); // ... select previous parameter ... updateParamLabels(); // ... refresh parameter labels ... updateParamValues(); // ... and parameter values. } /*** Button Parameter Select Up ***/ else if (buttonPushed == 0x08) { // If "parameter select up" button is pressed ... buttonPushed = 0; changeParamPointer(true); // ... select next parameter ... updateParamLabels(); // ... refresh parameter labels ... updateParamValues(); // ... and parameter values. } /*** Button Line select ***/ else if (buttonPushed == 0x02) { // If "line select" buton is pressed ... buttonPushed = 0; updateParamLabels(); // ... refresh parameter labels ... lineSelect++; // ... select next line. if(lineSelect >= LCDlines) { lineSelect = 0; } lcdWrite(true, LCDlinePos[lineSelect]); // First position on selected line lcdPrintString("....."); // Indicate the line with ".....". vCnt = 0; // Zero the values refresh counter. } } /*** Clear extra long ***/ void extraLongClear(byte* elVal) { for(int ci=0; ci<8; ci++) { elVal[ci] = 0x00; // Clear all 8 bytes of the eztra long } } /*** Clear array ***/ void avArrClear(unsigned int arrLen, long* avArr) { for(int ci=0; ci paramMax) { paramPointers[lineSelect] = 0; } } else { if(paramPointers[lineSelect] == 0) { // Decrease parameter pointer for the selected line paramPointers[lineSelect] = paramMax + 1; } paramPointers[lineSelect]--; } } /*** Write parameter label on each line ***/ void updateParamLabels() { for (int ci=0; ci3; fi--) { // Find the first MS byte without leading zero's in the higherst 4 if(sign == false && (elVal[fi] != 0x00 || bitRead(elVal[fi-1], 7) == true)) { break; } // Find the first MS byte without leading 0xFF in the higherst 4 if(sign == true && (elVal[fi] != 0xFF || bitRead(elVal[fi-1], 7) == false)) { break; } } long tVal; long loVal = 0; for(int shi=24; shi>=0; shi-=8) { // Shift values for each byte tVal = long(elVal[fi--]); // Take first relevant byte loVal += tVal << shi; // and shift it into te long } float flVal = float(loVal); // Make it a float flVal *= pow(2.0, (fi + 1.0) * 8.0); // and multiply it by the last byte exponent return flVal; } /*** Add a long to an extra long ***/ void extraLongAddLong(byte* elVal, long addVal) { byte add0 = byte(addVal); // put the long in seperate bytes. addVal = addVal >> 8; byte add1 = byte(addVal); addVal = addVal >> 8; byte add2 = byte(addVal); addVal = addVal >> 8; byte add3 = byte(addVal); byte ext = 0x00; // extend the long with 0x00 ... if(bitRead(add3, 7) == true) { ext = 0xFF; // ... or 0xFF if the long is negative. } asm volatile( "add %[v0], %[a0]\n" // add the bytes of the long to the extralong "adc %[v1], %[a1]\n" "adc %[v2], %[a2]\n" "adc %[v3], %[a3]\n" "adc %[v4], %[bext]\n" "adc %[v5], %[bext]\n" "adc %[v6], %[bext]\n" "adc %[v7], %[bext]\n" : [v0] "+r" (elVal[0]), // ouput operand list [v1] "+r" (elVal[1]), [v2] "+r" (elVal[2]), [v3] "+r" (elVal[3]), [v4] "+r" (elVal[4]), [v5] "+r" (elVal[5]), [v6] "+r" (elVal[6]), [v7] "+r" (elVal[7]) : [a0] "r" (add0), // input operand list [a1] "r" (add1), [a2] "r" (add2), [a3] "r" (add3), [bext] "r" (ext) ); } void initiateADC() { ADCSRB = 0x00; DIDR0 = 0x30; // digital inputs disabled for A4 & A5 ADMUX = 0xC4; // measuring on ADC4, left adjust, internal 1.1V ref ADCSRA = 0xAE; // AD-converter on, interrupt enabled, prescaler = 64 ADCSRB = 0x40; // AD channels MUX on, free running mode bitWrite(ADCSRA, 6, 1); // Start the conversion by setting bit 6 (=ADSC) in ADCSRA } /*** Interrupt routine ADC ready ***/ ISR(ADC_vect) { /*** Read ADC result ***/ int adcInt = ADCL; // store lower byte ADC adcInt += ADCH << 8; // store higher bytes ADC long adcVal = long(adcInt) - nullVal; // relative ADC value /*** ADC multiplexer counter ***/ muxCnt++; // Multiplexer counter +1 muxCnt &= 0x03; ADMUX = muxCnt | 0xC4; /*** Voltage measurement ***/ if(muxCnt == 2) { // Result Analog voltage input ready if(adcInt < 12 || adcInt > 1010) { // Voltage overflow detection overFlowVolt = true; } adcVolt = adcVal; primMeanVolt += adcVal; // Primary add add Mean Voltage primSquaredVolt += adcVal * adcVal; // calc. and primary add squared value if(adcVal > primMaxVolt) { // Store new maximum voltage if greater primMaxVolt = adcVal; } if(adcVal < primMinVolt) { // Store new minimum voltage if smaller primMinVolt = adcVal; } /*** Voltage frequency ***/ if(vfmDir == true && adcVal > vfmUpperTh) { // If the upper threshold is reached ... vfmDir = false; // the next threshold is the negative one vfmPeriodsTime = vfmTime; // copy the current time into the period time if(vfmTime > 0) { vfmPeriods++; // If the measurement is running: increase the number of periods. } else { vfmTime = 1; // Else start the measurement ... vfmPeriods = 0; // ... and clear the number of periods } } else if(vfmDir == false && adcVal < vfmLowerTh) { // If the negative threshold is reached ... vfmDir = true; // ... the next threshold is the positive one. } if(vfmTime > 0) { // If the frequency measurement is running ... vfmTime++; } // increase time. } /*** Current measurement ***/ if(muxCnt == 3) { // Result Analog current input ready if(adcInt < 12 || adcInt > 1010) { // Current overflow detection overFlowCurr = true; } adcVal = -adcVal; // Inverting preamp adcCurr = adcVal; primMeanCurr += adcVal; // Primary add Mean Voltage primSquaredCurr += adcVal * adcVal; // calc. and primaty add squared value if(adcVal > primMaxCurr) { // Store new maximum current if greater primMaxCurr = adcVal; } if(adcVal < primMinCurr) { // Store new minimum current if smaller primMinCurr = adcVal; } /*** Current frequency ***/ if(cfmDir == true && adcVal > cfmUpperTh) { // If the upper threshold is reached ... cfmDir = false; // the next threshold is the negative one cfmPeriodsTime = cfmTime; // copy the current time into the period time if(cfmTime > 0) { cfmPeriods++; // If the measurement is running: increase the number of periods. } else { cfmTime = 1; // Else start the measurement ... cfmPeriods = 0; // ... and clear the number of periods } } else if(cfmDir == false && adcVal < cfmLowerTh) { // If the negative threshold is reached ... cfmDir = true; // ... the next threshold is the positive one. } if(cfmTime > 0) { // If the frequency measurement is running ... cfmTime++; } // increase time. } /*** Power calculation ***/ if(muxCnt == 0) { // Result Analog null reference input ready nullVal = long(adcInt); // Store null reference long TadcVal = long(adcVolt) * long(adcCurr); // Calc. the instantanious power ... primMeanPow += TadcVal; // ... and primary add it. if(TadcVal > primMaxPow) { // Store new maximum power if greater primMaxPow = TadcVal; } if(TadcVal < primMinPow) { // Store new minimum power if smaller primMinPow = TadcVal; } } /*** Transfer primary averaged values ***/ if(muxCnt == 1) { // At a quiet moment ... (Analog channel 7 isn't used) primCnt++; // increase primary averaging counter if(primCnt >= primAvLength) { // If the required averaging number is reached ... primCnt = 0; primMeanVoltCopy = primMeanVolt; // Make a copy of all the primary averaging values ... primMeanVolt = 0; // ... and clear the primary averaging value. primSquaredVoltCopy = primSquaredVolt; primSquaredVolt = 0; primMeanCurrCopy = primMeanCurr; primMeanCurr = 0; primSquaredCurrCopy = primSquaredCurr; primSquaredCurr = 0; primMeanPowCopy = primMeanPow; primMeanPow = 0; primReady = true; // The primary averaging values are availeble for the secondary averaging. } vCnt++; // value count +1 // Increase value counter /*** Button debouncing ***/ byte buttons = ~PINB & 0x1E; // Read the four buttons if(buttons == 0) { // If no button is pressed ... if(antiBounceCnt == 0) { // ... and anti bounce is already zero ... buttonState = false; buttonPushed = 0; // clear the button pressed register } else { antiBounceCnt--; } // decrease anti bounce counter if it is not zero } else { if(antiBounceCnt < 40) { antiBounceCnt++; } // Increase anti bounce counter if not maximum else if(buttonState == false) { // If anti bounce reaches maximum the first time ... buttonState = true; buttonPushed = buttons; // ... copy button status for further processing } } } /*** Time ***/ preTimeCnt++; if(preTimeCnt >= 19231) { // Seconds counter prescaler preTimeCnt = 0; timeCnt++; // Seconds counter } //bitClear(PORTB, 5); // overflow LED off } void lcdInitiate() { /*** Initialize 2*16 char LCD ***/ /*** LCD DB4...7 => PD4...7 (D4...7), RS => PD2 (D2), R/W => PD3 (D3), E => PB0 (D8) ***/ bitClear(PORTB, 0); // LCD enable = low bitSet(DDRB, 0); // LCD enable = output PORTD = PORTD & 0x03; // LCD D4...7 & R/W & RS = low DDRD = DDRD | 0xFC; // LCD D4...7 & R/W & RS = output muDelay(50000); byte PD = PORTD & 0x03; // LCD in 4 bit mode PORTD = PD | 0x20; __asm__("nop\n\t"); bitSet(PORTB, 0); // E high __asm__("nop\n\tnop\n\tnop\n\tnop\n\t"); bitClear(PORTB, 0); // E low __asm__("nop\n\tnop\n\t"); // Write function set three times, else the second line isn't functional. lcdWrite(true, 0x28); // 2 line mode, 5*7 dots muDelay(40); lcdWrite(true, 0x28); // 2 line mode, 5*7 dots muDelay(40); lcdWrite(true, 0x28); // 2 line mode, 5*7 dots muDelay(40); lcdWrite(true, 0x0C); // display on, cursor off, blink off lcdWrite(true, 0x01); // display clear lcdWrite(true, 0x06); // increment mode, entire shift off } void lcdPrintString(const char* strArr) { /*** Write a text string to the LCD ***/ for(byte ci=0; ci> 4); // Read LS nibble bitClear(PORTB, 0); // enable = 0 __asm__("nop\n\tnop\n\tnop\n\t"); } while(bitRead(dVal, 7) == HIGH); bitClear(PORTD, 3); // R/W = 0 } void lcdCustomChars() { /*** Make some custom characters ***/ lcdWrite(true, 0x48); // Char 0x01 = phi lcdWrite(false, 0x02); lcdWrite(false, 0x15); lcdWrite(false, 0x15); lcdWrite(false, 0x0E); lcdWrite(false, 0x04); lcdWrite(false, 0x04); lcdWrite(false, 0x04); lcdWrite(false, 0x00); lcdWrite(true, 0x50); // Char 0x02 = deg lcdWrite(false, 0x0C); lcdWrite(false, 0x12); lcdWrite(false, 0x12); lcdWrite(false, 0x0C); lcdWrite(false, 0x00); lcdWrite(false, 0x00); lcdWrite(false, 0x00); lcdWrite(false, 0x00); lcdWrite(true, 0x80); // First char address } void lcdTechNot(float fval, long lval, int rangeExp, int digits, const char* unit, boolean overFlow, byte pos, boolean alignR) { /*** Write a value in Technical Notation on the LCD ***/ /*** fval = float value, ore use lval = long int value ***/ /*** rangeExp = exponent of the limit value (e.g. rangeExp=2 => 10^2=100 as limit), 99=autorange, 100 = use the long int value ***/ /*** digits = number of digits ***/ /*** unit = the unit: "V", "A",... ***/ /*** overFlow = external overflow indicator, e.g. from ADC. Puts the "^" char between number and unit ***/ /*** pos & alignR = LCD position & right align: last position if alignR=true, first position if alignR=false) ***/ byte totChar = 11; // Total number of chars to fill on the LCD (to ensure the old valu is erased) char valStr[totChar]; // Text string int strPnt = 0; // Text string pointer digits -= 1; int decpoint = digits + 3; // No decimal point if val is long char vvArr[] = {'f','p','n',0xE4,'m',' ','k','M','G','T','P'}; // prefix array int expo; // Exponent if (rangeExp == 99) { // If auto range ... expo = floor(log10(fabs(fval))); // ... use the value exponent ... } else { expo = rangeExp - 1; // ... else use a fixed exponent as range } if(rangeExp != 100) { // If the float value is used ... lval = long(round(fval / pow(10, expo - digits))); // ... calculate the value in N digits. if(lval >= round(pow(10, digits + 1))) { // Fix floating point errors lval /= 10; expo++; } decpoint = digits - ((expo+15) % 3); // Calc. decimal point position } else { // If the long int value is used ... expo = 0; // ... prevent using a prefix. } if(lval < 0) { // If the value is negative ... lval = abs(lval); // ... make the value absolute ... valStr[strPnt++] = 0x2D; // ... and put a negative sign => string } long dec; byte num; boolean prtStart = false; // Don't push any number into the string yet (no leading zero's) for(int nd=digits; nd>=0; nd--) { // For the number of digits the value is long ... dec = long(round(pow(10, nd))); // calc. the 10th power number for the current digit, num = 0; // start value is 0. while(lval >= dec) { // While the 10th power number is smaller than the value ... lval -= dec; // decrease the value, num++; // increase the current digit. if(num > 9) { // If digit > 9 ... num = 0x3C; // digit is "<" if value doesn't fit lval += dec; // for all digit places break; } } if(prtStart == true || num != 0 || nd == 0) { // If printing is active or digit is not zero or LS digit prtStart = true; // printing is active, valStr[strPnt++] = num | 0x30; // digit => string. } else if(prtStart == false && decpoint == nd) { // If no number is printed yet and decimal point must be printed prtStart = true; // printing is active, valStr[strPnt++] = 0x30; // 0 => string. } if(decpoint == nd && nd != 0) { // If decimal point must be printed and it is not the LS digit valStr[strPnt++] = 0x2E; // decimal point => string. } } if(overFlow == true) { valStr[strPnt++] = 0x5E; // "^" => string if there the external overflow flag is set } else { valStr[strPnt++] = 0x20; // or a white space => string } int ediv = floor(expo / 3.0); // Calc. prefix pointer if (ediv != 0) { valStr[strPnt++] = vvArr[ediv+5]; // Prefix => string } byte ci; for(ci=0; ci