Tính toán trung bình di chuyển nhanh và hiệu quả bộ nhớ


33

Tôi đang tìm kiếm một giải pháp hiệu quả về thời gian và bộ nhớ để tính trung bình di động trong C. Tôi cần tránh phân chia vì tôi đang ở trên PIC 16 không có đơn vị phân chia chuyên dụng.

Hiện tại, tôi chỉ lưu trữ tất cả các giá trị trong bộ đệm vòng và chỉ cần lưu trữ và cập nhật tổng mỗi khi có giá trị mới. Điều này thực sự hiệu quả, nhưng không may sử dụng hầu hết bộ nhớ khả dụng của tôi ...


3
Tôi không nghĩ có cách nào hiệu quả hơn về không gian để làm việc này.
Rocketmagnet

4
@JobyTaffey, đó là một thuật toán được sử dụng khá rộng rãi trên các hệ thống điều khiển và nó đòi hỏi phải xử lý các tài nguyên phần cứng hạn chế. Vì vậy, tôi nghĩ rằng anh ấy sẽ tìm thấy nhiều sự giúp đỡ ở đây hơn là trên SO.
clabacchio

3
@Joby: Có một số nếp nhăn về câu hỏi này có liên quan đến các hệ thống hạn chế tài nguyên nhỏ. Xem câu trả lời của tôi. Bạn sẽ làm điều này rất khác nhau trên một hệ thống lớn như những người SO đã quen. Điều này đã xuất hiện rất nhiều trong kinh nghiệm của tôi về thiết kế điện tử.
Olin Lathrop

1
Tôi đồng ý. Điều này khá thích hợp cho diễn đàn này, vì nó liên quan đến các hệ thống nhúng.
Rocketmagnet

Tôi rút lại sự phản đối của mình
Toby Jaffey

Câu trả lời:


55

Như những người khác đã đề cập, bạn nên xem xét bộ lọc IIR (phản hồi xung vô hạn) thay vì bộ lọc FIR (phản hồi xung hữu hạn) mà bạn hiện đang sử dụng. Có nhiều hơn thế, nhưng thoạt nhìn, các bộ lọc FIR được triển khai dưới dạng kết hợp rõ ràng và bộ lọc IIR với các phương trình.

Bộ lọc IIR cụ thể tôi sử dụng rất nhiều trong vi điều khiển là bộ lọc thông thấp cực đơn. Đây là tương đương kỹ thuật số của một bộ lọc tương tự RC đơn giản. Đối với hầu hết các ứng dụng, chúng sẽ có các đặc tính tốt hơn bộ lọc hộp mà bạn đang sử dụng. Hầu hết việc sử dụng bộ lọc hộp mà tôi gặp phải là kết quả của việc ai đó không chú ý đến lớp xử lý tín hiệu số, không phải do cần các đặc điểm cụ thể của chúng. Nếu bạn chỉ muốn giảm tần số cao mà bạn biết là nhiễu, bộ lọc thông thấp cực đơn sẽ tốt hơn. Cách tốt nhất để thực hiện một kỹ thuật số trong vi điều khiển thường là:

PHIM <- PHIM + FF (MỚI - PHIM)

LỌC là một phần của trạng thái bền bỉ. Đây là biến duy nhất liên tục bạn cần để tính toán bộ lọc này. MỚI là giá trị mới mà bộ lọc đang được cập nhật với lần lặp này. FF là phần lọc , điều chỉnh "độ nặng" của bộ lọc. Nhìn vào thuật toán này và thấy rằng với FF = 0, bộ lọc cực kỳ nặng vì đầu ra không bao giờ thay đổi. Với FF = 1, thực sự không có bộ lọc nào vì đầu ra chỉ theo đầu vào. Các giá trị hữu ích nằm ở giữa. Trên các hệ thống nhỏ, bạn chọn FF là 1/2 Nsao cho nhân với FF có thể được thực hiện như một sự dịch chuyển đúng bởi N bit. Ví dụ, FF có thể là 1/16 và nhân với FF do đó dịch chuyển đúng 4 bit. Mặt khác, bộ lọc này chỉ cần một phép trừ và một phép cộng, mặc dù các số thường cần rộng hơn giá trị đầu vào (nhiều hơn về độ chính xác số trong một phần riêng biệt bên dưới).

Tôi thường thực hiện các bài đọc A / D nhanh hơn đáng kể so với mức cần thiết và áp dụng hai trong số các bộ lọc này xếp tầng. Đây là tương đương kỹ thuật số của hai bộ lọc RC nối tiếp và suy giảm 12 dB / quãng tám trên tần số rolloff. Tuy nhiên, đối với các lần đọc A / D, thường phải xem xét bộ lọc trong miền thời gian bằng cách xem xét phản hồi bước của nó. Điều này cho bạn biết hệ thống của bạn sẽ thấy sự thay đổi nhanh như thế nào khi thứ bạn đang đo thay đổi.

Để thuận tiện cho việc thiết kế các bộ lọc này (điều này chỉ có nghĩa là chọn FF và quyết định số lượng chúng trong số các tầng đó), tôi sử dụng chương trình FILTBITS của chương trình. Bạn chỉ định số lượng bit thay đổi cho mỗi FF trong chuỗi bộ lọc xếp tầng và nó tính toán phản hồi bước và các giá trị khác. Trên thực tế, tôi thường chạy nó thông qua kịch bản trình bao bọc PLOTFILT. Điều này chạy FILTBITS, tạo một tệp CSV, sau đó vẽ tệp CSV. Ví dụ: đây là kết quả của "PLOTFILT 4 4":

Hai tham số cho PLOTFILT có nghĩa là sẽ có hai bộ lọc xếp tầng được mô tả ở trên. Các giá trị của 4 chỉ ra số bit dịch chuyển để nhận ra bội số của FF. Do đó, hai giá trị FF là 1/16 trong trường hợp này.

Dấu vết màu đỏ là phản ứng bước đơn vị, và là điều chính để xem xét. Ví dụ, điều này cho bạn biết rằng nếu đầu vào thay đổi tức thời, đầu ra của bộ lọc kết hợp sẽ giải quyết đến 90% giá trị mới trong 60 lần lặp. Nếu bạn quan tâm đến thời gian giải quyết 95% thì bạn phải chờ khoảng 73 lần lặp và trong 50% thời gian giải quyết chỉ có 26 lần lặp.

Dấu vết màu xanh lá cây cho bạn thấy đầu ra từ một đột biến biên độ đầy đủ duy nhất. Điều này cung cấp cho bạn một số ý tưởng về việc khử nhiễu ngẫu nhiên. Có vẻ như không có mẫu đơn nào sẽ gây ra thay đổi nhiều hơn 2,5% trong đầu ra.

Dấu vết màu xanh là mang lại cảm giác chủ quan về những gì bộ lọc này làm với nhiễu trắng. Đây không phải là một bài kiểm tra nghiêm ngặt vì không có gì đảm bảo chính xác nội dung của các số ngẫu nhiên được chọn làm đầu vào tiếng ồn trắng cho lần chạy PLOTFILT này. Nó chỉ mang đến cho bạn cảm giác thô ráp về việc nó sẽ bị nghiền nát và mịn như thế nào.

PLOTFILT, có thể là PHIM, và nhiều nội dung hữu ích khác, đặc biệt là để phát triển phần mềm PIC có sẵn trong bản phát hành phần mềm PIC Development Tools tại trang tải xuống Phần mềm của tôi .

Đã thêm về độ chính xác số

Tôi thấy từ các ý kiến ​​và bây giờ một câu trả lời mới rằng có sự quan tâm trong việc thảo luận về số lượng bit cần thiết để thực hiện bộ lọc này. Lưu ý rằng nhân với FF sẽ tạo ra các bit mới Log 2 (FF) bên dưới điểm nhị phân. Trên các hệ thống nhỏ, FF thường được chọn là 1/2 N để số nhân này thực sự được nhận ra bằng một sự dịch chuyển phải của N bit.

Do đó, FILT thường là một số nguyên điểm cố định. Lưu ý rằng điều này không thay đổi bất kỳ phép toán nào theo quan điểm của bộ xử lý. Ví dụ: nếu bạn đang lọc các số đọc A / D 10 bit và N = 4 (FF = 1/16), thì bạn cần 4 bit phân số dưới số đọc A / D số nguyên 10 bit. Hầu hết các bộ xử lý, bạn sẽ thực hiện các hoạt động số nguyên 16 bit do các lần đọc A / D 10 bit. Trong trường hợp này, bạn vẫn có thể thực hiện chính xác các phép toán số nguyên 16 bit giống nhau, nhưng bắt đầu với số đọc A / D được dịch chuyển trái 4 bit. Bộ xử lý không biết sự khác biệt và không cần. Làm toán trên toàn bộ số nguyên 16 bit hoạt động cho dù bạn coi chúng là 12,4 điểm cố định hay số nguyên 16 bit thực (điểm cố định 16,0).

Nói chung, bạn cần thêm N bit cho mỗi cực của bộ lọc nếu bạn không muốn thêm nhiễu do biểu diễn bằng số. Trong ví dụ trên, bộ lọc thứ hai của hai sẽ phải có 10 + 4 + 4 = 18 bit để không bị mất thông tin. Trong thực tế trên máy 8 bit có nghĩa là bạn sử dụng các giá trị 24 bit. Về mặt kỹ thuật, chỉ có cực thứ hai của hai sẽ cần giá trị rộng hơn, nhưng để đơn giản phần sụn, tôi thường sử dụng cùng một biểu diễn, và do đó cùng một mã, cho tất cả các cực của bộ lọc.

Thông thường tôi viết một chương trình con hoặc macro để thực hiện một thao tác cực bộ lọc, sau đó áp dụng điều đó cho mỗi cực. Việc một chương trình con hay macro phụ thuộc vào việc chu kỳ hoặc bộ nhớ chương trình là quan trọng hơn trong dự án cụ thể đó. Dù bằng cách nào, tôi sử dụng một số trạng thái cào để chuyển MỚI vào chương trình con / macro, cập nhật FILT, nhưng cũng tải trạng thái đó vào cùng trạng thái cào MỚI. Điều này giúp dễ dàng áp dụng nhiều cực kể từ khi PHẢI cập nhật một cực MỚI của cái tiếp theo. Khi một chương trình con, thật hữu ích khi có một con trỏ trỏ tới LỌC trên đường vào, được cập nhật ngay sau khi LỌC trên đường ra. Bằng cách đó, chương trình con tự động hoạt động trên các bộ lọc liên tiếp trong bộ nhớ nếu được gọi nhiều lần. Với một macro bạn không cần một con trỏ vì bạn chuyển địa chỉ để hoạt động trên mỗi lần lặp.

Mã ví dụ

Dưới đây là ví dụ về macro như được mô tả ở trên cho PIC 18:

///////////////////////////////////////////////////// /////////////////////////////////
//
// Bộ lọc Macro LỌC
//
// Cập nhật một cực bộ lọc với giá trị mới trong NEWVAL. NEWVAL được cập nhật thành
// chứa giá trị được lọc mới.
//
// FILT là tên của biến trạng thái bộ lọc. Nó được giả định là 24 bit
// rộng và trong ngân hàng địa phương.
//
// Công thức cập nhật bộ lọc là:
//
// PHIM <- PHIM + FF (NEWVAL - PHIM)
//
// Nhân với FF được thực hiện bằng một sự dịch chuyển đúng của các bit FILTBITS.
//
/ bộ lọc macro
  / viết
         ngân hàng lbankadr
         Movf [arg 1] +0, w; NEWVAL <- NEWVAL - PHIM
         subwf newval + 0
         Movf [arg 1] +1, w
         subwfb newval + 1
         Movf [arg 1] +2, w
         subwfb newval + 2

  / viết
  / loop n bộ lọc; một lần cho mỗi bit để dịch chuyển NEWVAL sang phải
         rlcf newval + 2, w; thay đổi NEWVAL ngay một bit
         rrcf newval + 2
         rrcf newval + 1
         rrcf newval + 0
    / endloop

  / viết
         Movf newval + 0, w; thêm giá trị đã dịch chuyển vào bộ lọc và lưu trong 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

Và đây là một macro tương tự cho PIC 24 hoặc DSPIC 30 hoặc 33:

///////////////////////////////////////////////////// /////////////////////////////////
//
// Ffbits Macro LỌC
//
// Cập nhật trạng thái của một bộ lọc thông thấp. Giá trị đầu vào mới nằm trong W1: W0
// và trạng thái bộ lọc được cập nhật được trỏ đến bởi W2.
//
// Giá trị bộ lọc được cập nhật cũng sẽ được trả về trong W1: W0 và W2 sẽ trỏ
// đến bộ nhớ đầu tiên qua trạng thái bộ lọc. Macro này do đó có thể là
// được gọi liên tiếp để cập nhật một loạt các bộ lọc thông thấp xếp tầng.
//
// Công thức lọc là:
//
// PHIM <- PHIM + FF (MỚI - PHIM)
//
// trong đó phép nhân với FF được thực hiện bởi sự dịch chuyển phải của số học của
// FFBITS.
//
// CẢNH BÁO: W3 bị vứt đi.
//
/ bộ lọc macro
  / var new ffbits số nguyên = [arg 1]; lấy số bit để dịch chuyển

  / viết
  / write "; Thực hiện lọc một cực thông thấp, shift bit =" ffbits
  / viết ";"

         phụ w0, [w2 ++], w0; MỚI - PHIM -> W1: W0
         phụ w1, [w2--], w1

         lsr w0, # [v ffbits], w0; dịch chuyển kết quả trong W1: W0 sang phải
         sl w1, # [- 16 khung hình], w3
         ior w0, w3, w0
         asr w1, # [v ffbits], w1

         thêm w0, [w2 ++], w0; thêm PHIM để tạo kết quả cuối cùng trong W1: W0
         addc w1, [w2--], w1

         Mov w0, [w2 ++]; ghi kết quả vào trạng thái bộ lọc, con trỏ trước
         Mov w1, [w2 ++]

  / viết
  / endmac

Cả hai ví dụ này đều được triển khai dưới dạng macro sử dụng bộ tiền xử lý trình biên dịch PIC của tôi , có khả năng cao hơn cả các tiện ích macro tích hợp.


1
+1 - ngay trên tiền. Điều duy nhất tôi muốn nói thêm là các bộ lọc trung bình di chuyển có vị trí của chúng khi được thực hiện đồng bộ với một số tác vụ (như tạo ra dạng sóng ổ đĩa để điều khiển máy phát siêu âm) để chúng lọc ra sóng hài 1 / T trong đó T là di chuyển thời gian trung bình.
Jason S

2
Câu trả lời tốt đẹp, nhưng chỉ hai điều. Thứ nhất: không nhất thiết là thiếu chú ý dẫn đến việc lựa chọn bộ lọc sai; trong trường hợp của tôi, tôi chưa bao giờ được dạy về sự khác biệt và điều tương tự cũng áp dụng cho những người không tốt nghiệp. Vì vậy, đôi khi nó chỉ là sự thiếu hiểu biết. Nhưng thứ hai: tại sao bạn xếp tầng hai bộ lọc kỹ thuật số bậc nhất thay vì sử dụng bộ lọc bậc cao hơn? (chỉ để hiểu, tôi không chỉ trích)
clabacchio

3
hai bộ lọc IIR cực xếp tầng mạnh hơn đối với các vấn đề về số và dễ thiết kế hơn so với bộ lọc IIR bậc 2; sự đánh đổi là với 2 giai đoạn xếp tầng, bạn có được bộ lọc Q (= 1/2?) thấp, nhưng trong hầu hết các trường hợp đó không phải là một vấn đề lớn.
Jason S

1
@clabacchio: Một vấn đề khác tôi nên đề cập là việc triển khai phần sụn. Bạn có thể viết một chương trình con bộ lọc thông thấp cực một lần, sau đó áp dụng nó nhiều lần. Trong thực tế, tôi thường viết một chương trình con như vậy để đưa một con trỏ trong bộ nhớ về trạng thái bộ lọc, sau đó cho nó tiến con trỏ để có thể gọi nó một cách dễ dàng để nhận ra các bộ lọc đa cực.
Olin Lathrop

1
1. cảm ơn rất nhiều vì câu trả lời của bạn - tất cả chúng. Tôi đã quyết định sử dụng Bộ lọc IIR này, nhưng Bộ lọc này không được sử dụng làm Bộ lọc LowPass tiêu chuẩn, vì tôi cần trung bình các Giá trị bộ đếm và so sánh chúng để phát hiện các Thay đổi trong một Phạm vi nhất định. vì các giá trị này có các kích thước rất khác nhau tùy thuộc vào Phần cứng, tôi muốn lấy trung bình để có thể tự động phản ứng với các thay đổi cụ thể của Phần cứng này.
Sensslen

18

Nếu bạn có thể sống với giới hạn công suất của hai số lượng vật phẩm ở mức trung bình (ví dụ 2,4,8,16,32, v.v.) thì việc phân chia có thể được thực hiện dễ dàng và hiệu quả trên một vi mô hiệu suất thấp mà không có phân chia chuyên dụng vì nó có thể được thực hiện như một sự thay đổi một chút. Mỗi ca làm việc là một sức mạnh của hai, ví dụ:

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

hoặc là

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

v.v.


Làm thế nào để giúp đỡ? OP cho biết vấn đề chính là giữ các mẫu trong quá khứ.
Jason S

Điều này hoàn toàn không giải quyết câu hỏi của OP.
Rocketmagnet

12
OP nghĩ rằng anh ta có hai vấn đề, phân chia trong PIC16 và bộ nhớ cho bộ đệm vòng của mình. Câu trả lời này cho thấy việc phân chia không khó. Phải thừa nhận rằng nó không giải quyết được vấn đề về bộ nhớ nhưng hệ thống SE cho phép trả lời một phần và người dùng có thể tự mình lấy một cái gì đó từ mỗi câu trả lời hoặc thậm chí chỉnh sửa và kết hợp các câu trả lời của người khác. Vì một số câu trả lời khác yêu cầu thao tác chia, chúng không hoàn chỉnh tương tự vì chúng không chỉ ra cách đạt được điều này một cách hiệu quả trên PIC16.
Martin

8

một câu trả lời cho một bộ lọc trung bình chuyển động đúng (hay còn gọi là "toa chở súc vật lọc") với yêu cầu bộ nhớ ít hơn, nếu bạn không nhớ downsampling. Nó được gọi là bộ lọc tích hợp xếp tầng (CIC). Ý tưởng là bạn có một bộ tích hợp mà bạn có sự khác biệt trong một khoảng thời gian và thiết bị tiết kiệm bộ nhớ chính là bằng cách lấy mẫu xuống, bạn không phải lưu trữ mọi giá trị của bộ tích hợp. Nó có thể được thực hiện bằng cách sử dụng mã giả sau:

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

Chiều dài trung bình di chuyển hiệu quả của bạn là decimationFactor*statesizenhưng bạn chỉ cần giữ xung quanh statesizecác mẫu. Rõ ràng bạn có thể có được hiệu suất tốt hơn nếu bạn statesizedecimationFactorcó quyền hạn bằng 2, để các toán tử phân chia và phần còn lại được thay thế bằng các ca và mặt nạ.


Postcript: Tôi đồng ý với Olin rằng bạn nên luôn luôn xem xét các bộ lọc IIR đơn giản trước khi bộ lọc trung bình di chuyển. Nếu bạn không cần tần số null của bộ lọc boxcar, bộ lọc thông thấp 1 cực hoặc 2 cực có thể sẽ hoạt động tốt.

Mặt khác, nếu bạn đang lọc cho các mục đích của số thập phân (lấy đầu vào tốc độ mẫu cao và lấy trung bình để sử dụng cho quy trình tốc độ thấp) thì bộ lọc CIC có thể là thứ bạn đang tìm kiếm. (đặc biệt là nếu bạn có thể sử dụng stateize = 1 và tránh ringbuffer hoàn toàn chỉ với một giá trị tích hợp trước đó)


8

Có một số phân tích chuyên sâu về toán học đằng sau bằng cách sử dụng bộ lọc IIR thứ tự đầu tiên mà Olin Lathrop đã mô tả trên trao đổi ngăn xếp xử lý tín hiệu số (bao gồm rất nhiều hình ảnh đẹp.) Phương trình cho bộ lọc IIR này là:

y [n] = αx [n] + (1 − α) y [n − 1]

Điều này có thể được thực hiện bằng cách chỉ sử dụng các số nguyên và không có phân chia sử dụng mã sau (có thể cần một số gỡ lỗi khi tôi nhập từ bộ nhớ.)

/**
*  @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.
}

Bộ lọc này xấp xỉ trung bình di động của các mẫu K cuối cùng bằng cách đặt giá trị của alpha thành 1 / K. Làm điều này trong mã trước bởi #defineing BITSđể log2 (K), tức là cho K = 16 bộ BITS4, cho K = 4 set BITSđể 2, vv

(Tôi sẽ xác minh mã được liệt kê ở đây ngay khi tôi nhận được thay đổi và chỉnh sửa câu trả lời này nếu cần.)


6

Đây là bộ lọc thông thấp cực đơn (trung bình di chuyển, với tần số cắt = CutoffFrequency). Rất đơn giản, rất nhanh, hoạt động tuyệt vời và hầu như không có bộ nhớ trên đầu.

Lưu ý: Tất cả các biến có phạm vi ngoài chức năng bộ lọc, ngoại trừ thông qua 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);
}

Lưu ý: Đây là một bộ lọc giai đoạn duy nhất. Nhiều giai đoạn có thể được xếp tầng với nhau để tăng độ sắc nét của bộ lọc. Nếu bạn sử dụng nhiều hơn một giai đoạn, bạn sẽ phải điều chỉnh DecayFactor (liên quan đến Tần số cắt) để bù.

Và rõ ràng tất cả những gì bạn cần là hai dòng được đặt ở bất cứ đâu, chúng không cần chức năng riêng của chúng. Bộ lọc này có thời gian tăng tốc trước khi trung bình di chuyển thể hiện tín hiệu đầu vào. Nếu bạn cần bỏ qua thời gian tăng tốc đó, bạn có thể khởi tạo MoveAlusive thành giá trị đầu tiên của newInput thay vì 0 và hy vọng newInput đầu tiên không phải là ngoại lệ.

(CutoffFrequency / SampleRate) có phạm vi từ 0 đến 0,5. DecayFactor là giá trị từ 0 đến 1, thường gần bằng 1.

Phao đơn chính xác là đủ tốt cho hầu hết mọi thứ, tôi chỉ thích gấp đôi. Nếu bạn cần gắn bó với số nguyên, bạn có thể chuyển đổi DecayFactor và Amplitude Factor thành số nguyên phân số, trong đó tử số được lưu dưới dạng số nguyên và mẫu số là công suất nguyên 2 (vì vậy bạn có thể dịch chuyển bit sang bên phải như mẫu số thay vì phải chia trong vòng lọc). Ví dụ: nếu DecayFactor = 0,99 và bạn muốn sử dụng số nguyên, bạn có thể đặt DecayFactor = 0.99 * 65536 = 64881. Sau đó, bất cứ khi nào bạn nhân với DecayFactor trong vòng lọc của mình, chỉ cần thay đổi kết quả >> 16.

Để biết thêm thông tin về điều này, một cuốn sách tuyệt vời trực tuyến, chương 19 về các bộ lọc đệ quy: http://www.dspguide.com/ch19.htm

PS Đối với mô hình Trung bình Di chuyển, một cách tiếp cận khác để đặt DecayFactor và AmplitudeFactor có thể phù hợp hơn với nhu cầu của bạn, giả sử bạn muốn trước đó, khoảng 6 mục được tính trung bình cùng nhau, thực hiện một cách riêng biệt, bạn sẽ thêm 6 mục và chia cho 6, vì vậy bạn có thể đặt AmplitudeFactor thành 1/6 và DecayFactor thành (1.0 - AmplitudeFactor).


4

Bạn có thể ước chừng một avarage di chuyển cho một số ứng dụng với bộ lọc IIR đơn giản.

trọng số là 0..255 giá trị, giá trị cao = thời gian ngắn hơn để bay

Giá trị = (giá trị mới * trọng lượng + giá trị * (trọng lượng 256)) / 256

Để tránh các lỗi làm tròn, giá trị thường sẽ là một khoảng thời gian dài, trong đó bạn chỉ sử dụng các byte có thứ tự cao hơn làm giá trị 'thực tế' của mình.


3

Mọi người khác đã nhận xét kỹ lưỡng về tiện ích của IIR so với FIR và về phân chia quyền lực của hai. Tôi chỉ muốn cung cấp một số chi tiết thực hiện. Dưới đây hoạt động tốt trên các vi điều khiển nhỏ không có FPU. Không có phép nhân và nếu bạn giữ cho N một lũy thừa hai, tất cả phép chia là dịch chuyển bit đơn chu kỳ.

Bộ đệm vòng FIR cơ bản: giữ bộ đệm đang chạy của các giá trị N cuối cùng và SUM chạy tất cả các giá trị trong bộ đệm. Mỗi khi có một mẫu mới, trừ đi giá trị cũ nhất trong bộ đệm từ SUM, thay thế nó bằng mẫu mới, thêm mẫu mới vào SUM và xuất 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;
}

Bộ đệm vòng IIR đã sửa đổi: giữ SUM chạy của các giá trị N cuối cùng. Mỗi khi có một mẫu mới, SUM - = SUM / N, thêm vào mẫu mới và xuất SUM / N.

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

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

    return sum/N;
}

Nếu tôi đọc đúng bạn, bạn đang mô tả bộ lọc IIR đầu tiên; giá trị bạn đang trừ không phải là giá trị cũ nhất sẽ giảm, mà thay vào đó là giá trị trung bình của các giá trị trước đó. Bộ lọc IIR thứ nhất chắc chắn có thể hữu ích, nhưng tôi không chắc ý của bạn là gì khi bạn đề xuất rằng đầu ra giống nhau cho tất cả các tín hiệu định kỳ. Ở tốc độ mẫu 10KHz, đưa sóng vuông 100Hz vào bộ lọc hộp 20 giai đoạn sẽ tạo ra tín hiệu tăng đồng đều cho 20 mẫu, ở mức cao trong 30, giảm đều cho 20 mẫu và ở mức thấp trong 30. Đơn hàng đầu tiên Bộ lọc IIR ...
supercat

... sẽ tạo ra một làn sóng bắt đầu tăng mạnh và giảm dần mức gần (nhưng không phải ở mức tối đa đầu vào, sau đó bắt đầu giảm mạnh và giảm dần mức gần (nhưng không phải) ở mức tối thiểu đầu vào. Hành vi rất khác nhau.
supercat

Bạn nói đúng, tôi đã nhầm lẫn hai loại bộ lọc. Đây thực sự là một IIR đầu tiên. Tôi đang thay đổi câu trả lời của mình cho phù hợp. Cảm ơn.
Stephen Collings

Một vấn đề là trung bình di chuyển đơn giản có thể có hoặc không hữu ích. Với bộ lọc IIR, bạn có thể có được một bộ lọc đẹp với tương đối ít calcs. FIR mà bạn mô tả chỉ có thể cung cấp cho bạn một hình chữ nhật kịp thời - một sự chân thành trong freq - và bạn không thể quản lý các thùy bên. Nó có thể là giá trị nó để ném vào một số nhân số nguyên để làm cho nó trở thành một FIR điều chỉnh đối xứng đẹp nếu bạn có thể dự phòng các đồng hồ tích tắc.
Scott Seidman

@ScottSeidman: Không cần nhân nếu chỉ đơn giản là mỗi giai đoạn của FIR sẽ xuất trung bình của đầu vào cho giai đoạn đó và giá trị được lưu trữ trước đó, sau đó lưu trữ đầu vào (nếu có phạm vi số, người ta có thể sử dụng tổng hơn là trung bình). Việc đó có tốt hơn bộ lọc hộp hay không phụ thuộc vào ứng dụng (ví dụ: phản hồi bước của bộ lọc hộp có tổng độ trễ là 1ms, chẳng hạn, sẽ có sự tăng đột biến d2 / dt khó chịu khi đầu vào thay đổi, và lại 1ms sau, nhưng sẽ có d / dt tối thiểu có thể cho một bộ lọc có độ trễ tổng cộng 1ms).
supercat

2

Như điện máy nói, nếu bạn thực sự cần giảm nhu cầu bộ nhớ và bạn không tâm đến phản ứng thúc đẩy của mình là một hàm mũ (thay vì xung hình chữ nhật), tôi sẽ đi đến bộ lọc trung bình di chuyển theo cấp số nhân . Tôi sử dụng chúng rộng rãi. Với loại bộ lọc đó, bạn không cần bất kỳ bộ đệm nào. Bạn không phải lưu trữ N mẫu trước đây. Chỉ một. Vì vậy, yêu cầu bộ nhớ của bạn bị cắt giảm bởi một yếu tố của N.

Ngoài ra, bạn không cần bất kỳ phân chia cho điều đó. Chỉ nhân. Nếu bạn có quyền truy cập vào số học dấu phẩy động, hãy sử dụng phép nhân dấu phẩy động. Mặt khác, thực hiện phép nhân số nguyên và dịch chuyển sang phải. Tuy nhiên, chúng tôi đang ở trong năm 2012 và tôi khuyên bạn nên sử dụng trình biên dịch (và MCU) cho phép bạn làm việc với các số có dấu phẩy động.

Bên cạnh đó là bộ nhớ hiệu quả hơn và nhanh hơn (bạn không phải cập nhật các mục trong bất kỳ bộ đệm tròn nào), tôi sẽ nói rằng nó cũng tự nhiên hơn , bởi vì phản ứng xung theo cấp số nhân phù hợp hơn với cách hành xử của thiên nhiên, trong hầu hết các trường hợp.


5
Tôi không đồng ý với bạn về việc sử dụng số dấu phẩy động. OP có thể sử dụng vi điều khiển 8 bit vì một lý do. Tìm một vi điều khiển 8 bit có hỗ trợ điểm nổi phần cứng có thể là một nhiệm vụ khó khăn (bạn có biết gì không?). Và sử dụng số dấu phẩy động mà không cần hỗ trợ phần cứng sẽ là một nhiệm vụ rất tốn tài nguyên.
PetPaulsen

5
Nói rằng bạn nên luôn luôn sử dụng một quá trình với khả năng dấu phẩy động chỉ là ngớ ngẩn. Bên cạnh đó, bất kỳ bộ xử lý nào cũng có thể thực hiện dấu phẩy động, đó chỉ là một câu hỏi về tốc độ. Trong thế giới nhúng, một vài xu trong chi phí xây dựng có thể có ý nghĩa.
Olin Lathrop

@Olin Lathrop và PetPaulsen: Tôi chưa bao giờ nói anh ấy nên sử dụng MCU với phần cứng FPU. Đọc lại câu trả lời của tôi. Bởi "(và MCU)" Ý tôi là MCU đủ mạnh để làm việc với số học dấu phẩy động phần mềm theo cách trôi chảy, điều này không đúng với tất cả các MCU.
Telaclavo

4
Không cần sử dụng dấu phẩy động (phần mềm HOẶC phần cứng) chỉ cho bộ lọc thông thấp 1 cực.
Jason S

1
Nếu anh ta có các hoạt động điểm nổi, anh ta sẽ không phản đối việc phân chia ở nơi đầu tiên.
Federico Russo

0

Một vấn đề với bộ lọc IIR gần như bị chạm bởi @olin và @supercat nhưng dường như không quan tâm đến những người khác là việc làm tròn giới thiệu một số sự thiếu chính xác (và có khả năng sai lệch / cắt ngắn): giả sử rằng N là lũy thừa của hai và chỉ có số nguyên là được sử dụng, quyền thay đổi sẽ loại bỏ một cách có hệ thống các LSB của mẫu mới. Điều đó có nghĩa là bộ phim có thể kéo dài bao lâu, trung bình sẽ không bao giờ tính đến chúng.

Ví dụ: giả sử một chuỗi giảm chậm (8,8,8, ..., 8,7,7,7, ... 7,6,6,) và giả sử trung bình thực sự là 8 ở đầu. Mẫu "7" nắm tay sẽ mang lại mức trung bình là 7, bất kể cường độ bộ lọc. Chỉ cho một mẫu. Câu chuyện tương tự cho 6, v.v ... Bây giờ nghĩ về điều ngược lại: serie đi lên. Trung bình sẽ ở lại 7 mãi mãi, cho đến khi mẫu đủ lớn để làm cho nó thay đổi.

Tất nhiên, bạn có thể sửa lỗi cho "độ lệch" bằng cách thêm 1/2 ^ N / 2, nhưng điều đó sẽ không thực sự giải quyết được vấn đề chính xác: trong trường hợp đó, chuỗi giảm sẽ duy trì ở mức 8 cho đến khi mẫu là 8-1 / 2 ^ (N / 2). Ví dụ, với N = 4, mọi mẫu trên 0 sẽ giữ mức trung bình không đổi.

Tôi tin rằng một giải pháp cho điều đó có nghĩa là nắm giữ một người tích lũy các LSB bị mất. Nhưng tôi đã không làm cho nó đủ xa để sẵn sàng mã và tôi không chắc nó sẽ không gây hại cho sức mạnh IIR trong một số trường hợp khác của chuỗi (ví dụ: liệu 7,9,7,9 có trung bình đến 8 không) .

@Olin, tầng hai tầng của bạn cũng sẽ cần một số lời giải thích. Bạn có nghĩa là giữ hai giá trị trung bình với kết quả của lần đầu tiên được đưa vào lần thứ hai trong mỗi lần lặp? Lợi ích của việc này là gì?

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.