การคำนวณค่าเฉลี่ยเคลื่อนที่อย่างรวดเร็วและหน่วยความจำมีประสิทธิภาพ


33

ฉันกำลังมองหาเวลาและโซลูชันที่มีประสิทธิภาพของหน่วยความจำในการคำนวณค่าเฉลี่ยเคลื่อนที่ใน C ฉันต้องหลีกเลี่ยงการหารเพราะฉันใช้ PIC 16 ซึ่งไม่มีหน่วยการแบ่งเฉพาะ

ในขณะนี้ฉันเพียงเก็บค่าทั้งหมดไว้ในบัฟเฟอร์บัฟเฟอร์และเก็บและอัปเดตผลรวมทุกครั้งที่มีค่าใหม่มาถึง มันมีประสิทธิภาพจริงๆ แต่น่าเสียดายที่ใช้หน่วยความจำส่วนใหญ่ที่มีอยู่ของฉัน ...


3
ฉันไม่คิดว่าจะมีวิธีที่มีประสิทธิภาพในการทำเช่นนี้อีก
Rocketmagnet

4
@JobyTaffey ดีมันเป็นอัลกอริทึมที่ใช้กันอย่างแพร่หลายในระบบควบคุมและต้องจัดการกับทรัพยากรฮาร์ดแวร์ที่ จำกัด ดังนั้นฉันคิดว่าเขาจะช่วยได้มากกว่านี้
clabacchio

3
@Joby: มีริ้วรอยบางอย่างเกี่ยวกับคำถามนี้ที่เกี่ยวข้องกับระบบ จำกัด ทรัพยากรขนาดเล็ก ดูคำตอบของฉัน คุณจะทำสิ่งนี้แตกต่างกันมากในระบบขนาดใหญ่เช่น SO folks คุ้นเคย นี่เป็นประสบการณ์ของฉันในการออกแบบอุปกรณ์อิเล็กทรอนิกส์
Olin Lathrop

1
ฉันเห็นด้วย. นี่ค่อนข้างเหมาะสมสำหรับฟอรัมนี้เนื่องจากเกี่ยวข้องกับระบบฝังตัว
Rocketmagnet

ฉันถอนคำคัดค้านของฉัน
Toby Jaffey

คำตอบ:


55

ตามที่คนอื่น ๆ ได้กล่าวไว้คุณควรพิจารณาตัวกรอง IIR (การตอบสนองต่อแรงกระตุ้นแบบไม่สิ้นสุด) มากกว่าตัวกรอง FIR (การตอบสนองต่อแรงกระตุ้นแบบ จำกัด แน่นอน) ที่คุณกำลังใช้อยู่ในขณะนี้ มีมากไปกว่านั้น แต่เมื่อใช้ตัวกรอง FIR แรกจะใช้งานอย่างชัดเจนและตัวกรอง IIR ที่มีสมการ

ตัวกรอง IIR ที่ฉันใช้เป็นจำนวนมากในไมโครคอนโทรลเลอร์เป็นตัวกรองสัญญาณความถี่ต่ำแบบเสาเดียว นี่คือดิจิตอลเทียบเท่าของตัวกรองสัญญาณอนาล็อก RC แบบง่าย สำหรับแอปพลิเคชันส่วนใหญ่สิ่งเหล่านี้จะมีลักษณะที่ดีกว่าตัวกรองกล่องที่คุณใช้ การใช้ตัวกรองกล่องส่วนใหญ่ที่ฉันพบเป็นผลมาจากคนที่ไม่ใส่ใจในชั้นเรียนการประมวลผลสัญญาณดิจิตอลไม่ได้เป็นผลมาจากความต้องการลักษณะเฉพาะของพวกเขา หากคุณต้องการลดทอนความถี่สูงที่คุณรู้ว่าเป็นเสียงตัวกรองสัญญาณความถี่ต่ำแบบเสาเดียวจะดีกว่า วิธีที่ดีที่สุดในการใช้หนึ่งดิจิตอลในไมโครคอนโทรลเลอร์มักจะ:

FILT <- FILT + FF (ใหม่ - ตัวกรอง)

FILT เป็นชิ้นส่วนของสถานะถาวร นี่เป็นตัวแปรถาวรเพียงตัวเดียวที่คุณต้องใช้ในการคำนวณตัวกรองนี้ ใหม่คือค่าใหม่ที่ตัวกรองจะถูกอัปเดตด้วยการวนซ้ำนี้ FF คือส่วนกรองซึ่งปรับ "ความหนัก" ของตัวกรอง ดูอัลกอริธึมนี้และดูว่าสำหรับ FF = 0 ตัวกรองนั้นหนักมากเนื่องจากเอาต์พุตไม่เคยเปลี่ยนแปลง สำหรับ FF = 1 จะไม่มีตัวกรองเลยจริงๆเพราะเอาต์พุตจะตามหลังอินพุต ค่าที่มีประโยชน์อยู่ในระหว่าง สำหรับระบบเล็ก ๆ คุณเลือก FF เป็น 1/2 Nดังนั้นการคูณด้วย FF สามารถทำได้โดยการเลื่อนขวาโดย N bits ตัวอย่างเช่น FF อาจเป็น 1/16 และคูณด้วย FF ดังนั้นการเปลี่ยนที่ถูกต้องคือ 4 บิต มิฉะนั้นตัวกรองนี้ต้องการเพียงหนึ่งการลบและการเพิ่มเพียงอย่างเดียวถึงแม้ว่าโดยทั่วไปแล้วตัวเลขจะต้องกว้างกว่าค่าอินพุต (เพิ่มเติมเกี่ยวกับความแม่นยำเชิงตัวเลขในส่วนที่แยกต่างหากด้านล่าง)

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

เพื่ออำนวยความสะดวกในการออกแบบตัวกรองเหล่านี้ (ซึ่งหมายถึงการเลือก FF และตัดสินใจว่าจะกรองจำนวนเท่าใด) ฉันจึงใช้โปรแกรม FILTBITS คุณระบุจำนวนของ shift shift สำหรับ FF แต่ละตัวในชุดกรองแบบเรียงซ้อนและคำนวณการตอบสนองขั้นตอนและค่าอื่น ๆ ที่จริงฉันมักจะเรียกใช้สิ่งนี้ผ่านสคริปต์ wrapper ของฉัน PLOTFILT สิ่งนี้จะทำงานกับ FILTBITS ซึ่งสร้างไฟล์ CSV จากนั้นแปลงไฟล์ CSV ตัวอย่างเช่นนี่คือผลลัพธ์ของ "PLOTFILT 4 4":

พารามิเตอร์ทั้งสองไปยัง PLOTFILT หมายความว่าจะมีตัวกรองสองตัวเรียงตามประเภทที่อธิบายไว้ข้างต้น ค่าของ 4 ระบุจำนวนของ shift shift เพื่อรับรู้การคูณด้วย FF ดังนั้นค่า FF ทั้งสองจึงเป็น 1/16 ในกรณีนี้

ร่องรอยสีแดงคือการตอบสนองขั้นตอนของหน่วยและเป็นสิ่งสำคัญที่ต้องดู ตัวอย่างเช่นสิ่งนี้บอกคุณว่าถ้าอินพุตเปลี่ยนแปลงทันทีผลลัพธ์ของตัวกรองที่รวมกันจะชำระให้ 90% ของค่าใหม่ในการวนซ้ำ 60 ครั้ง หากคุณสนใจเวลานั่งที่ 95% คุณต้องรอการคำนวณซ้ำ 73 ครั้งและสำหรับการตั้งเวลา 50% เพียง 26 รอบเท่านั้น

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

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

PLOTFILT อาจ FILTBITS และจำนวนมากของสิ่งที่มีประโยชน์อื่น ๆ โดยเฉพาะอย่างยิ่งสำหรับการพัฒนาเฟิร์มแว PIC ที่มีอยู่ในการเปิดตัวซอฟต์แวร์เครื่องมือพัฒนา PIC ที่ของฉันหน้าดาวน์โหลดซอฟแวร์

เพิ่มเกี่ยวกับความแม่นยำเชิงตัวเลข

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

FILT จึงมักจะเป็นจำนวนเต็มจุดคงที่ โปรดทราบว่านี่จะไม่เปลี่ยนคณิตศาสตร์ใด ๆ จากมุมมองของโปรเซสเซอร์ ตัวอย่างเช่นหากคุณกำลังกรองการอ่าน A / D 10 บิตและ N = 4 (FF = 1/16) ดังนั้นคุณต้องมีเศษส่วน 4 บิตใต้การอ่าน A / D จำนวนเต็ม 10 บิต หนึ่งโปรเซสเซอร์ส่วนใหญ่คุณจะทำการดำเนินการจำนวนเต็ม 16 บิตเนื่องจากการอ่าน A / D 10 บิต ในกรณีนี้คุณยังคงสามารถทำจำนวนเต็ม 16 บิต แต่เริ่มต้นด้วยการอ่าน A / D ที่เลื่อนไปทางซ้าย 4 บิต โปรเซสเซอร์ไม่ทราบความแตกต่างและไม่จำเป็นต้อง การดำเนินการทางคณิตศาสตร์กับจำนวนเต็ม 16 บิตทั้งที่ทำงานไม่ว่าคุณจะคิดว่ามันเป็น 12.4 จุดคงที่หรือเป็นจำนวนเต็ม 16 บิตจริง (16.0 จุดคงที่)

โดยทั่วไปคุณต้องเพิ่ม N บิตแต่ละขั้วกรองหากคุณไม่ต้องการเพิ่มเสียงรบกวนเนื่องจากการแสดงตัวเลข ในตัวอย่างข้างต้นตัวกรองตัวที่สองของทั้งสองจะต้องมี 10 + 4 + 4 = 18 บิตเพื่อไม่ให้ข้อมูลสูญหาย ในทางปฏิบัติกับเครื่อง 8 บิตซึ่งหมายความว่าคุณจะใช้ค่า 24 บิต ในทางเทคนิคเพียงขั้วที่สองของทั้งสองจะต้องการค่าที่กว้างขึ้น แต่สำหรับความเรียบง่ายของเฟิร์มแวร์ฉันมักจะใช้การแทนแบบเดียวกันและด้วยเหตุนี้จึงใช้รหัสเดียวกันสำหรับทุกขั้วของตัวกรอง

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

ตัวอย่างรหัส

นี่คือตัวอย่างของมาโครตามที่อธิบายไว้ข้างต้นสำหรับ PIC 18:

////////////////////////////////////////////////// //////////////////////////////
//
// ตัวกรองมาโคร
//
// อัปเดตหนึ่งขั้วกรองด้วยค่าใหม่ใน NEWVAL NEWVAL ได้รับการอัปเดตเป็น
// มีค่าตัวกรองใหม่
//
// FILT เป็นชื่อของตัวแปรสถานะตัวกรอง จะถือว่าเป็น 24 บิต
// กว้างและในธนาคารท้องถิ่น
//
// สูตรการอัพเดตตัวกรองคือ:
//
// FILT <- FILT + FF (NEWVAL - FILT)
//
// การคูณด้วย FF สามารถทำได้โดยการเปลี่ยนบิตของฟิลท์บิตต์
//
/ ตัวกรองมาโคร
  /เขียน
         dbankif lbankadr
         movf [arg 1] +0, w; NEWVAL <- NEWVAL - FILT
         subwf newval + 0
         movf [arg 1] +1, w
         subwfb newval + 1
         movf [arg 1] +2, w
         subwfb newval + 2

  /เขียน
  / loop n filtbits หนึ่งครั้งต่อการเลื่อน NEWVAL ไปทางขวาหนึ่งครั้ง
         rlcf newval + 2, w; เลื่อน NEWVAL ไปทางขวาหนึ่งบิต
         rrcf newval + 2
         rrcf newval + 1
         rrcf newval + 0
    / endloop

  /เขียน
         movf newval + 0, w; เพิ่มค่า shifted ลงในตัวกรองและบันทึกใน NEWVAL
         addwf [arg 1] +0, w
         movwf [arg 1] +0
         movwf newval + 0

         movf newval + 1, w
         addwfc [arg 1] +1, w
         movwf [arg 1] +1
         movwf newval + 1

         movf newval + 2, w
         addwfc [arg 1] +2, w
         movwf [arg 1] +2
         movwf newval + 2
  / endmac

และนี่คือมาโครที่คล้ายกันสำหรับ PIC 24 หรือ dsPIC 30 หรือ 33:

////////////////////////////////////////////////// //////////////////////////////
//
// ตัวกรองมาโคร ffbits
//
// อัปเดตสถานะของตัวกรองความถี่ต่ำหนึ่งตัว ค่าอินพุตใหม่อยู่ใน W1: W0
// และสถานะตัวกรองที่จะอัปเดตนั้นชี้ไปที่ W2
//
// ค่าตัวกรองที่อัปเดตจะถูกส่งคืนใน W1: W0 และ W2 ด้วย
// ไปยังหน่วยความจำแรกที่ผ่านสถานะตัวกรอง มาโครนี้จึงสามารถ
// เรียกใช้อย่างต่อเนื่องเพื่ออัปเดตชุดตัวกรอง low pass แบบต่อเนื่อง
//
// สูตรตัวกรองคือ:
//
// FILT <- FILT + FF (ใหม่ - ตัวกรอง)
//
// โดยที่การคูณด้วย FF จะถูกดำเนินการโดยการคำนวณทางขวาของ
// FFBITS
//
// คำเตือน: W3 ถูกทิ้งในถังขยะ
//
/ ตัวกรองมาโคร
  / var ใหม่ ffbits จำนวนเต็ม = [หา 1]; รับจำนวนบิตที่จะเปลี่ยน

  /เขียน
  / write "; ดำเนินการกรองผ่านความถี่ต่ำหนึ่งขั้ว, shift bits =" ffbits
  / เขียน ";"

         ย่อย w0, [w2 ++], w0; ใหม่ - ตัวกรอง -> W1: W0
         subb w1, [w2--], w1

         lsr w0, # [v ffbits], w0; เลื่อนผลลัพธ์เป็น W1: W0 ขวา
         sl w1, # [- 16 ffbits], w3
         หรือ w0, w3, w0
         เช่น w1, # [v ffbits], w1

         เพิ่ม w0, [w2 ++], w0; เพิ่ม FILT เพื่อสร้างผลลัพธ์สุดท้ายใน W1: W0
         addc w1, [w2--], w1

         mov w0, [w2 ++]; เขียนผลลัพธ์ไปยังสถานะตัวกรอง, ตัวชี้ล่วงหน้า
         mov w1, [w2 ++]

  /เขียน
  / endmac

ตัวอย่างทั้งสองนี้มีการใช้งานเป็นมาโครโดยใช้ตัวประมวลผลล่วงหน้าแอสเซมเบลอร์ PICของฉันซึ่งมีความสามารถมากกว่าสิ่งอำนวยความสะดวกในตัวแมโครอย่างใดอย่างหนึ่ง


1
+1 - ถูกต้องกับเงิน สิ่งเดียวที่ฉันเพิ่มคือตัวกรองค่าเฉลี่ยเคลื่อนที่จะมีตำแหน่งของตัวเองเมื่อทำงานพร้อมกันกับงานบางอย่าง (เช่นสร้างรูปคลื่นไดรฟ์เพื่อขับเครื่องกำเนิดอัลตร้าซาวด์) เพื่อให้พวกเขากรองฮาร์โมนิกส์ 1 / T เวลาเฉลี่ย
Jason S

2
คำตอบที่ดี แต่แค่สองสิ่ง ข้อแรก: ไม่จำเป็นต้องขาดความสนใจที่นำไปสู่การเลือกตัวกรองผิด ในกรณีของฉันฉันไม่เคยได้รับการสอนเกี่ยวกับความแตกต่างและเช่นเดียวกับคนที่ไม่จบการศึกษา ดังนั้นบางครั้งมันก็เป็นแค่ความไม่รู้ แต่ข้อที่สอง: ทำไมคุณเลือกเรียงลำดับตัวกรองดิจิทัลลำดับที่สองก่อนแทนที่จะใช้ลำดับที่สูงกว่า (เพียงเข้าใจฉันไม่วิจารณ์)
clabacchio

3
ตัวกรอง IIR แบบสองขั้วเดี่ยวแบบเรียงซ้อนสองตัวมีความทนทานต่อปัญหาเชิงตัวเลขและออกแบบได้ง่ายกว่าตัวกรอง IIR ลำดับที่สองเดียว ข้อดีคือมี 2 ขั้นตอนที่ลดหลั่นคุณจะได้ตัวกรอง Q (= 1/2?) ที่ต่ำ แต่ในกรณีส่วนใหญ่นั่นไม่ใช่เรื่องใหญ่
Jason S

1
@clabacchio: อีกเรื่องที่ฉันควรจะพูดถึงก็คือการติดตั้งเฟิร์มแวร์ คุณสามารถเขียนรูทีนย่อย single low pass filter หนึ่งครั้งจากนั้นใช้หลายครั้ง ในความเป็นจริงฉันมักจะเขียนรูทีนย่อยดังกล่าวเพื่อใช้ตัวชี้ในหน่วยความจำไปยังสถานะตัวกรองจากนั้นให้เลื่อนตัวชี้ไปข้างหน้าเพื่อให้สามารถเรียกได้อย่างต่อเนื่องเพื่อให้สามารถรู้ตัวกรองหลายขั้วได้อย่างง่ายดาย
Olin Lathrop

1
1. ขอบคุณมากสำหรับคำตอบของคุณ - ทั้งหมด ฉันตัดสินใจที่จะใช้ตัวกรอง IIR นี้ แต่ตัวกรองนี้ไม่ได้ใช้เป็นตัวกรอง LowPass มาตรฐานเนื่องจากฉันต้องการค่าเฉลี่ยตัวนับและเปรียบเทียบเพื่อตรวจจับการเปลี่ยนแปลงในช่วงที่แน่นอน เนื่องจากค่าเหล่านี้มีขนาดที่แตกต่างกันมากขึ้นอยู่กับฮาร์ดแวร์ฉันต้องการใช้ค่าเฉลี่ยเพื่อให้สามารถตอบสนองต่อการเปลี่ยนแปลงเฉพาะฮาร์ดแวร์เหล่านี้โดยอัตโนมัติ
sensslen

18

หากคุณสามารถใช้ชีวิตอยู่กับการ จำกัด พลังของจำนวนไอเท็มสองจำนวนถึงค่าเฉลี่ย (เช่น 2,4,8,16,32 เป็นต้น) การหารสามารถทำได้ง่ายและมีประสิทธิภาพในไมโครประสิทธิภาพต่ำโดยไม่มีการแบ่งเฉพาะเพราะมัน สามารถทำได้เป็นกะบิต การเลื่อนขวาแต่ละครั้งคือหนึ่งพลังของสองตัวอย่างเช่น:

avg = sum >> 2; //divide by 2^2 (4)

หรือ

avg = sum >> 3; //divide by 2^3 (8)

เป็นต้น


มันช่วยได้อย่างไร OP กล่าวว่าปัญหาหลักคือการเก็บรักษาตัวอย่างที่ผ่านมาในหน่วยความจำ
Jason S

นี่ไม่ได้ตอบคำถามของ OP เลย
Rocketmagnet

12
OP คิดว่าเขามีปัญหาสองข้อโดยแบ่งเป็น PIC16 และหน่วยความจำสำหรับบัฟเฟอร์วงแหวนของเขา คำตอบนี้แสดงให้เห็นว่าการหารนั้นไม่ยาก เป็นที่ยอมรับว่าไม่ได้แก้ไขปัญหาหน่วยความจำ แต่ระบบ SE อนุญาตคำตอบบางส่วนและผู้ใช้สามารถรับบางสิ่งจากแต่ละคำตอบด้วยตนเองหรือแม้แต่แก้ไขและรวมคำตอบของผู้อื่น เนื่องจากคำตอบอื่น ๆ บางคำต้องการการดำเนินการหารพวกเขาจึงไม่สมบูรณ์เช่นเดียวกันเนื่องจากพวกเขาไม่ได้แสดงให้เห็นถึงวิธีการบรรลุผลอย่างมีประสิทธิภาพใน PIC16
Martin

8

มีเป็นคำตอบสำหรับตัวกรองที่แท้จริงเฉลี่ยเคลื่อนที่ (aka "กรองสัมภาระ") ที่มีความต้องการหน่วยความจำน้อยที่ถ้าคุณไม่คิดลดขนาด มันเรียกว่าตัวกรองรวมตัวเรียงซ้อน (CIC) แนวคิดก็คือคุณมีผู้รวมระบบซึ่งคุณใช้เวลาแตกต่างกันในช่วงเวลาหนึ่งและอุปกรณ์ประหยัดหน่วยความจำที่สำคัญคือเมื่อลดขนาดลงคุณจะไม่ต้องเก็บทุกค่าของผู้รวมระบบ มันสามารถนำมาใช้โดยใช้รหัสเทียมต่อไปนี้:

function out = filterInput(in)
{
   const int decimationFactor = /* 2 or 4 or 8 or whatever */;
   const int statesize = /* whatever */
   static int integrator = 0;
   static int downsample_count = 0;
   static int ringbuffer[statesize];
   // don't forget to initialize the ringbuffer somehow
   static int ringbuffer_ptr = 0;
   static int outstate = 0;

   integrator += in;
   if (++downsample_count >= decimationFactor)
   {
     int oldintegrator = ringbuffer[ringbuffer_ptr];
     ringbuffer[ringbuffer_ptr] = integrator;
     ringbuffer_ptr = (ringbuffer_ptr + 1) % statesize;
     outstate = (integrator - oldintegrator) / (statesize * decimationFactor);
   }
   return outstate;
}

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


Postscript: ฉันเห็นด้วยกับแลงว่าคุณควรพิจารณาตัวกรอง IIR แบบง่าย ๆ เสมอก่อนตัวกรองค่าเฉลี่ยเคลื่อนที่ หากคุณไม่ต้องการความถี่ -Nulls ของตัวกรองแบบ Boxcar ตัวกรองความถี่ต่ำแบบ 1 ขั้วหรือ 2 ขั้วอาจทำงานได้ดี

ในทางกลับกันถ้าคุณกำลังกรองเพื่อจุดประสงค์ในการทำลายล้าง (รับอินพุตอัตราสุ่มตัวอย่างสูงและเฉลี่ยเพื่อใช้งานโดยกระบวนการอัตราต่ำ) ตัวกรอง CIC อาจเป็นสิ่งที่คุณกำลังมองหา (โดยเฉพาะอย่างยิ่งถ้าคุณสามารถใช้ stateize = 1 และหลีกเลี่ยง ringbuffer พร้อมกับค่า integrator ก่อนหน้าเดียว)


8

มีการวิเคราะห์เชิงลึกของคณิตศาสตร์เบื้องหลังโดยใช้ตัวกรอง IIR ลำดับแรกที่ Olin Lathrop ได้อธิบายไว้แล้วเกี่ยวกับการประมวลผลสัญญาณดิจิตอลแลกเปลี่ยน (รวมถึงภาพสวย ๆ จำนวนมาก) สมการสำหรับตัวกรอง IIR นี้คือ:

Y [N] = αx [N] + (1-α) Y [n-1]

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

/**
*  @details    Implement a first order IIR filter to approximate a K sample 
*              moving average.  This function implements the equation:
*
*                  y[n] = alpha * x[n] + (1 - alpha) * y[n-1]
*
*  @param      *filter - a Signed 15.16 fixed-point value.
*  @param      sample - the 16-bit value of the current sample.
*/

#define BITS 2      ///< This is roughly = log2( 1 / alpha )

short IIR_Filter(long *filter, short sample)
{
    long local_sample = sample << 16;

    *filter += (local_sample - *filter) >> BITS;

    return (short)((*filter+0x8000) >> 16);     ///< Round by adding .5 and truncating.
}

ตัวกรองนี้มีค่าใกล้เคียงกับค่าเฉลี่ยเคลื่อนที่ของตัวอย่าง K ที่ผ่านมาโดยการตั้งค่าของ alpha เป็น 1 / K ทำสิ่งนี้ในรหัสก่อนหน้าโดย#defineไอเอ็นจีBITSที่จะ log2 (K) คือสำหรับ K = 16 ชุดBITS4 สำหรับ K = 4 ชุดBITS2 ฯลฯ

(ฉันจะตรวจสอบรหัสที่แสดงที่นี่ทันทีที่ฉันได้รับการเปลี่ยนแปลงและแก้ไขคำตอบนี้หากจำเป็น)


6

นี่คือตัวกรอง low-pass ขั้วเดี่ยว (ค่าเฉลี่ยเคลื่อนที่, ด้วยความถี่ cutoff = CutoffFrequency) ง่ายมากเร็วมากใช้งานได้ดีและแทบไม่มีหน่วยความจำเหนือศีรษะ

หมายเหตุ: ตัวแปรทั้งหมดมีขอบเขตนอกเหนือจากฟังก์ชั่นการกรองยกเว้นการส่งผ่านใน newInput

// One-time calculations (can be pre-calculated at compile-time and loaded with constants)
DecayFactor = exp(-2.0 * PI * CutoffFrequency / SampleRate);
AmplitudeFactor = (1.0 - DecayFactor);

// Filter Loop Function ----- THIS IS IT -----
double Filter(double newInput)
{
   MovingAverage *= DecayFactor;
   MovingAverage += AmplitudeFactor * newInput;

   return (MovingAverage);
}

หมายเหตุ: นี่เป็นตัวกรองลำดับขั้นเดียว สามารถซ้อนหลายขั้นตอนเข้าด้วยกันเพื่อเพิ่มความคมชัดของตัวกรอง หากคุณใช้มากกว่าหนึ่งสเตจคุณจะต้องปรับ DecayFactor (ที่เกี่ยวข้องกับคัตออฟความถี่) เพื่อชดเชย

และเห็นได้ชัดว่าสิ่งที่คุณต้องการคือทั้งสองบรรทัดวางไว้ที่ใดก็ได้พวกเขาไม่ต้องการฟังก์ชั่นของตัวเอง ตัวกรองนี้มีเวลาทางลาดก่อนค่าเฉลี่ยเคลื่อนที่แสดงถึงสัญญาณอินพุต หากคุณต้องการข้ามเวลา ramp-up คุณสามารถเริ่มต้นการย้ายค่าเฉลี่ยเป็นค่าแรกของ newInput แทน 0 และหวังว่า newInput แรกไม่ใช่ค่าที่ผิด

(CutoffFrequency / SampleRate) มีช่วงตั้งแต่ 0 ถึง 0.5 DecayFactor เป็นค่าระหว่าง 0 ถึง 1 โดยปกติจะใกล้กับ 1

ทุ่นความแม่นยำเดี่ยวนั้นดีพอสำหรับสิ่งต่าง ๆ ส่วนใหญ่ฉันแค่ชอบเพิ่มเป็นสองเท่า หากคุณต้องการติดกับจำนวนเต็มคุณสามารถแปลง DecayFactor และ Amplitude Factor เป็นจำนวนเต็มเศษส่วนซึ่งเป็นตัวเศษที่เก็บเป็นจำนวนเต็มและตัวส่วนเป็นพลังงานจำนวนเต็ม 2 (เพื่อให้คุณสามารถเลื่อนบิตไปทางขวาเป็น ตัวหารแทนที่จะต้องแบ่งระหว่างลูปตัวกรอง) ตัวอย่างเช่นถ้า DecayFactor = 0.99 และคุณต้องการใช้จำนวนเต็มคุณสามารถตั้งค่า DecayFactor = 0.99 * 65536 = 64881 และเมื่อใดก็ตามที่คุณคูณด้วย DecayFactor ในลูปตัวกรองของคุณเพียงแค่เลื่อนผลลัพธ์ >> 16

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้หนังสือยอดเยี่ยมที่ออนไลน์ตอนที่ 19 เกี่ยวกับตัวกรองซ้ำ: http://www.dspguide.com/ch19.htm

ป.ล. สำหรับกระบวนทัศน์ค่าเฉลี่ยเคลื่อนที่วิธีการต่าง ๆ ในการตั้งค่า DecayFactor และ AmplitudeFactor ที่อาจเกี่ยวข้องกับความต้องการของคุณมากกว่าสมมติว่าคุณต้องการค่าเฉลี่ยก่อนหน้านี้ประมาณ 6 รายการด้วยกันโดยแยกเป็น 6 รายการและแยกโดย 6 ดังนั้นคุณสามารถตั้งค่า AmplitudeFactor เป็น 1/6 และ DecayFactor เป็น (1.0 - AmplitudeFactor)


4

คุณสามารถประมาณ Avarage ที่กำลังเคลื่อนที่สำหรับบางแอปพลิเคชันด้วยตัวกรอง IIR อย่างง่าย

น้ำหนักคือค่า 0.255 ค่าสูง = เวลาที่สั้นลงสำหรับการสร้างความคุ้นเคย

ค่า = (newvalue * weight + value * (256-weight)) / 256

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


3

คนอื่น ๆ ได้แสดงความคิดเห็นอย่างละเอียดเกี่ยวกับยูทิลิตี้ของ IIR กับ FIR และในส่วนของ power-of-two ฉันแค่ต้องการให้รายละเอียดการใช้งานบางอย่าง ด้านล่างใช้งานได้ดีกับไมโครคอนโทรลเลอร์ขนาดเล็กที่ไม่มี FPU ไม่มีการทวีคูณและถ้าคุณรักษา N ให้เป็นสองเท่าการหารทั้งหมดจะเป็นการเปลี่ยนบิตรอบเดียว

Basic FIR ring buffer: เก็บบัฟเฟอร์ที่กำลังรันอยู่ของค่า N สุดท้ายและ SUM ที่กำลังทำงานของค่าทั้งหมดในบัฟเฟอร์ ทุกครั้งที่มีตัวอย่างใหม่เข้ามาลบค่าที่เก่าที่สุดในบัฟเฟอร์จาก SUM แทนที่ด้วยตัวอย่างใหม่เพิ่มตัวอย่างใหม่ไปยัง SUM และเอาท์พุท SUM / N

unsigned int Filter(unsigned int sample){
    static unsigned int buffer[N];
    static unsigned char oldest = 0;
    static unsigned long sum;

    sum -= buffer[oldest];
    sum += sample;
    buffer[oldest] = sample;
    oldest += 1;
    if (oldest >= N) oldest = 0;

    return sum/N;
}

Modified IIR ring buffer: เก็บ SUM ที่รันอยู่ของค่า N ล่าสุด ทุกครั้งที่มีตัวอย่างใหม่เข้ามา SUM - = SUM / N เพิ่มตัวอย่างใหม่และส่งออก SUM / N

unsigned int Filter(unsigned int sample){
    static unsigned long sum;

    sum -= sum/N;
    sum += sample;

    return sum/N;
}

ถ้าฉันอ่านคุณถูกต้องคุณกำลังอธิบายตัวกรอง IIR ลำดับที่หนึ่ง ค่าที่คุณลบไม่ใช่ค่าที่เก่าที่สุดซึ่งหลุดออกมา แต่แทนที่จะเป็นค่าเฉลี่ยของค่าก่อนหน้า ตัวกรอง IIR อันดับหนึ่งจะมีประโยชน์อย่างแน่นอน แต่ฉันไม่แน่ใจว่าคุณหมายถึงอะไรเมื่อคุณแนะนำว่าเอาต์พุตนั้นเหมือนกันสำหรับสัญญาณเป็นระยะทั้งหมด ที่อัตราตัวอย่าง 10KHz การป้อนคลื่นสี่เหลี่ยม 100Hz เป็นฟิลเตอร์กล่อง 20 ขั้นจะให้สัญญาณที่เพิ่มขึ้นอย่างสม่ำเสมอสำหรับ 20 ตัวอย่างตั้งอยู่สูง 30, ลดลงอย่างสม่ำเสมอสำหรับ 20 ตัวอย่างและอยู่ในระดับต่ำสำหรับ 30 ลำดับแรก ตัวกรอง IIR ...
supercat

... จะให้ผลของคลื่นที่เริ่มเพิ่มขึ้นอย่างรวดเร็วและค่อยๆลดระดับลงใกล้ (แต่ไม่ใช่ที่) ค่าสูงสุดของข้อมูลจากนั้นก็จะเริ่มลดลงอย่างรวดเร็วและจะค่อยๆลดลงในระดับที่ใกล้ที่สุด พฤติกรรมที่แตกต่างกันมาก
supercat

คุณพูดถูกฉันสร้างความสับสนให้กับตัวกรองสองชนิด นี่เป็น IIR อันดับหนึ่งอย่างแท้จริง ฉันเปลี่ยนคำตอบของฉันให้ตรงกัน ขอบคุณ
Stephen Collings

ประเด็นหนึ่งก็คือค่าเฉลี่ยเคลื่อนที่ที่เรียบง่ายอาจจะมีประโยชน์หรือไม่ก็ได้ ด้วยตัวกรอง IIR คุณจะได้ตัวกรองที่ดีซึ่งมี Calcs ค่อนข้างน้อย FIR ที่คุณอธิบายสามารถให้รูปสี่เหลี่ยมผืนผ้าได้ทันเวลา - รูปสี่เหลี่ยมในความถี่ - และคุณไม่สามารถจัดการกับด้านข้างได้ มันอาจจะคุ้มค่าที่จะโยนเป็นจำนวนเต็มไม่กี่ทวีคูณเพื่อให้เป็น FIR ที่ปรับได้ได้อย่างสมมาตรหากคุณสามารถสำรองเห็บนาฬิกาได้
Scott Seidman

@ScottSeidman: ไม่จำเป็นต้องทวีคูณถ้ามีเพียงแค่แต่ละขั้นตอนของ FIR ไม่ว่าจะเป็นเอาท์พุทค่าเฉลี่ยของอินพุตไปยังสเตจนั้นและค่าที่เก็บไว้ก่อนหน้านี้ของมันแล้วเก็บอินพุต (ถ้ามีช่วงตัวเลข มากกว่าค่าเฉลี่ย) ไม่ว่าจะเป็นเรื่องที่ดีกว่าตัวกรองกล่องขึ้นอยู่กับแอปพลิเคชัน (ตัวอย่างเช่นการตอบสนองขั้นตอนของตัวกรองกล่องที่มีความล่าช้ารวม 1ms จะมีการเพิ่มขึ้นของ d2 / dt ที่น่ารังเกียจเมื่ออินพุตเปลี่ยนและอีก 1 มิลลิวินาทีในภายหลัง ต่ำสุดที่เป็นไปได้ d / dt สำหรับตัวกรองที่มีความล่าช้า 1ms ทั้งหมด)
supercat

2

ดังที่mikeselectricstuffกล่าวว่าหากคุณต้องการลดความต้องการหน่วยความจำของคุณลงและคุณไม่คิดว่าการตอบสนองต่อแรงกระตุ้นของคุณจะเป็นแบบเอ็กซ์โพเนนเชียล (แทนที่จะเป็นพัลส์รูปสี่เหลี่ยมผืนผ้า) ฉันจะไปหาตัวกรองค่าเฉลี่ยเคลื่อนที่ ฉันใช้มันอย่างกว้างขวาง ด้วยตัวกรองประเภทนี้คุณไม่จำเป็นต้องใช้บัฟเฟอร์ใด ๆ คุณไม่ต้องเก็บ N ตัวอย่างที่ผ่านมา แค่หนึ่ง. ดังนั้นความต้องการหน่วยความจำของคุณจะถูกลดทอนลงด้วยปัจจัยของ N

นอกจากนี้คุณไม่ต้องการแผนกใด ๆ การคูณเท่านั้น หากคุณมีสิทธิ์เข้าถึงเลขคณิตทศนิยมให้ใช้การคูณทศนิยม มิฉะนั้นให้คูณจำนวนเต็มและเลื่อนไปทางขวา อย่างไรก็ตามเราอยู่ในปี 2012 และฉันขอแนะนำให้คุณใช้คอมไพเลอร์ (และ MCU) ที่อนุญาตให้คุณทำงานกับตัวเลขทศนิยม

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


5
ฉันไม่เห็นด้วยกับการแนะนำของคุณในการใช้ตัวเลขจุดลอยตัว OP อาจใช้ไมโครคอนโทรลเลอร์ 8 บิตด้วยเหตุผล การค้นหาไมโครคอนโทรลเลอร์ 8 บิตพร้อมการสนับสนุนจุดลอยตัวของฮาร์ดแวร์อาจเป็นงานที่ยาก (คุณรู้หรือไม่?) และการใช้หมายเลขทศนิยมโดยไม่มีการสนับสนุนฮาร์ดแวร์จะเป็นงานที่ต้องใช้ทรัพยากรมาก
PetPaulsen

5
การบอกว่าคุณควรใช้กระบวนการที่มีความสามารถในจุดลอยตัวเป็นเพียงความโง่เขลา นอกจากนี้ตัวประมวลผลใด ๆ สามารถทำคะแนนทศนิยมมันเป็นเพียงคำถามของความเร็ว ในโลกฝังตัวค่าใช้จ่ายในการสร้างเพียงไม่กี่เซนต์ก็มีความหมาย
Olin Lathrop

@Olin Lathrop และ PetPaulsen: ฉันไม่เคยบอกว่าเขาควรใช้ MCU กับฮาร์ดแวร์ FPU อ่านคำตอบของฉันอีกครั้ง โดย "(และ MCUs)" ฉันหมายถึง MCU ที่ทรงพลังพอที่จะทำงานกับเลขคณิตจุดลอยตัวของซอฟต์แวร์ในลักษณะลื่นไหลซึ่งไม่ใช่กรณีสำหรับ MCU ทั้งหมด
Telaclavo

4
ไม่จำเป็นต้องใช้ floating-point (ฮาร์ดแวร์หรือซอฟต์แวร์) สำหรับตัวกรอง low-pass 1-pole
Jason S

1
หากเขามีการดำเนินการจุดลอยตัวเขาจะไม่คัดค้านการแบ่งในตอนแรก
Federico Russo

0

ปัญหาหนึ่งของตัวกรอง IIR ที่เกือบจะสัมผัสโดย @olin และ @supercat แต่ดูเหมือนว่าคนอื่นไม่สนใจก็คือการปัดเศษลงทำให้เกิดความไม่แน่นอน (และอาจมีอคติ / ตัด): สมมติว่า N เป็นพลังของสองและเลขจำนวนเต็มเท่านั้น ใช้แล้วการเลื่อนขวาจะกำจัด LSBs ของตัวอย่างใหม่อย่างเป็นระบบ นั่นหมายถึงว่าจะมีซีรี่ส์นานเท่าใดค่าเฉลี่ยจะไม่นำมาพิจารณา

ตัวอย่างเช่นสมมติว่าซีรีย์ที่ลดลงอย่างช้าๆ (8,8,8, ... , 8,7,7,7, ... 7,6,6,) และถือว่าค่าเฉลี่ยเท่ากับ 8 ในตอนเริ่มต้น ตัวอย่างกำปั้น "7" จะนำมาซึ่งค่าเฉลี่ยถึง 7 ไม่ว่าความแรงของตัวกรองจะเป็นเท่าใด เพียงหนึ่งตัวอย่าง เรื่องราวเดียวกันสำหรับ 6 คนตอนนี้คิดเรื่องตรงกันข้าม: ซีรีย์ขึ้นไป ค่าเฉลี่ยจะอยู่ที่ 7 ตลอดไปจนกว่าตัวอย่างจะมีขนาดใหญ่พอที่จะทำให้การเปลี่ยนแปลง

แน่นอนคุณสามารถแก้ไข "อคติ" ได้โดยเพิ่ม 1/2 ^ N / 2 แต่นั่นไม่ได้แก้ปัญหาความแม่นยำ: ในกรณีนั้นชุดที่ลดลงจะอยู่ตลอดไปที่ 8 จนกระทั่งตัวอย่างคือ 8-1 / 2 ^ (N / 2) สำหรับ N = 4 ตัวอย่างใด ๆ ที่สูงกว่าศูนย์จะไม่เปลี่ยนแปลงค่าเฉลี่ย

ฉันเชื่อว่าวิธีการแก้ปัญหาสำหรับการที่จะหมายถึงการสะสมของ LSB ที่หายไป แต่ฉันไม่ได้ทำให้มันเพียงพอที่จะมีโค้ดพร้อมและฉันไม่แน่ใจว่ามันจะไม่เป็นอันตรายต่อพลังงาน IIR ในบางกรณีของซีรีส์ (ตัวอย่างเช่น 7,9,7,9 จะเฉลี่ยถึง 8 หรือไม่) .

@Olin น้ำตกสองขั้นตอนของคุณจะต้องมีคำอธิบาย คุณหมายถึงการถือสองค่าเฉลี่ยกับผลลัพธ์ของการป้อนครั้งแรกเป็นครั้งที่สองในการทำซ้ำแต่ละครั้งหรือไม่? ประโยชน์ของสิ่งนี้คืออะไร?

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