Thuật toán / cấu trúc dữ liệu hiệu quả để tính trung bình di chuyển


9

Hiện tại tôi đang phát triển một hệ thống LCD đồ họa để hiển thị nhiệt độ, dòng chảy, điện áp, năng lượng và năng lượng trong hệ thống bơm nhiệt. Việc sử dụng màn hình LCD đồ họa có nghĩa là một nửa SRAM của tôi và ~ 75% đèn flash của tôi đã được sử dụng bởi bộ đệm màn hình và chuỗi.

Tôi hiện đang hiển thị số liệu tối thiểu / tối đa / trung bình cho năng lượng Vào nửa đêm khi con số hàng ngày đặt lại, hệ thống sẽ kiểm tra xem mức tiêu thụ trong ngày cao hơn hoặc thấp hơn mức tối thiểu hoặc tối đa trước đó và lưu trữ giá trị. Trung bình được tính bằng cách chia mức tiêu thụ năng lượng tích lũy cho số ngày.

Tôi muốn hiển thị trung bình hàng ngày trong tuần và tháng trước (4 tuần cho đơn giản) tức là trung bình cán. Hiện tại, điều này liên quan đến việc duy trì một mảng các giá trị trong 28 ngày qua và tính trung bình trên toàn bộ mảng cho hàng tháng và 7 ngày cuối tuần.

Ban đầu tôi đã thực hiện việc này bằng cách sử dụng một mảng số float (vì năng lượng ở dạng "12.12kWh"), nhưng điều này đã sử dụng 28 * 4 byte = 112 byte (5,4% SRAM). Tôi không ngại chỉ có một độ phân giải thập phân duy nhất, vì vậy tôi đã đổi sang sử dụng uint16_t và nhân số đó với 100. Điều này có nghĩa là 12.12 được biểu thị là 1212 và tôi chia cho 100 cho mục đích hiển thị.

Kích thước của mảng hiện giảm xuống còn 56 byte (tốt hơn nhiều!).

Không có cách tầm thường nào để giảm con số xuống uint8_t mà tôi có thể thấy. Tôi có thể chấp nhận mất vị trí thập phân ("12.1kWh" thay vì "12.12kWh"), nhưng mức tiêu thụ thường cao hơn 25,5kWh (255 là giá trị cao nhất được biểu thị bằng số nguyên không dấu 8 bit). Tiêu thụ chưa bao giờ dưới 10,0kWh hoặc trên 35,0kWh, vì vậy có thể hình dung tôi có thể trừ 10 từ các số liệu được lưu trữ, nhưng tôi biết rằng một ngày nào đó chúng ta sẽ vượt quá các giới hạn này.

Sau đó tôi đã kiểm tra mã để đóng gói các giá trị 9 bit vào một mảng. Điều này cho phạm vi 0-51.2kWh và sử dụng tổng cộng 32 byte. Tuy nhiên, việc truy cập vào một mảng như thế này khá chậm, đặc biệt là khi bạn phải lặp lại tất cả các giá trị để tính trung bình.

Vì vậy, câu hỏi của tôi là - có cách nào hiệu quả hơn để tính trung bình di chuyển với ba cửa sổ - trọn đời, 28 ngày và 7 ngày? Hiệu quả có nghĩa là nhỏ hơn về việc sử dụng SRAM, nhưng không bị phạt bởi mã lớn. Tôi có thể tránh lưu trữ tất cả các giá trị?


Bạn có muốn tính trung bình di chuyển trên các cửa sổ cụ thể hoặc ước tính / xấp xỉ trung bình sẽ làm gì không?
asheeshr

Tôi muốn trung bình di chuyển trên một cửa sổ 7 ngày và 28 ngày.
Cyberg Ribbon

bạn có thể sử dụng độ phân giải 0,2kWh (chia và nhân với hệ số 5) và bạn vẫn nhận được phạm vi 0-51.2kWh trong 8 bit
ratchet freak

Cuối cùng, bạn có thể đặt các chuỗi và các hằng số khác vào RAM ngoài hoặc Flash ngoài - xem "Tôi có thể làm gì nếu hết bộ nhớ Flash hoặc SRAM?" .
David Cary

Câu trả lời:


2

Nếu dữ liệu của bạn có độ lệch chuẩn thấp, thì một phương pháp sẽ là tính tổng các giá trị trên cửa sổ, sau đó tiếp tục trừ giá trị trung bình khỏi tổng, trong khi thêm giá trị mới.

Điều này sẽ hoạt động tốt nếu không có ngoại lệ , do đó dẫn đến lỗi tổng hợp có xu hướng về 0 theo thời gian.

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

bạn có thể sử dụng một phương pháp khác, bạn giữ mức trung bình hiện tại và sau đó làm

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

nó không phải là một trung bình thực sự và có ngữ nghĩa khác nhau, nhưng dù sao nó có thể phù hợp với nhu cầu của bạn

để có phương pháp tính toán hiệu quả hơn cho 9 bit cho mỗi giải pháp giá trị, bạn có thể giữ 8 bit cao nhất của các giá trị trong một mảng và tách ra các bit có ý nghĩa nhỏ nhất:

uint8_t[28] highbits;
uint32_t lowbits;

để thiết lập một giá trị bạn cần tách nó ra

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

dẫn đến 2 lần thay đổi AND và OR và không

để tính trung bình bạn có thể sử dụng các thủ thuật bit khác nhau để tăng tốc:

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

bạn có thể sử dụng một bitcount song song hiệu quả chobitcount()


1
Bạn có thể giải thích thêm làm thế nào điều này sẽ cho phép tôi tính trung bình 7 và 28 ngày không?
Cyberg Ribbon

Tôi đã sử dụng phương pháp này để làm mịn các giá trị tương tự nhiễu trước đây và nó chắc chắn khá hiệu quả. Mặc dù vậy, tôi không cần nhiều độ chính xác, vì các giá trị kết quả đã được đưa vào một bộ lượng tử rất thô. Tôi cũng không cần trung bình lịch sử.
Peter Bloomfield

Điều này không cho phép tính trung bình cho một cửa sổ cụ thể.
asheeshr

@Cyberg Ribbon bạn có thể sử dụng các trọng số khác nhau để xấp xỉ cửa sổ để các giá trị cũ trở nên không đáng kể sớm hơn hoặc muộn hơn hoặc giữ 7 ngày cho cửa sổ 7 ngày và mức trung bình di chuyển này trong trung bình 28 ngày
ratchet freak

1

Làm thế nào về việc chỉ lưu trữ sự khác biệt từ giá trị trước đó? Trong điện tử có một khái niệm tương tự gọi là bộ chuyển đổi Delta Sigma, được sử dụng cho bộ chuyển đổi DA / AD. Nó dựa vào thực tế là phép đo trước đó gần hợp lý với phép đo hiện tại.


Một ý tưởng thú vị khác. Thật không may, tôi không chắc chắn mức tiêu thụ năng lượng sẽ luôn như thế này, vì nó là một hệ thống bơm nhiệt và một ngày có thể mất 30kWh, 10kWh tiếp theo. Tôi thực sự cần phải thu thập dữ liệu và xem.
Cyberg Ribbon

0

Tại sao bạn không thể thêm các giá trị lại với nhau ngay khi bạn có được chúng. Vì vậy, ý tôi là bạn nhận được giá trị cho ngày 1, bạn chia nó cho 1 và lưu trữ nó và 1 ở đâu đó. Sau đó, bạn nhân 1 với giá trị và thêm nó vào giá trị tiếp theo và chia cả hai cho 2.

Làm phương pháp này sẽ tạo ra một trung bình cán với hai hoặc ba biến như tôi có thể nghĩ. Tôi sẽ viết một số mã nhưng tôi chưa quen với stackexchange vì vậy hãy đồng ý với tôi.


Tôi không hiểu làm thế nào điều này đối phó với cửa sổ 7 ngày và 28 ngày?
Cyberg Ribbon

Theo dõi các giá trị trước đó và tiếp theo và tiếp tục cộng và trừ chúng khỏi giá trị trung bình đang chạy của bạn ...
Aditya Somani

1
Vì vậy, sau đó tôi trở lại trong tình trạng cần phải nhớ 27 ngày của lịch sử, chắc chắn?
Cyberg Ribbon

Tôi đã suy nghĩ và bạn đã đúng. Vì vậy, về mặt kỹ thuật làm cho câu trả lời của tôi không chính xác. Tôi đang đầu tư thêm thời gian và kiên nhẫn vào đó. Có lẽ một cái gì đó ra khỏi hộp. Tôi sẽ cho bạn biết nếu tôi nghĩ ra thứ gì đó. Chúng tôi làm một cái gì đó như thế này rất nhiều tại nơi làm việc của tôi. Cho tôi hỏi xung quanh. Xin lỗi về sự nhầm lẫn.
Aditya Somani

0

Có cách nào hiệu quả hơn để tính trung bình di chuyển với ... 28 ngày và 7 ngày không? ... Cần nhớ 27 ngày lịch sử ...?

Bạn có thể nhận được gần đủ lưu trữ 11 giá trị thay vì 28 giá trị, có lẽ đại loại như:

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

Nói cách khác, thay vì lưu trữ mọi chi tiết mỗi ngày trong 27 ngày qua, (a) lưu trữ 7 hoặc hơn các giá trị thông tin chi tiết hàng ngày trong 7 ngày qua và cũng (b) lưu trữ 4 hoặc hơn "tóm tắt" các giá trị của tổng số hoặc thông tin trung bình cho mỗi 4 tuần qua.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.