高效的算法/数据结构来计算移动平均值


9

目前,我正在开发图形LCD系统,以显示热泵系统中的温度,流量,电压,功率和能量。使用图形LCD意味着屏幕缓冲区和字符串耗尽了我一半的SRAM和约75%的闪存。

我当前正在显示能源的最小/最大/平均数字。在午夜,当重置每日数字时,系统将检查当天的消耗量是否高于或低于先前的最小值或最大值,并存储该值。通过将累计能耗除以天数来计算平均值。

我想显示最近一周和一个月的日平均值(为简单起见,为4周),即滚动平均值。当前,这涉及维护最近28天的值数组,并计算整个数组中每月的平均值和每周的最后7天的平均值。

最初,我是使用浮点数组来完成此操作的(因为能量的形式为“ 12.12kWh”),但这使用的是28 * 4字节= 112字节(占SRAM的5.4%)。我不在乎分辨率只有一个小数点,因此我改为使用uint16_t并将该数字乘以100。这意味着12.12表示为1212,为了显示目的,我将其除以100。

数组的大小现在减少到56个字节(好多了!)。

没有简单的方法可以将图形缩小到我能看到的uint8_t。我可以忍受小数点后的损失(“ 12.1kWh”而不是“ 12.12kWh”),但是功耗通常高于25.5kWh(255是由8位无符号整数表示的最大值)。耗电量从未低于10.0kWh或高于35.0kWh,因此可以想象,我可以从存储的数据中减去10,但是我知道有一天我们会超过这些限制。

然后,我测试了将9位值打包到数组中的代码。范围为0-51.2kWh,总共使用32个字节。但是,访问这样的数组非常慢,尤其是当您必须遍历所有值以计算平均值时。

所以我的问题是-是否有更有效的方法来计算三个窗口(寿命,28天和7天)的移动平均值?效率意味着就SRAM的使用而言更小,但不会带来巨额代码的代价。我可以避免存储所有值吗?


您是要计算特定窗口上的移动平均值,还是要对该平均值进行估算/近似?
asheeshr 2014年

我想要在7天和28天的窗口中移动均线。
Cyber​​gibbons 2014年

您可以使用0.2kWh的分辨率(除以5,然后乘以5),仍然可以在8位中获得0-51.2kWh的范围
棘轮怪胎

您可能最终会在外部RAM或外部闪存中放入字符串和其他常量-请参阅“如果闪存或SRAM用完了怎么办?”
大卫·卡里

Answers:


2

如果您的数据的标准偏差较低,则一种方法是对窗口上的值求和,然后继续从总和中减去平均值,同时添加新值。

如果没有异常值,这将很好地工作,从而导致总误差随时间趋于零。

//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

2

您可以使用其他方法,保持当前平均值,然后执行

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

它不是真正的滚动平均值,并且具有不同的语义,但是仍然可以满足您的需求

对于每个值9位解决方案的更有效的计算方法,可以将值的最高8位保留在数组中,并分离出最低有效位:

uint8_t[28] highbits;
uint32_t lowbits;

要设置一个值,您需要将其拆分

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

导致2移位AND和OR或not

要计算平均值,您可以使用各种技巧来加快平均值:

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;
}

您可以将有效的并行比特数用于bitcount()


1
您能否进一步说明这将如何让我计算7天和28天的平均值?
Cyber​​gibbons 2014年

我以前曾经使用过这种方法来平滑嘈杂的模拟值,并且肯定是相当有效的。但是我并不需要很高的精度,因为结果值是通过非常粗糙的量化器输入的。我也不需要历史平均值。
彼得·布卢姆菲尔德

这不允许计算特定窗口的平均值。
asheeshr 2014年

@Cyber​​gibbons,您可以使用不同的权重来近似窗口,以使旧值早晚变得无关紧要,或者将7天窗口保持7天,并将该移动平均值保持为28天平均值
棘轮异常2014年

1

仅存储与先前值的差异怎么样?在电子产品中,有一个类似的概念称为Delta Sigma转换器,用于DA / AD转换器。它依赖于这样一个事实,即先前的测量合理地接近当前的测量。


另一个有趣的想法。不幸的是,我不确定能耗是否会一直这样,因为它是一个热泵系统,一天可能要消耗30kWh,接下来的10kWh。我真的需要收集数据并查看。
Cyber​​gibbons 2014年

0

为什么不一获得值就立即将它们加在一起。因此,我的意思是,您得到第1天的值,将其除以1,然后将其和1存储在某个位置。然后,将1乘以该值,然后将其加到下一个值,然后将二者均除以2。

我认为,执行此方法将创建具有两个或三个变量的滚动平均值。我会写一些代码,但是我是Stackexchange的新手,所以请多多包涵。


我不知道这如何处理7天和28天的窗口?
Cyber​​gibbons 2014年

跟踪上一个和下一个值,并从运行平均值中减去和减去它们
Aditya Somani 2014年

1
那么,我又回到需要记住27天历史的状态了吗?
Cyber​​gibbons 2014年

我一直在想,你是对的。因此,从技术上讲,这使我的答案不正确。我要花更多的时间和耐心。也许开箱即用。如果我想出办法,我会通知您。我们在工作场所经常做这样的事情。我问一下。对不起,我很困惑。
Aditya Somani 2014年

0

有没有一种更有效的方式来计算... 28天和7天的移动平均值?...需要记住27天的历史...?

您可能会足够接近地存储11个值而不是28个值,例如:

// 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 );
}

换句话说,不是存储过去27天的每一天的每个细节,而是(a)存储过去7天左右的7个左右的每日详细信息的值,并且(b)存储4个“摘要”的大约每日的信息。过去4周左右的每一周的总体或平均信息值。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.