[導讀]:在嵌入式系統中常常須要採集模擬信號,採集模擬信號的信號鏈中不免引入干擾,那麼如何濾除干擾呢?今天就來個一步一步描述如何設計部署一個IIR濾波器到你的系統。算法
無限衝激響應(IIR: Infinite Impulse Response)是一種適用於許多線性時不變系統的屬性,這些系統的特徵是具備一個衝激響應h(t),該衝激響應h(t)不會在特定點上徹底變爲零,而是無限期地持續。 這與有限衝激響應(FIR: Finite Impulse Response)系統造成對比,在有限衝激響應(FIR)系統中,對於某個有限T,在時間t> T時,衝激響應確實剛好變爲零。 線性時不變系統的常見示例是大多數電子和數字濾波器。 具備此屬性的系統稱爲IIR系統或IIR濾波器。編程
這是常見的教科書式數學嚴謹定義,不少人看到這一下就蒙了,能說人話嗎?數組
線性時不變系統理論俗稱LTI系統理論,源自應用數學,直接在覈磁共振頻譜學、地震學、電路、信號處理和控制理論等技術領域運用。它研究的是線性、非時變系統對任意輸入信號的響應。雖然這些系統的軌跡一般會隨時間變化(例如聲學波形)來測量和跟蹤,可是應用到圖像處理和場論時,LTI系統在空間維度上也有軌跡。所以,這些系統也被稱爲線性非時變平移,在最通常的範圍理論給出此理論。在離散(即採樣)系統中對應的術語是線性非時變平移系統。由電阻、電容、電感組成的電路是LTI系統的一個很好的例子。好比一個運放系統在必定頻帶範圍內知足信號的時域疊加,輸入一個100Hz和200Hz正弦信號,輸出頻率是這兩種信號的線性疊加。微信
用數學對LTI系統描述:函數
線性:輸入\(x_1(t)\),產生響應\(y_1(t)\),而輸入\(x_2(t)\),產生響應\(y_2(t)\),那麼縮放和加和輸入\(a_1x_1(t)+a_2x_2(t)\),產生縮放、加和的響應\(a_1y_1(t)+a_2y_2(t)\),其中\(a_1\)和\(a_2\)是標量,對於任意的有:輸入\(\sum_{k=0}^{N}a_kx_k(t)\),產生響應\(\sum_{k=0}^{N}a_ky_k(t)\)測試
時不變性:指若是將系統的輸入信號延遲\(\tau\)秒,那麼獲得的輸出響應也相應延時\(\tau\)秒。用數學描述,也即若是輸入\(x_1(t)\),產生響應\(y_1(t)\),而輸入\(x_1(t+\tau)\),產生響應\(y_1(t+\tau)\)。這麼描述仍是不易懂,來個圖,有圖有真相:優化
假定一個信號放大電路對100Hz正弦信號放大2倍:ui
則輸出爲:spa
而對200Hz的正弦信號,其放大倍數爲1.7倍。(作過運放電路設計的朋友應該有經驗,在其同頻帶其放大倍數每每並不平坦,也即幅頻響應在頻帶內不平坦,這是比較常見的)。命令行
也即輸入爲:
響應爲:
那麼若是輸入100Hz和200Hz的時域疊加信號,則其輸入爲:
則其響應爲:
由這些圖可看出,輸入信號的形狀保持不變,輸出爲對應輸入的線性時域疊加。
對於時不變,就不用圖描述了,在一個真實電路中,若是輸入延遲必定時間,則響應對應延遲相同時間輸出。
上面這麼多文字只是爲了描述在什麼場合可使用IIR濾波器對信號進行數字濾波。總結而言,就是在線性時不變系統中適用。換言之,在大多數電路系統中咱們均可以嘗試採用IIR濾波器進行數字濾波。
那麼究竟什麼是IIR濾波器呢?從數字信號處理的書籍中咱們能看到這樣的Z變換信號流圖:
表示延遲一拍,在數字系統中表示對於輸入信號而言,即爲上一次採樣值,對於輸出而言,即爲上一次的輸出值。
在時域中對於上述流圖,用時域描述即爲:
若是熟悉Z變換,則Z變換傳遞函數爲:
上述數字濾波器,若是從編程的角度來看,x(n-1),表示上一次的信號,多是來自ADC的上次採樣,而y(n-1)則爲上一次濾波器的輸出值,對應就比較好理解x(n-N)就表示前第n次輸入樣本信號,而y(n-M)則爲前第M次濾波器的輸出。
說了這麼多,只是爲了更好的理解概念,只有概念理解正確,才能用爭取。 概念理解這對工程師而言,很是之重要。
MATLAB提供了很是容易使用的FDATool幫助咱們設計數字濾波器,真正精彩的地方開始了,讓咱們拭目以待究竟如何一步一步設計並實施一個IIR濾波器。
首先打開MATLAB,在命令行中敲fdatool
彈出窗體就是fdatool了,以下:
在設計具體,有幾個相關概念須要澄清:
Fs:採樣率,單位爲Hz,真實部署在系統中,請務必確保樣本是按照恆定採樣率進行採樣,不然將得不到想要的效果。
Fpass: 通頻帶,單位爲Hz,即系統中指望經過的最高頻率。
Fstop: 截至頻率,即幅頻響應的-3dB處的頻率,這個如不理解,請自行查閱相關書籍。
分貝dB: 這是一個無單位反應輸出與輸入倍數的一個術語。電學中分貝與放大倍數的轉換關係爲:
濾波器類型:這裏有Butterworth(巴特沃斯)、Chebyshev Type I,Chebyshev Type II、(切比雪夫)、Elipic 等可選。
就其特色,這裏對其中幾種略做介紹:
假設咱們須要設計一個IIR濾波器,採樣率爲32000Hz, 有用信號頻率在2000Hz內,設計IIR濾波器對信號進行數字濾波。這裏爲節省算力,咱們指定濾波器的階數,也即傳遞函數中N/M中的最大值,通常而言N大於M。
這裏指定階數爲8階,類型指定爲巴特沃斯型IIR濾波器,輸入階數8階,採樣率32000Hz,截至頻率設置爲2000Hz而後點擊Design Filter以下圖所示:
其相頻響應曲線以下:
楚辭以外,咱們還能夠將幅頻與相頻曲線放在一個頻率座標上去看設計結果:
導出濾波器參數,這裏咱們選擇,
選擇ASCII碼
而後就獲得了一個文件,保存2kHz_LPF.fcf,文件名隨你喜歡。
文件內容以下:
% Generated by MATLAB(R) 8.4 and the Signal Processing Toolbox 6.22. % Generated on: 27-Mar-2020 21:27:06 % Coefficient Format: Decimal % Discrete-Time IIR Filter (real) % ------------------------------- % Filter Structure : Direct-Form II, Second-Order Sections % Number of Sections : 4 % Stable : Yes % Linear Phase : No SOS Matrix: 1 2 1 1 -1.7193929141691948 0.8610574795347461 1 2 1 1 -1.5237898734101736 0.64933827386370635 1 2 1 1 -1.4017399331200424 0.51723237044751591 1 2 1 1 -1.3435020629061745 0.45419615396638446 Scale Values: 0.035416141341387819 0.031387100113383172 0.028873109331868367 0.027673522765052503
至此設計工做就結束了,立刻進入濾波器的部署測試階段。
到這裏,沒有經驗的朋友可能會說,這麼一堆參數我該咋用呢?
須要本身去寫前面描述的計算公式嗎?固然你也能夠這麼作,這裏就不寫了,ARM的CMSIS庫已經幫你們設計好了種類繁多的數字信號處理函數實現了,並且通過了測試,這裏直接拿來用便可。有興趣本身寫也不難,只要理解Z傳遞函數概念內涵,很是容易實現。這裏咱們採用32位浮點實現函數:arm_biquad_cascade_df1_f32。
該函數位於:CMSIS\DSP\Source\FilteringFunctions\arm_biquad_cascade_df1_init_f32.c中
以及CMSIS\DSP\Source\FilteringFunctions\arm_biquad_cascade_df1_f32.c
咱們來看一看這個函數:
arm_biquad_cascade_df1_init_f32.c:
/* *做用 :初始化濾波器 *S :指向浮點SOS級聯結構的實例。 *numStages:濾波器中二階SOS的數量 *pCoeffs :濾波器參數指針,參數按下列順序存儲 * {b10, b11, b12, a11, a12, b20, b21, b22, a21, a22, ...} *pState :歷史狀態緩衝區指針 */ void arm_biquad_cascade_df1_init_f32( arm_biquad_casd_df1_inst_f32 * S, uint8_t numStages, const float32_t * pCoeffs, float32_t * pState) { /* Assign filter stages */ S->numStages = numStages; /* Assign coefficient pointer */ S->pCoeffs = pCoeffs; /* Clear state buffer and size is always 4 * numStages */ memset(pState, 0, (4U * (uint32_t) numStages) * sizeof(float32_t)); /* Assign state pointer */ S->pState = pState; }
arm_math.h 定義了須用到的結構體,對於本例相關的結構體爲arm_biquad_casd_df1_inst_f32
typedef struct { unsigned int numStages; /*2階節的個數,應爲2*numStages. */ float *pState; /*狀態係數數組指針,數組長度爲4*numStages*/ float *pCoeffs; /*係數數組指針, 數組的長度爲5*numStages.*/ } arm_biquad_casd_df1_inst_f32;
濾波器具體濾波函數爲arm_biquad_cascade_df1_f32
/** * *S :指向浮點Biquad級聯結構的實例. * *pSrc :指向輸入數據塊。 * *pDst :指向輸出數據塊。 * blockSize:每次調用要處理的樣本數。 * 返回值 :無. */ void arm_biquad_cascade_df1_f32( const arm_biquad_casd_df1_inst_f32 * S, float * pSrc, float * pDst, unsigned int blockSize) { float *pIn = pSrc; /*源指針 */ float *pOut = pDst; /*目的指針 */ float *pState = S->pState; /*狀態指針 */ float *pCoeffs = S->pCoeffs; /*參數指針 */ float acc; /*累加器 */ float b0, b1, b2, a1, a2; /*濾波器參數 */ float Xn1, Xn2, Yn1, Yn2; /*濾波器狀態變量*/ float Xn; /*臨時輸入 */ unsigned int sample, stage = S->numStages; /*循環計數 */ do { /* Reading the coefficients */ b0 = *pCoeffs++; b1 = *pCoeffs++; b2 = *pCoeffs++; a1 = *pCoeffs++; a2 = *pCoeffs++; Xn1 = pState[0]; Xn2 = pState[1]; Yn1 = pState[2]; Yn2 = pState[3]; sample = blockSize >> 2u; while(sample > 0u) { /* 讀第一個輸入 */ Xn = *pIn++; /* acc = b0 * x[n] + b1 * x[n-1] + b2 * x[n-2] + a1 * y[n-1] + a2 * y[n-2] */ Yn2 = (b0 * Xn) + (b1 * Xn1) + (b2 * Xn2) + (a1 * Yn1) + (a2 * Yn2); /* Store the result in the accumulator in the destination buffer. */ *pOut++ = Yn2; /* 每次計算輸出後,狀態都應更新. */ /* 狀態應更新爲: */ /* Xn2 = Xn1 */ /* Xn1 = Xn */ /* Yn2 = Yn1 */ /* Yn1 = acc */ /* Read the second input */ Xn2 = *pIn++; /* acc = b0 * x[n] + b1 * x[n-1] + b2 * x[n-2] + a1 * y[n-1] + a2 * y[n-2] */ Yn1 = (b0 * Xn2) + (b1 * Xn) + (b2 * Xn1) + (a1 * Yn2) + (a2 * Yn1); /* 將結果存儲在目標緩衝區的累加器中. */ *pOut++ = Yn1; /* 每次計算輸出後,狀態都應更新. */ /* 狀態應更新爲: */ /* Xn2 = Xn1 */ /* Xn1 = Xn */ /* Yn2 = Yn1 */ /* Yn1 = acc */ /*讀第三個輸入 */ Xn1 = *pIn++; /* acc = b0 * x[n] + b1 * x[n-1] + b2 * x[n-2] + a1 * y[n-1] + a2 * y[n-2] */ Yn2 = (b0 * Xn1) + (b1 * Xn2) + (b2 * Xn) + (a1 * Yn1) + (a2 * Yn2); /* 將結果存儲在目標緩衝區的累加器中. */ *pOut++ = Yn2; /* 每次計算輸出後,狀態都應更新. */ /* 狀態應更新爲: */ /* Xn2 = Xn1 */ /* Xn1 = Xn */ /* Yn2 = Yn1 */ /* Yn1 = acc */ /* 讀第四個輸入 */ Xn = *pIn++; /* acc = b0 * x[n] + b1 * x[n-1] + b2 * x[n-2] + a1 * y[n-1] + a2 * y[n-2] */ Yn1 = (b0 * Xn) + (b1 * Xn1) + (b2 * Xn2) + (a1 * Yn2) + (a2 * Yn1); /* 將結果存儲在目標緩衝區的累加器中. */ *pOut++ = Yn1; /* 每次計算輸出後,狀態都應更新. */ /* 狀態應更新爲: */ /* Xn2 = Xn1 */ /* Xn1 = Xn */ /* Yn2 = Yn1 */ /* Yn1 = acc */ Xn2 = Xn1; Xn1 = Xn; /* 遞減循環計數器 */ sample--; } /* 若是blockSize不是4的倍數, *請在此處計算任何剩餘的輸出樣本。 *不使用循環展開. */ sample = blockSize & 0x3u; while(sample > 0u) { /* 讀取輸入 */ Xn = *pIn++; /* acc = b0 * x[n] + b1 * x[n-1] + b2 * x[n-2] + a1 * y[n-1] + a2 * y[n-2] */ acc = (b0 * Xn) + (b1 * Xn1) + (b2 * Xn2) + (a1 * Yn1) + (a2 * Yn2); /* 將結果存儲在目標緩衝區的累加器中. */ *pOut++ = acc; /* 每次計算輸出後,狀態都應更新。 */ /* 狀態應更新爲: */ /* Xn2 = Xn1 */ /* Xn1 = Xn */ /* Yn2 = Yn1 */ /* Yn1 = acc */ Xn2 = Xn1; Xn1 = Xn; Yn2 = Yn1; Yn1 = acc; /* d遞減循環計數器 */ sample--; } /* 將更新後的狀態變量存儲回pState數組中 */ *pState++ = Xn1; *pState++ = Xn2; *pState++ = Yn1; *pState++ = Yn2; /*第一階段從輸入緩衝區到輸出緩衝區. */ /*隨後的numStages在輸出緩衝區中就地發生*/ pIn = pDst; /* 重置輸出指針 */ pOut = pDst; /* 遞減循環計數器 */ stage--; } while(stage > 0u); }
開始測試:
#include <stdio.h> #include <math.h> /* SOS Matrix: 1 2 1 1 -1.7193929141691948 0.8610574795347461 1 2 1 1 -1.5237898734101736 0.64933827386370635 1 2 1 1 -1.4017399331200424 0.51723237044751591 1 2 1 1 -1.3435020629061745 0.45419615396638446 Scale Values: 0.035416141341387819 0.031387100113383172 0.028873109331868367 0.027673522765052503 作以下轉換: 1.縮放 [1 2 1] * 0.035416141341387819 [1 2 1] * 0.031387100113383172 [1 2 1] * 0.028873109331868367 [1 2 1] * 0.027673522765052503 獲得: [0.035416141341387819 2*0.035416141341387819 0.035416141341387819] [0.031387100113383172 2*0.031387100113383172 0.031387100113383172] [0.028873109331868367 2*0.028873109331868367 0.028873109331868367] [0.027673522765052503 2*0.027673522765052503 0.027673522765052503] 2.舍掉第四列參數 3.將後兩列分別乘以-1,即: 0.035416141341387819 2*0.035416141341387819 0.035416141341387819 -1.7193929141691948 0.8610574795347461 0.031387100113383172 2*0.031387100113383172 0.031387100113383172 -1.5237898734101736 0.64933827386370635 0.028873109331868367 2*0.028873109331868367 0.028873109331868367 -1.4017399331200424 0.51723237044751591 0.027673522765052503 2*0.027673522765052503 0.027673522765052503 -1.3435020629061745 0.45419615396638446 這樣就獲得了濾波器係數組了 */ #define IIR_SECTION 4 /*見前面設計輸出爲4個SOS塊*/ static float iir_state[4*IIR_SECTION];/*歷史狀態緩衝區 */ const float iir_coeffs[5*IIR_SECTION]={ 0.035416141341387819,2*0.035416141341387819,0.035416141341387819,1.7193929141691948,-0.8610574795347461, 0.031387100113383172,2*0.031387100113383172,0.031387100113383172,1.5237898734101736,-0.64933827386370635, 0.028873109331868367,2*0.028873109331868367,0.028873109331868367,1.4017399331200424,-0.51723237044751591, 0.027673522765052503,2*0.027673522765052503,0.027673522765052503,1.3435020629061745,-0.45419615396638446 }; static arm_biquad_casd_df1_inst_f32 S; /*假定採樣512個點*/ #define BUF_SIZE 512 #define PI 3.1415926 #define SAMPLE_RATE 32000 /*32000Hz*/ int main() { float raw[BUF_SIZE]; float raw_4k[BUF_SIZE]; float raw_out[BUF_SIZE]; float raw_noise[BUF_SIZE]; float raw_noise_out[BUF_SIZE]; arm_biquad_casd_df1_inst_f32 S; FILE *pFile=fopen("./simulation.csv","wt+"); if(pFile==NULL) { printf("file opened failed"); return -1; } for(int i=0;i<BUF_SIZE;i++) { /*模擬9000Hz輸入信號 */ raw[i] = 0.5*1024.0/3*sin(2*PI*800*i/32000.0f)+rand()%50; raw_4k[i] = 0.5*1024.0/3*sin(2*PI*4000*i/32000.0f); /*模擬9000Hz +11000Hz疊加輸入*/ raw_noise[i] = raw[i] + raw_4k[i]; } arm_biquad_cascade_df1_init_f32(&S, IIR_SECTION, (float *)&iir_coeffs[0], (float *)&iir_state[0]); arm_biquad_cascade_df1_f32(&S, raw, raw_out, BUF_SIZE); for(int i=0;i<BUF_SIZE;i++) { fprintf(pFile,"%f,",raw[i]); } fprintf(pFile,"\n"); for(int i=0;i<BUF_SIZE;i++) { fprintf(pFile,"%f,",raw_4k[i]); } fprintf(pFile,"\n"); for(int i=0;i<BUF_SIZE;i++) { fprintf(pFile,"%f,",raw_out[i]); } /*從新初始化*/ arm_biquad_cascade_df1_init_f32(&S, IIR_SECTION, (float *)&iir_coeffs[0], (float *)&iir_state[0]); arm_biquad_cascade_df1_f32(&S, raw_noise, raw_noise_out, BUF_SIZE); fprintf(pFile,"\n"); for(int i=0;i<BUF_SIZE;i++) { fprintf(pFile,"%f,",raw_noise[i]); } fprintf(pFile,"\n"); for(int i=0;i<BUF_SIZE;i++) { fprintf(pFile,"%f,",raw_noise_out[i]); } fclose(pFile); return 0; }
利用csv文件,將模擬數據存儲,直接用excel打開,將行數據生成曲線圖以下:
總結:
IIR濾波器在線性時不變系統中能夠很好的解決工程中通常噪聲問題
若是須要設計帶通、高通濾波器其步驟基本相似,只是濾波器的參數以及SOS塊個數可能不同而已
須要提醒的時,IIR的相頻響應不線性,若是系統對相頻響應有嚴格要求,就須要採用其餘的數字濾波器拓撲形式了
實際應用中,若是階數不高時,如今算力強勁的單片機或者DSP以及能夠直接使用浮點處理。
若是對處理速度有嚴格的實時要求,須要在極短期進行濾波處理,能夠考慮下降階數,或採用定點IIR濾波算法實現。也或者將文中函數進行彙編級優化。
文章出自微信公衆號:嵌入式客棧,關注公衆號,獲取更新更多內容