이동 평균을 계산하기위한 효율적인 알고리즘 / 데이터 구조


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.12kWh"대신 "12.1kWh")의 손실을 허용 할 수 있지만 소비는 종종 25.5kWh (255는 8 비트 부호없는 정수로 표시되는 가장 높은 값)보다 높습니다. 소비량이 10.0kWh 이하 또는 35.0kWh 이상이었던 적이 없으므로 저장된 수치에서 10을 뺄 수 있지만 언젠가는 이러한 한계를 초과한다는 것을 알고 있습니다.

그런 다음 9 비트 값을 배열로 묶는 코드를 테스트했습니다. 이것은 0-51.2kWh의 범위를 제공하고 총 32 바이트를 사용합니다. 그러나 이와 같이 배열에 액세스하면 특히 평균을 계산하기 위해 모든 값을 반복해야 할 때 속도가 느립니다.

내 질문은-수명, 28 일 및 7 일 세 개의 창으로 이동 평균을 계산하는보다 효율적인 방법이 있습니까? 효율성은 SRAM 사용 측면에서 더 작지만 큰 코드를 사용하지 않는 것을 의미합니다. 모든 값을 저장하지 않아도 되나요?


특정 창에 대한 이동 평균을 계산하거나 평균의 추정 / 근사를 계산 하시겠습니까?
asheeshr

7 일 28 일 동안 이동 평균을 원합니다.
Cybergibbons

당신은 0.2kWh의 해상도를 사용할 수 있고 (계수 5와 곱하기) 8 비트에서 0-51.2kWh 범위를 여전히 얻습니다
ratchet freak

외부 RAM 또는 외부 플래시에 문자열 및 기타 상수를 넣을 수 있습니다. "플래시 메모리 또는 SRAM이 부족하면 어떻게해야합니까?"를 참조하십시오. .
David Cary

답변:


2

데이터에 표준 편차가 낮은 경우 한 가지 방법은 창에서 값을 합한 다음 새 값을 추가하면서 합에서 평균을 계속 빼는 것입니다.

특이 치가 없으면 제대로 작동 하므로 시간이 지남에 따라 집계 오류가 0이되는 경향이 있습니다.

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

결과적으로 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()


1
이것이 7 일과 28 일 평균을 계산할 수있는 방법에 대해 더 설명해 주시겠습니까?
Cybergibbons

이전에는 노이즈가 많은 아날로그 값을 평활화하기 위해이 방법을 사용했으며 확실히 효과적이었습니다. 결과 값이 매우 거친 양자화기를 통과했기 때문에 많은 정밀도가 필요하지 않았습니다. 또한 역사적 평균도 필요하지 않았습니다.
피터 블룸필드

특정 창에 대한 평균을 계산할 수 없습니다.
asheeshr

@Cybergibbons 다른 가중치를 사용하여 창을 근사화 할 수 있으므로 이전 값은 이전 또는 이후에 중요하지 않게되거나 7 일 동안 7 일 동안 유지되며 28 일 평균 동안이 이동 평균을 유지합니다.
ratchet freak

1

이전 값과의 차이 만 저장하는 것은 어떻습니까? 전자 제품에는 델타 시그마 변환기 (Delta Sigma converter)라는 유사한 개념이 있는데, 이는 DA / AD 변환기에 사용됩니다. 이전 측정이 현재 측정에 상당히 가깝다는 사실에 의존합니다.


또 다른 흥미로운 아이디어. 불행히도 열 펌프 시스템이므로 하루 10kWh 인 30kWh를 소비 할 수 있기 때문에 에너지 소비가 항상 이와 같은지 확신 할 수 없습니다. 나는 정말로 데이터를 수집하고 볼 필요가있다.
Cybergibbons

0

값을 얻 자마자 함께 추가 할 수없는 이유는 무엇입니까? 제가 의미하는 바는 1 일째의 값을 1로 나누고 1과 1을 어딘가에 저장한다는 것입니다. 그런 다음 1에 값을 곱하고 다음 값에 더하고 2로 나눕니다.

이 방법을 사용하면 내가 생각할 수있는 두 개 또는 세 개의 변수로 롤링 평균을 만들 수 있습니다. 나는 약간의 코드를 작성할 것이지만 나는 stackexchange를 처음 사용하기 때문에 나와 함께 견뎌주십시오.


이것이 7 일 및 28 일 창을 처리하는 방법을 이해하지 못합니까?
Cybergibbons

이전 및 다음 값을 추적하고 실행 평균에서 계속 더하고 빼기 ...
Aditya Somani

1
그렇다면 나는 27 일의 역사를 기억할 필요가있는 상태로 돌아 왔습니다.
Cybergibbons

나는 생각하고 당신이 옳습니다. 기술적으로 내 대답이 잘못되었습니다. 나는 그것에 더 많은 시간과 인내심을 투자하고 있습니다. 상자에서 무언가가 나올 수도 있습니다. 내가 뭔가 생각해 내면 알려 드리겠습니다. 우리는 직장에서 이런 일을 많이합니다. 내가 물어볼 게 혼란에 대해 죄송합니다.
Aditya Somani

0

28 일과 7 일로 이동 평균을 계산하는 더 효율적인 방법이 있습니까? ... 27 일의 역사를 기억할 필요가 있는가 ...?

28 값 대신 11 값을 충분히 저장하면 다음과 같습니다.

// 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 주 정도 각각의 총 또는 평균 정보 값.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.