10

Currently I am developing a graphic LCD system to display temperatures, flows, voltages, power and energy in a heat pump system. The use of a graphic LCD means that half of my SRAM and ~75% of my flash have been used up by a screen buffer and strings.

I am currently displaying min/max/average figures for energy At midnight when the daily figure resets, the system checks if the consumption for the day is above or below the previous minimum or maximum, and stores the value. The average is calculated by dividing the cumulative energy consumption by the number of days.

I would like to display the daily average over the last week and month (4 weeks for simplicity) i.e. a rolling average. Currently this involves maintaining an array of values for the last 28 days and calculating an average over the whole array for monthly and last 7 days for weekly.

Initially I was doing this using an array of floats (as the energy is in the form "12.12kWh"), but this was using 28 * 4 bytes = 112 bytes (5.4% of SRAM). I don't mind having only a single decimal point of resolution, so I changed to using uint16_t and multiplying the figure by 100. This means that 12.12 is represented as 1212, and I divide by 100 for display purposes.

The size of the array is now down to 56 bytes (much better!).

There is no trivial way to reduce the figure down to a uint8_t that I can see. I could tolerate the loss of a decimal place ("12.1kWh" instead of "12.12kWh"), but consumption is frequently higher than 25.5kWh (255 being the highest value represented by a 8-bit unsigned integer). Consumption has never been below 10.0kWh or above 35.0kWh, so conceivably I could subtract 10 from the stored figures, but I know that one day we will exceed these limits.

I then tested code to pack 9-bit values into an array. This gives a range of 0-51.2kWh and uses 32 bytes in total. However, accessing an array like this is pretty slow, especially when you have to iterate over all values to calculate an average.

So my question is - is there a more efficient way of calculating a moving average with three windows - lifetime, 28 days and 7 days? Efficiency means smaller in terms of SRAM usage, but without the penalty of huge code. Can I avoid storing all values?

Cybergibbons
  • 5,420
  • 7
  • 34
  • 51

5 Answers5

3

If your data has low standard deviation, then one method would be to sum values over the window, and then keep subtracting the mean from the sum, while adding the new value.

This would work well if there are no outliers, thereby leading to the aggregate error tending to zero over time.

//Pseudocode

count=0
while new_reading and count<7:
    sum += new_reading        //Calculate the sum of first 7 values
    count++

while new_reading:            //Loop till new readings available
    avg = sum / 7             //Calculate average
    sum -= avg                //Subtract average from sum
    sum += new_reading        //Add next reading to sum
    print avg
asheeshr
  • 3,847
  • 3
  • 26
  • 61
2

you can use a different method, you keep the current average and then do

average = (weight1*average+weight2*new_value)/(weight1+weight2);

it's not a true rolling average and has different semantics, but it may fit your needs nonetheless

for a more efficient calculation method for your 9 bits per value solution you could keep the 8 highest bits of the values in an array and separate out the least significant bits:

uint8_t[28] highbits;
uint32_t lowbits;

to set a value you need to split it out

void getvalue(uint8_t index, uint16_t value){
    highbits[index] = value>>1;
    uint32_t flag = (value & 1)<<index;
    highbits|=flag;
    highbits&=~flag;
}

resulting in 2 shifts an AND and an OR and a not

to calculate the average you can use various bit tricks to speed it up:

uint16_t getAverage(){
    uint16_t sum=0;
    for(uint8_t i=0;i<28;i++){
        sum+=highbits[i];
    }
    sum<<=1;//multiply by 2 after the loop
    sum+=bitcount(lowbits);
    return sum/28;
}

you can use an efficient parallel bitcount for the bitcount()

ratchet freak
  • 3,267
  • 1
  • 13
  • 12
1

How about only storing the difference from the previous value? In electronics there is a similar concept called Delta Sigma converter, which is used for DA/AD converters. It relies on the fact that the previous measurement is reasonably near the current one.

jippie
  • 2,911
  • 14
  • 23
0

is there a more efficient way of calculating a moving average with ... 28 days and 7 days? ... needing to remember 27 days of history ... ?

You might get close enough storing 11 values rather than 28 values, perhaps something like:

// untested code
// static variables
uint16_t daily_energy[7]; // perhaps in units of 0.01 kWh ?
uint16_t weekly_energy[4]; // perhaps in units of 0.1 kWh ?

void print_week_status(){
    Serial.print( F("last week's total energy :") );
    Serial.println( weekly_energy[0] );
    int sum = 0;
    for( int i=0; i<4; i++ ){
        sum += weekly_energy[i];
    };
    Serial.print( F("Total energy over last 4 complete weeks :") );
    Serial.println( sum );
    int average_weekly_energy = sum/4;
    int average_daily_energy = average_weekly_energy/7;
    Serial.print( F("Average daily energy over last 4 weeks :") );
    Serial.println( average_daily_energy );
}
void print_day_status(){
    Serial.print( F("Yesterday's energy :") );
    Serial.println( daily_energy[0] );
    Serial.print( F("average daily energy over the last 7 complete days: ") );
    int sum = 0;
    for( int i=0; i<7; i++ ){
        sum += daily_energy[i];
    };
    int average = sum/7;
    Serial.println( average );
}

In other words, rather than storing every detail of every day for the last 27 days, (a) store 7 or so values of detailed daily information for the past 7 or so days, and also (b) store 4 or so "summarized" values of total or average information for each of the past 4 or so weeks.

David Cary
  • 1,112
  • 8
  • 23
0

Why couldn't you just add the values together as soon as you obtain them. So what I mean is you get the value for day 1, you divide it by 1 and store it and the 1 somewhere. Then you multiply the 1 by the value and add it to the next value and divide them both by 2.

Doing this method would create a rolling average with two or three variable as I can think of. I would write some code but I am new to stackexchange so please bear with me.