Algoritmo / estrutura de dados eficiente para calcular médias móveis

9

Atualmente, estou desenvolvendo um sistema LCD gráfico para exibir temperaturas, fluxos, tensões, potência e energia em um sistema de bomba de calor. O uso de um LCD gráfico significa que metade da minha SRAM e ~ 75% do meu flash foram usadas por um buffer de tela e seqüências de caracteres.

Atualmente, estou exibindo valores mínimo / máximo / médio para energia À meia-noite, quando o valor diário é redefinido, o sistema verifica se o consumo do dia está acima ou abaixo do mínimo ou máximo anterior e armazena o valor. A média é calculada dividindo o consumo acumulado de energia pelo número de dias.

Gostaria de exibir a média diária da última semana e mês (4 semanas por simplicidade), ou seja, uma média móvel. Atualmente, isso envolve a manutenção de uma matriz de valores nos últimos 28 dias e o cálculo da média de toda a matriz nos meses mensais e nos últimos 7 dias na semana.

Inicialmente, eu fazia isso usando uma matriz de flutuadores (como a energia está na forma "12,12kWh"), mas isso estava usando 28 * 4 bytes = 112 bytes (5,4% da SRAM). Não me importo de ter apenas um único ponto decimal de resolução, então mudei para usar uint16_t e multiplique o número por 100. Isso significa que 12.12 é representado como 1212 e divido por 100 para fins de exibição.

O tamanho da matriz agora está reduzido a 56 bytes (muito melhor!).

Não há uma maneira trivial de reduzir a figura para um uint8_t que eu possa ver. Eu pude tolerar a perda de uma casa decimal ("12,1 kWh" em vez de "12,12 kWh"), mas o consumo geralmente é superior a 25,5 kWh (255 é o valor mais alto representado por um número inteiro não assinado de 8 bits). O consumo nunca ficou abaixo de 10,0kWh ou acima de 35,0kWh, então é possível subtrair 10 dos números armazenados, mas sei que um dia excederemos esses limites.

Testei o código para compactar valores de 9 bits em uma matriz. Isso fornece um intervalo de 0 a 51,2 kWh e usa 32 bytes no total. No entanto, acessar uma matriz como essa é muito lento, especialmente quando você precisa iterar sobre todos os valores para calcular uma média.

Então, minha pergunta é: existe uma maneira mais eficiente de calcular uma média móvel com três janelas - vida útil, 28 dias e 7 dias? Eficiência significa menor em termos de uso de SRAM, mas sem a penalidade de um grande código. Posso evitar armazenar todos os valores?

Cybergibbons
fonte
Deseja calcular uma média móvel em janelas específicas ou uma estimativa / aproximação da média faria?
asheeshr
Quero uma média móvel em uma janela de 7 e 28 dias.
Cybergibbons
você poderia usar uma resolução de 0.2kWh (divisão e multiplicação com o factor 5) e ainda obter gama 0-51.2kWh em 8 bits
catraca aberração
Você pode acabar colocando strings e outras constantes na RAM externa ou no Flash externo - consulte "O que posso fazer se ficar sem memória Flash ou SRAM?" .
David Cary

Respostas:

2

Se seus dados tiverem um desvio padrão baixo, um método seria somar valores pela janela e continuar subtraindo a média da soma, enquanto adicionava o novo valor.

Isso funcionaria bem se não houvesse discrepâncias , levando ao erro agregado tendendo a zero ao longo do tempo.

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

você pode usar um método diferente, mantém a média atual e depois

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

não é uma verdadeira média móvel e possui semântica diferente, mas pode atender às suas necessidades

para um método de cálculo mais eficiente para sua solução de 9 bits por valor, você pode manter os 8 bits mais altos em uma matriz e separar os bits menos significativos:

uint8_t[28] highbits;
uint32_t lowbits;

para definir um valor, você precisa dividi-lo

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

resultando em 2 turnos um AND e um OR e um não

Para calcular a média, você pode usar vários truques de bits para acelerar:

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

você pode usar um contador de bits paralelo eficiente para obitcount()

catraca arrepiante
fonte
11
Você pode explicar mais como isso me permitiria calcular a média de 7 e 28 dias?
Cybergibbons
Eu usei essa abordagem para suavizar valores analógicos ruidosos antes e certamente era bastante eficaz. Eu não precisava de muita precisão, pois os valores resultantes estavam sendo submetidos a um quantizador muito grosseiro. Eu também não precisava de médias históricas.
Peter Bloomfield
Isso não permite calcular a média para uma janela específica.
asheeshr
@Cybergibbons você pode usar pesos diferentes para aproximar a janela para valores antigos se tornam insignificantes cedo ou mais tarde, ou mantê 7 dias para a janela dia o 7 e esta média móvel de 28 dias média
aberração catraca
1

Que tal armazenar apenas a diferença do valor anterior? Em eletrônica, existe um conceito semelhante chamado conversor Delta Sigma, usado para conversores DA / AD. Ele se baseia no fato de que a medição anterior está razoavelmente próxima da atual.

jippie
fonte
Outra ideia interessante. Infelizmente, não tenho certeza se o consumo de energia será sempre assim, pois é um sistema de bomba de calor e um dia pode levar 30kWh, os próximos 10kWh. Eu realmente preciso coletar dados e ver.
Cybergibbons
0

Por que você não pode simplesmente adicionar os valores assim que obtê-los? Então, o que quero dizer é que você obtém o valor do dia 1, divide-o por 1 e armazena-o e o 1 em algum lugar. Em seguida, multiplique o 1 pelo valor e adicione-o ao próximo valor e divida os dois por 2.

Fazer esse método criaria uma média móvel com duas ou três variáveis, como posso pensar. Eu escreveria algum código, mas sou novo no stackexchange, portanto, tenha paciência comigo.

Aditya Somani
fonte
Não entendo como isso lida com a janela de 7 e 28 dias?
Cybergibbons
Manter o controle dos valores anteriores e seguintes e continuar adicionando e subtraindo-los de seu correndo média ...
Aditya Somani
11
Então, estou de volta ao estado de precisar lembrar de 27 dias de história, com certeza?
Cybergibbons
Eu estive pensando e você está certo. Então, tecnicamente, minha resposta está incorreta. Estou investindo mais tempo e paciência nisso. Talvez algo fora da caixa. Eu vou deixar você saber se eu chegar a alguma coisa. Fazemos algo assim muito no meu local de trabalho. Deixe-me perguntar por aí. Desculpe a confusão.
Aditya Somani
0

existe uma maneira mais eficiente de calcular uma média móvel com ... 28 dias e 7 dias? ... precisando se lembrar de 27 dias de história ...?

Você pode se aproximar o suficiente armazenando 11 valores em vez de 28, talvez algo como:

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

Em outras palavras, em vez de armazenar todos os detalhes de todos os dias nos últimos 27 dias, (a) armazene aproximadamente 7 valores de informações diárias detalhadas dos últimos 7 dias, e também (b) armazene 4 ou mais "resumidos" valores da informação total ou média de cada uma das últimas quatro semanas.

David Cary
fonte