0

I want to calculate an average value on my microcontroller with this formula:

uint16_t uAverage;
uint64_t counter;
uint16_t u;

uAverage = uAverage * (1 + 1 / counter) + u / counter;

But I had problems with those calculations in the past and this is also not working, because of the different types and also because of the division which should return a float value.

Can you please show me the best way how to cast in this formula.?

Edit: This is the whole source at the moment. This programm can measure an electrical power with the ADCs. If you have questions, please ask. microcontroller: ATmega2560

#include <avr/io.h>
#include <avr/interrupt.h>
#include <inttypes.h>
#include "LITECShieldDefinitions.h"
#include "ADC.h"
#include "Timer.h"
#include "HWTimer.h"
#include "LCD.h"
#include "USART.h"

volatile uint8_t setOffset = 0;
volatile uint8_t resetValues = 0;
volatile uint8_t resetSecondValues = 0;

volatile uint64_t counter = 1;
volatile uint64_t counterSecond = 1;
volatile uint16_t u = 0;
volatile uint16_t i = 0;
volatile int32_t p = 0;
volatile uint16_t uMin = 1023;
volatile uint16_t iMin = 1023;
volatile int32_t pMin = 1046529;
volatile uint16_t uMax = 0;
volatile uint16_t iMax = 0;
volatile int32_t pMax = 0;
volatile uint16_t uAverage = 0;
volatile uint16_t iAverage = 0;
volatile int32_t pAverage = 0;
volatile uint16_t uSecondAverage = 0;
volatile uint16_t iSecondAverage = 0;
volatile int32_t pSecondAverage = 0;
volatile uint16_t uOffset = 0;
volatile uint16_t iOffset = 0;

ISR(TIMER1_OVF_vect) {
    TimerLoadValue(TIMER_1, 3000);
    resetSecondValues = 1;

    //TODO: print values to LCD

    printf("u=%" PRIu16 "\n", u);
    printf("i=%" PRIu16 "\n", i);
    printf("p=%" PRId32 "\n", p);
    printf("uMin=%" PRIu16 "\n", uMin);
    printf("iMin=%" PRIu16 "\n", iMin);
    printf("pMin=%" PRId32 "\n", pMin);
    printf("uMax=%" PRIu16 "\n", uMax);
    printf("iMax=%" PRIu16 "\n", iMax);
    printf("pMax=%" PRId32 "\n", pMax);
    printf("uAverage=%" PRIu16 "\n", uAverage);
    printf("iAverage=%" PRIu16 "\n", iAverage);
    printf("pAverage=%" PRId32 "\n", pAverage);
    printf("uSecondAverage=%" PRIu16 "\n", uSecondAverage);
    printf("iSecondAverage=%" PRIu16 "\n", iSecondAverage);
    printf("pSecondAverage=%" PRId32 "\n", pSecondAverage);
    printf("uOffset=%" PRIu16 "\n", uOffset);
    printf("iOffset=%" PRIu16 "\n\n", iOffset);
}

ISR(USART0_RX_vect) {
    char data = UDR0;

    if (data == 'S') {
        setOffset = 1;
        printf("S\n");
    } else if (data == 'R') {
        resetValues = 1;
        printf("R\n");
    } else {
        printf("S, R\n");
    }
}

int main(void) {
    DDRD = 0x00;
    PORTD = 0xff;

    LCDInit();
    LCDClear();
    LCDString(1, 1, "Wattmeter ");
    USARTInit(0, 19200, 1, 1, 1, 0);
    printf("Wattmeter\n");
    sei();
    TimerEnableOVFInt(TIMER_1);
    TimerEnable(TIMER_1, PRE_DIV_256, TIMER_MODE0_NORMAL);
    TimerLoadValue(TIMER_1, 3000);
    ADCInit(ADC_VREF_TYPE_INTERNAL_AREF);

    while(1) {
        if (S1 || setOffset) {
            setOffset = 0;
            uOffset = u;
            iOffset = i;
        }

        if (S0 || resetValues) {
            resetValues = 0;
            counter = 1;
            counterSecond = 1;
            u = 0;
            i = 0;
            p = 0;
            uMin = 1023;
            iMin = 1023;
            pMin = 1046529;
            uMax = 0;
            iMax = 0;
            pMax = 0;
            uAverage = 0;
            iAverage = 0;
            pAverage = 0;
            uSecondAverage = 0;
            iSecondAverage = 0;
            pSecondAverage = 0;
        }

        if (resetSecondValues) {
            resetSecondValues = 0;
            counterSecond = 1;
            uSecondAverage = 0;
            iSecondAverage = 0;
            pSecondAverage = 0;
        }

        u = ADCReadChannel(0);  //max. 10 bit value
        i = ADCReadChannel(1);

        int16_t uReal = u - uOffset;
        int16_t iReal = i - iOffset;
        p = (int32_t) uReal * iReal;

        if (u < uMin) {
            uMin = u;
        }

        if (i < iMin) {
            iMin = i;
        }

        if (p < pMin) {
            pMin = p;
        }

        if (u > uMax) {
            uMax = u;
        }

        if (i > iMax) {
            iMax = i;
        }

        if (p > pMax) {
            pMax = p;
        }

        uAverage = uAverage * (1 - 1 / counter) + u / counter;
        iAverage = iAverage * (1 - 1 / counter) + i / counter;
        pAverage = pAverage * (1 - 1 / counter) + p / counter;
        uSecondAverage = uSecondAverage * (1 - 1 / counterSecond) + u / counterSecond;
        iSecondAverage = iSecondAverage * (1 - 1 / counterSecond) + i / counterSecond;
        pSecondAverage = pSecondAverage * (1 - 1 / counterSecond) + p / counterSecond;

        counter ++;
        counterSecond ++;
    }
}
stonar96
  • 1,359
  • 2
  • 11
  • 39
  • I don't think it's really possible to provide meaningful guidance based on your current description of the problem. Could you perhaps describe (mathematically) what your expected output is, or provide some examples of where you expect rounding and truncation to occur? – Gian Sep 23 '14 at 20:28
  • `1 / counter` will give you 0 in almost all cases, i.e., if `counter>1`. It will give you 1 if `counter==1`, and it will generate a runtime exception if `counter==0`. – barak manos Sep 23 '14 at 20:34
  • That's the problem, so I have to cast it to a float, or a double? What part should I cast then? Casting the whole formula to float would be inefficient right? And this should be very fast and efficient. – stonar96 Sep 23 '14 at 20:37
  • The values are not 0, I just wrote down the types of the variables. counter starts with one. – stonar96 Sep 23 '14 at 20:39
  • 2
    possible duplicate of [Division result is always zero](http://stackoverflow.com/questions/2345902/division-result-is-always-zero) – M.M Sep 23 '14 at 20:43
  • `uAverage` must be float or double. instead of 1 use 1.0. and your calculation gives for 1 and 2 average of 2.5 instead of 1.5 (average=1 u=2 and counter=2 gives: 1*(1+0.5)+2/2=2.5) shouldn't it be: 1-1/counter? – SHR Sep 23 '14 at 20:53
  • uAverage must not be float or double. it's a value between 0 and 1023 (10 bit value from ADC channel) in my programm and rounded values are accurate enough – stonar96 Sep 23 '14 at 21:31
  • Do you really need a 64-bit counter? It'll take million to billion years for your MCU to count all 2^64 values – phuclv Sep 24 '14 at 04:13
  • There is a significant problem with your code, I.E. interrupt functions cannot use printf() . Mostly because printf() heavily uses interrupts. – user3629249 Sep 24 '14 at 04:48

3 Answers3

1

Should one want a non-floating point answer:

Perform one integer division. Get a rounded quotient by adding counter/2 before dividing.

Improved precision is available depending on the allowable range of the variables by scaling uAverage.

uint16_t uAverage;
uint64_t counter;
uint16_t u;

// uAverage = uAverage * (1 + 1 / counter) + u / counter;
// uAverage = uAverage + uAverage / counter  + u / counter;
// uAverage = (uAverage*counter + uAverage + u) / counter;
uAverage = (uAverage*counter + uAverage + u + counter/2) / counter;
//  or 
const unsigned Scale = 16;  // Largest value that does not overflow computations.
uScAverage = (uScAverage*counter + uScAverage + scale*(u + counter/2)) / counter;
uAverage =  (uScAverage + Scale/2)/Scale;
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
0

Dividing an integer by an integer returns an integer.

Take 1 / counter for example:

  • If counter > 1, then it will return 0
  • If counter == 1, then it will return 1
  • If counter == 0, then it will generate an exception

So you should basically cast at least one of the operands to float or double.

That being said, here is a solution to the calculation that you're trying to perform:

uAverage += (double)(uAverage+u)/counter;
barak manos
  • 29,648
  • 10
  • 62
  • 114
  • but what happens if counter gets very large, as you can see counter is uint64_t. Isn't a double to small for those values? – stonar96 Sep 23 '14 at 20:48
  • @stonar96: It is, but it matters only when `counter` becomes very large, most certainly larger than the sum of `uAverage+u`, which are 16-bit each. In such case, the result of the division will be less than 1, and the value added to `uAverage` will be 0. You can't achieve better accuracy that that anyway, since `uAverage` is an integer. – barak manos Sep 23 '14 at 20:50
  • Ok thanks, but is it possible to cast every possible value of counter to a double? – stonar96 Sep 23 '14 at 20:59
  • If you are using a micro-controller, check that you have a floating point unit before using floating point operations (the `double` cast here). Floating point software emulation takes a LOT of cycles. – ouah Sep 23 '14 at 21:00
  • @stonar96: Yes, but for large values it will not yield an accurate result. – barak manos Sep 23 '14 at 21:27
  • @ouah: Thanks. Coming from similar background (TI C64 fixed-point processor), I definitely agree with you. However, this becomes an issue only when the floating-point operation is performed repetitively, e.g., inside a `for` loop. Since OP does not specify the context in which this operation is performed, I cannot relate to that issue. – barak manos Sep 24 '14 at 07:39
0

This works:

Note to the fix in the calculation: yours gives for 1 and 2 the result 2.5...

float uAverage=0;
int counter=0;
int u=0;

while(scanf("%d",&u)) {//can type any no numeric to quit...
    counter++;
    uAverage = uAverage * (1.0 - 1.0 / counter) + (float)u / counter;
    printf("%f\n",uAverage);
}
M.M
  • 138,810
  • 21
  • 208
  • 365
SHR
  • 7,940
  • 9
  • 38
  • 57
  • Thanks for the fix, this was a typing mistake. Will this work also with my datatypes? uAverage should not be a float and the counter can get really large, that's why counter is an uint64_t. – stonar96 Sep 23 '14 at 21:08
  • It should work if any calculation have at least 1 float type, since mixing int and float gives float(that is the reason for casting the u). if uAverage has int type you just lose precision. – SHR Sep 23 '14 at 21:12
  • Any reason you're mixing floats and doubles here? To do the whole thing in `float` precision use `1.f` ; or to do it in `double` precision do `(double)u` or `1.0 * u`. – M.M Sep 23 '14 at 21:48