Эффективный алгоритм /структура данных для расчета скользящих средних

В настоящее время я разрабатываю графическую ЖК-систему для отображения температур, потоков, напряжений, мощности и энергии в системе тепловых насосов. Использование графического ЖК-дисплея означает, что половина моей SRAM и ~ 75% моей вспышки были использованы экранным буфером и строками.

В настоящее время я показываю min /max /average цифры для энергии. В полночь, когда ежедневная цифра сбрасывается, система проверяет, превышает ли потребление за день значение выше или ниже предыдущего минимума или максимума и сохраняет значение. Среднее значение рассчитывается путем деления кумулятивного потребления энергии на количество дней.

Я хотел бы показывать среднесуточный за последнюю неделю и месяц (4 недели для простоты), т. е. скользящее среднее. В настоящее время это включает в себя сохранение массива значений за последние 28 дней и вычисление среднего значения по всему массиву за месяц и последние 7 дней в неделю.

Изначально я делал это, используя массив поплавков (так как энергия в форме «12.12kWh»), но это использовало 28 * 4 байта = 112 байт (5,4% от SRAM). Я не против иметь только одну десятичную точку разрешения, поэтому я перешел на использование uint16_t и умножил цифру на 100. Это означает, что 12.12 представляется как 1212, а для целей отображения - на 100.

Размер массива теперь до 56 байтов (намного лучше!).

Нет никакого тривиального способа уменьшить показатель до uint8_t, который я вижу. Я мог терпеть потерю десятичного места («12,1 кВт-ч» вместо «12,12 кВт-ч»), но потребление часто превышает 25,5 кВт-ч (255 наивысшее значение представлено 8-разрядным целым без знака). Потребление никогда не было ниже 10,0 кВт /ч или выше 35,0 кВтч, поэтому я мог бы вычесть 10 из хранимых цифр, но я знаю, что в один прекрасный день мы превысим эти пределы.

Затем я протестировал код, чтобы упаковать 9-битные значения в массив. Это дает диапазон 0-51,2 кВт-ч и использует всего 32 байта. Однако доступ к подобному массиву довольно медленный, особенно когда вам нужно перебирать все значения для вычисления среднего значения.

Итак, мой вопрос: есть ли более эффективный способ расчета скользящей средней с тремя окнами - срок службы, 28 дней и 7 дней? Эффективность означает меньшую с точки зрения использования SRAM, но без штрафа за огромный код. Можно ли сохранить все значения?

9 голосов | спросил Cybergibbons 7 MarpmFri, 07 Mar 2014 12:32:43 +04002014-03-07T12:32:43+04:0012 2014, 12:32:43

5 ответов


2

Если ваши данные имеют низкое стандартное отклонение, то один метод должен был бы суммировать значения по окну, а затем продолжать вычитать среднее из суммы при добавлении нового значения.

Это будет хорошо работать, если нет outliers , что приводит к тому, что совокупная ошибка стремится к нулю со временем.

//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 7 MarpmFri, 07 Mar 2014 13:37:34 +04002014-03-07T13:37:34+04:0001 2014, 13:37:34
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, а не

, чтобы вычислить среднее значение, вы можете использовать различные бит-трюки, чтобы ускорить его:

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()

ответил ratchet freak 7 MarpmFri, 07 Mar 2014 13:15:21 +04002014-03-07T13:15:21+04:0001 2014, 13:15:21
1

Как только сохранить только разницу с предыдущим значением? В электронике есть аналогичная концепция, называемая преобразователем Delta Sigma, которая используется для преобразователей DA /AD. Он полагается на тот факт, что предыдущее измерение достаточно близко к текущему.

ответил jippie 7 MarpmFri, 07 Mar 2014 22:00:57 +04002014-03-07T22:00:57+04:0010 2014, 22:00:57
0

Почему бы вам просто не добавить значения вместе сразу после их получения. Итак, я имею в виду, что вы получаете значение за 1-й день, вы делите его на 1 и сохраните его и 1 где-нибудь. Затем вы умножаете значение 1 на значение и добавляете его к следующему значению и делите их на 2.

Выполнение этого метода создало бы скользящее среднее с двумя или тремя переменными, как я могу себе представить. Я бы написал код, но я новичок в stackexchange, поэтому, пожалуйста, несите меня.

ответил Aditya Somani 8 MaramSat, 08 Mar 2014 05:33:29 +04002014-03-08T05:33:29+04:0005 2014, 05:33:29
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 дней, (а) хранить 7 или около того значений подробной ежедневной информации за последние 7 или около того дней, а также (б) хранить 4 или около того «суммированных» значений общей или средней информации за каждую из последних 4 или около того недель.

ответил David Cary 23 72014vEurope/Moscow11bEurope/MoscowSun, 23 Nov 2014 08:57:42 +0300 2014, 08:57:42

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132