อัลกอริทึมที่มีประสิทธิภาพ / โครงสร้างข้อมูลในการคำนวณค่าเฉลี่ยเคลื่อนที่


9

ขณะนี้ฉันกำลังพัฒนาระบบ LCD กราฟิกเพื่อแสดงอุณหภูมิการไหลแรงดันไฟฟ้าพลังงานและพลังงานในระบบปั๊มความร้อน การใช้กราฟิก LCD หมายความว่าครึ่งหนึ่งของ SRAM ของฉันและประมาณ 75% ของแฟลชของฉันถูกใช้จนหมดโดยบัฟเฟอร์หน้าจอและสตริง

ขณะนี้ฉันกำลังแสดงตัวเลขพลังงานต่ำสุด / เฉลี่ย / เฉลี่ยเวลาเที่ยงคืนเมื่อรูปประจำวันรีเซ็ตระบบจะตรวจสอบว่าปริมาณการใช้พลังงานสำหรับวันนั้นสูงกว่าหรือต่ำกว่าค่าต่ำสุดหรือค่าต่ำสุดก่อนหน้านี้และเก็บค่าไว้ ค่าเฉลี่ยจะคำนวณโดยหารปริมาณการใช้พลังงานสะสมด้วยจำนวนวัน

ฉันต้องการแสดงค่าเฉลี่ยรายวันในสัปดาห์ที่แล้วและเดือนที่แล้ว (4 สัปดาห์สำหรับความเรียบง่าย) เช่นค่าเฉลี่ยเคลื่อนที่ ขณะนี้สิ่งนี้เกี่ยวข้องกับการบำรุงรักษาอาร์เรย์ของค่าในช่วง 28 วันที่ผ่านมาและการคำนวณค่าเฉลี่ยของทั้งอาร์เรย์สำหรับรายเดือนและ 7 วันล่าสุดสำหรับรายสัปดาห์

ตอนแรกฉันทำสิ่งนี้โดยใช้อาร์เรย์ลอย (เนื่องจากพลังงานอยู่ในรูปแบบ "12.12kWh") แต่ใช้ 28 * 4 ไบต์ = 112 ไบต์ (5.4% ของ SRAM) ฉันไม่รังเกียจที่จะมีเพียงจุดทศนิยมเดียวของความละเอียดดังนั้นฉันจึงเปลี่ยนเป็นการใช้ uint16_t และคูณตัวเลขด้วย 100 ซึ่งหมายความว่า 12.12 แทนด้วย 1212 และฉันหารด้วย 100 เพื่อการแสดงผล

ขนาดของอาร์เรย์ลดลงเหลือ 56 ไบต์ (ดีกว่ามาก!)

ไม่มีวิธีการเล็กน้อยเพื่อลดรูปลงเป็น uint8_t ที่ฉันเห็น ฉันสามารถทนต่อการสูญเสียตำแหน่งทศนิยม ("12.1kWh" แทนที่จะเป็น "12.12kWh") แต่การบริโภคมักจะสูงกว่า 25.5kWh (255 เป็นค่าสูงสุดที่แสดงด้วยเลขจำนวนเต็ม 8 บิตที่ไม่ได้ลงนาม) การบริโภคไม่เคยต่ำกว่า 10.0kWh หรือสูงกว่า 35.0kWh ดังนั้นฉันอาจลบ 10 จากตัวเลขที่เก็บไว้ แต่ฉันรู้ว่าวันหนึ่งเราจะเกินขีด จำกัด เหล่านี้

ฉันทดสอบโค้ดเพื่อแพ็คค่า 9 บิตลงในอาร์เรย์ ซึ่งจะให้ช่วง 0-51.2kWh และใช้ทั้งหมด 32 ไบต์ อย่างไรก็ตามการเข้าถึงอาเรย์แบบนี้ค่อนข้างช้าโดยเฉพาะเมื่อคุณต้องวนซ้ำค่าทั้งหมดเพื่อคำนวณค่าเฉลี่ย

ดังนั้นคำถามของฉันคือ - มีวิธีที่มีประสิทธิภาพมากขึ้นในการคำนวณค่าเฉลี่ยเคลื่อนที่ด้วยสามหน้าต่าง - อายุการใช้งาน 28 วันและ 7 วัน? ประสิทธิภาพหมายถึงมีขนาดเล็กลงในแง่ของการใช้ SRAM แต่ไม่มีโทษของรหัสขนาดใหญ่ ฉันจะหลีกเลี่ยงการเก็บค่าทั้งหมดได้หรือไม่?


คุณต้องการคำนวณค่าเฉลี่ยเคลื่อนที่ผ่านหน้าต่างเฉพาะหรือประมาณ / ประมาณค่าเฉลี่ยจะทำอย่างไร
asheeshr

ฉันต้องการค่าเฉลี่ยเคลื่อนที่ในช่วง 7 วันและ 28 วัน
Cybergibbons

คุณสามารถใช้ความละเอียด 0.2kWh (หารด้วยทวีคูณด้วยตัวคูณ 5) และคุณยังคงได้ช่วง 0-51.2kWh ใน 8 บิต
เฟืองวงล้อประหลาด

คุณอาจวางสตริงและค่าคงที่อื่น ๆ ใน RAM ภายนอกหรือแฟลชภายนอก - ดู"ฉันจะทำอย่างไรถ้าฉันไม่มีหน่วยความจำแฟลชหรือ SRAM หมด" .
David Cary

คำตอบ:


2

หากข้อมูลของคุณมีค่าเบี่ยงเบนมาตรฐานต่ำวิธีการหนึ่งก็คือการหาค่าผลรวมเหนือหน้าต่างแล้วทำการลบค่าเฉลี่ยจากผลรวมในขณะที่เพิ่มค่าใหม่

สิ่งนี้จะทำงานได้ดีหากไม่มีค่าผิดปกติดังนั้นจะนำไปสู่ข้อผิดพลาดโดยรวมซึ่งมีแนวโน้มเป็นศูนย์เมื่อเวลาผ่านไป

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

ส่งผลให้ 2 กะและและและหรือไม่

ในการคำนวณค่าเฉลี่ยคุณสามารถใช้ลูกเล่นเล็กน้อยเพื่อเพิ่มความเร็ว:

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 ขนานที่มีประสิทธิภาพสำหรับbitcount()


1
คุณช่วยอธิบายเพิ่มเติมได้ไหมว่าจะให้ฉันคำนวณค่าเฉลี่ย 7 และ 28 วันได้อย่างไร
Cybergibbons

ฉันใช้วิธีนี้ในการปรับค่าอะนาล็อกให้มีเสียงรบกวนมาก่อนและมันก็ค่อนข้างมีประสิทธิภาพพอสมควร ฉันไม่ต้องการความแม่นยำมากนักเนื่องจากค่าที่ได้นั้นถูกใส่เข้าไปในเครื่องวัดปริมาณที่หยาบมาก ฉันไม่ต้องการค่าเฉลี่ยในอดีต
Peter Bloomfield

นี่ไม่อนุญาตให้คำนวณค่าเฉลี่ยสำหรับหน้าต่างเฉพาะ
asheeshr

@Cybergibbons คุณสามารถใช้น้ำหนักต่างกันเพื่อประมาณหน้าต่างเพื่อให้ค่าเก่าไม่สำคัญก่อนหน้านี้หรือภายหลังหรือเก็บไว้ 7 วันสำหรับหน้าต่าง 7 วันและค่าเฉลี่ยเคลื่อนที่นี้สำหรับค่าเฉลี่ย 28 วัน
ratchet freak

1

วิธีการเกี่ยวกับการเก็บความแตกต่างจากค่าก่อนหน้าเท่านั้น? ในอุปกรณ์อิเล็กทรอนิกส์มีแนวคิดที่คล้ายกันที่เรียกว่าตัวแปลง Delta Sigma ซึ่งใช้สำหรับตัวแปลง DA / AD ขึ้นอยู่กับความจริงที่ว่าการวัดก่อนหน้านี้อยู่ใกล้กับค่าที่วัดได้ในปัจจุบัน


ความคิดที่น่าสนใจอีกประการหนึ่ง น่าเสียดายที่ฉันไม่แน่ใจว่าการใช้พลังงานจะเป็นเช่นนี้เสมอเพราะเป็นระบบปั๊มความร้อนและวันหนึ่งอาจใช้เวลา 30kWh หรือ 10kWh ต่อไป ฉันต้องการรวบรวมข้อมูลและดูจริงๆ
Cybergibbons

0

ทำไมคุณไม่สามารถเพิ่มค่าเข้าด้วยกันทันทีที่คุณได้รับมา สิ่งที่ฉันหมายถึงคือคุณได้ค่าสำหรับวันที่ 1 คุณหารมันด้วย 1 แล้วเก็บมันกับ 1 ที่ จากนั้นคุณคูณ 1 ด้วยค่าและเพิ่มไปยังค่าถัดไปแล้วหารทั้งสองด้วย 2

การทำวิธีนี้จะสร้างค่าเฉลี่ยการหมุนด้วยตัวแปรสองหรือสามอย่างที่ฉันคิดได้ ฉันจะเขียนโค้ดบางส่วน แต่ฉันใหม่เพื่อ stackexchange ดังนั้นโปรดอดทนกับฉัน


ฉันไม่เข้าใจว่าข้อตกลงนี้มีหน้าต่าง 7 วันและ 28 วันได้อย่างไร
Cybergibbons

ติดตามค่าก่อนหน้าและค่าถัดไปและทำการเพิ่มและลบออกจากค่าเฉลี่ยที่คุณใช้อยู่ ...
Aditya Somani

1
ถ้าอย่างนั้นฉันก็กลับมาอยู่ในสถานะที่ต้องจำ 27 วันแห่งประวัติศาสตร์แน่ ๆ ?
Cybergibbons

ฉันกำลังคิดและคุณพูดถูก ดังนั้นเทคนิคทำให้คำตอบของฉันไม่ถูกต้อง ฉันลงทุนเวลาและความอดทนเพิ่มขึ้น อาจมีอะไรบางอย่างนอกกรอบ ฉันจะบอกให้คุณรู้ถ้าฉันคิดอะไรบางอย่าง เราทำอะไรแบบนี้มากมายในที่ทำงานของฉัน ขอถามหน่อย ขออภัยเกี่ยวกับความสับสน
Aditya Somani

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 สัปดาห์ที่ผ่านมา

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.