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


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.