【STM32F429的DSP教程】第38章 STM32F429的FIR高通濾波器實現(支援逐個資料的實時濾波)
完整版教程下載地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=94547
第38章 STM32F429的FIR高通濾波器實現(支援逐個資料的實時濾波)
本章節講解FIR高通濾波器實現。
38.1 初學者重要提示
38.2 高通濾波器介紹
38.3 FIR濾波器介紹
38.4 Matlab工具箱filterDesigner生成高通濾波器C標頭檔案
38.5 FIR高通濾波器設計
38.6 實驗例程說明(MDK)
38.7 實驗例程說明(IAR)
38.8 總結
38.1 初學者重要提示
1、 本章節提供的高通濾波器支援實時濾波,每次可以濾波一個數據,也可以多個數據,不限制大小。但要注意以下兩點:
- 所有資料是在同一個取樣率下依次採集的資料。
- 每次過濾資料個數一旦固定下來,執行中不可再修改。
2、 FIR濾波器的群延遲是一個重要的知識點,詳情在本教程第41章有詳細說明。
38.2 高通濾波器介紹
允許高頻訊號通過,而減弱低於截止頻率的訊號通過。比如混合訊號含有50Hz + 200Hz訊號,我們可通過高通濾波器,過濾掉200Hz訊號,讓50Hz訊號通過。
38.3 FIR濾波器介紹
ARM官方提供的FIR庫支援Q7,Q15,Q31和浮點四種資料型別。其中Q15和Q31提供了快速演算法版本。
FIR濾波器的基本演算法是一種乘法-累加(MAC)執行,輸出表達式如下:
y[n] = b[0] * x[n] + b[1] * x[n-1] + b[2] * x[n-2] + ...+ b[numTaps-1] * x[n-numTaps+1]
結構圖如下:
這種網路結構就是在35.2.1小節所講的直接型結構。
38.4 Matlab工具箱filterDesinger生成高通濾波器C標頭檔案
下面我們講解下如何通過filterDesigner工具生成C標頭檔案,也就是生成濾波器係數。首先在matlab的命視窗輸入filterDesigner就能開啟這個工具箱:
filterDesigner介面開啟效果如下:
FIR濾波器的低通,高通,帶通,帶阻濾波的設定會在後面逐個講解,這裡重點介紹設定後相應引數後如何生成濾波器係數。引數設定好以後點選如下按鈕:
點選Design Filter按鈕以後就生成了所需的濾波器係數,生成濾波器係數以後點選filterDesigner介面上的選單Targets->Generate C header ,開啟後顯示如下介面:
然後點選Generate,生成如下介面:
再點選儲存,並開啟fdatool.h檔案,可以看到生成的係數:
/* * Filter Coefficients (C Source) generated by the Filter Design and Analysis Tool * Generated by MATLAB(R) 9.4 and Signal Processing Toolbox 8.0. * Generated on: 20-Jul-2021 12:19:30 */ /* * Discrete-Time FIR Filter (real) * ------------------------------- * Filter Structure : Direct-Form FIR * Filter Length : 51 * Stable : Yes * Linear Phase : Yes (Type 1) */ /* General type conversion for MATLAB generated C-code */ #include "tmwtypes.h" /* * Expected path to tmwtypes.h * D:\Program Files\MATLAB\R2018a\extern\include\tmwtypes.h */ /* * Warning - Filter coefficients were truncated to fit specified data type. * The resulting response may not match generated theoretical response. * Use the Filter Design & Analysis Tool to design accurate * single-precision filter coefficients. */ const int BL = 51; const real32_T B[51] = { -0.0009190982091, -0.00271769613,-0.002486952813, 0.003661438357, 0.0136509249, 0.01735116541, 0.00766530633,-0.006554719061,-0.007696784101, 0.006105459295, 0.01387391612,0.0003508617228, -0.01690892503,-0.008905642666, 0.01744112931, 0.02074504457, -0.0122964941, -0.03424086422,-0.001034529647, 0.04779030383, 0.02736303769, -0.05937951803, -0.08230702579, 0.06718690693, 0.3100151718, 0.4300478697, 0.3100151718, 0.06718690693, -0.08230702579, -0.05937951803, 0.02736303769, 0.04779030383,-0.001034529647, -0.03424086422, -0.0122964941, 0.02074504457, 0.01744112931,-0.008905642666, -0.01690892503,0.0003508617228, 0.01387391612, 0.006105459295,-0.007696784101,-0.006554719061, 0.00766530633, 0.01735116541, 0.0136509249, 0.003661438357,-0.002486952813, -0.00271769613, -0.0009190982091 };
上面陣列B[51]中的資料就是濾波器係數。下面小節講解如何使用filterDesigner配置FIR低通,高通,帶通和帶阻濾波。關於Filter Designer的其它用法,大家可以在matlab命令視窗中輸入help filterDesigner開啟幫助文件進行學習。
38.5 FIR高通濾波器設計
本章使用的FIR濾波器函式是arm_fir_f32。使用此函式可以設計FIR低通,高通,帶通和帶阻濾波器。
38.5.1 函式arm_fir_init_f32
函式原型:
void arm_fir_init_f32( arm_fir_instance_f32 * S, uint16_t numTaps, const float32_t * pCoeffs, float32_t * pState, uint32_t blockSize);
函式描述:
這個函式用於FIR初始化。
函式引數:
- 第1個引數是arm_fir_instance_f32型別結構體變數。
- 第2個引數是濾波器係數的個數。
- 第3個引數是濾波器係數地址。
- 第4個引數是緩衝狀態地址。
- 第5個引數是每次處理的資料個數,最小可以每次處理1個數據,最大可以每次全部處理完。
注意事項:
結構體arm_fir_instance_f32的定義如下(在檔案arm_math.h檔案):
typedef struct { uint16_t numTaps; /**< number of filter coefficients in the filter. */ float32_t *pState; /**< points to the state variable array. The array is of length */ numTaps+blockSize-1. float32_t *pCoeffs; /**< points to the coefficient array. The array is of length numTaps. */ } arm_fir_instance_f32;
1、引數pCoeffs指向濾波因數,濾波因數陣列長度為numTaps。但要注意pCoeffs指向的濾波因數應該按照如下的逆序進行排列:
{b[numTaps-1], b[numTaps-2], b[N-2], ..., b[1], b[0]}
但滿足線性相位特性的FIR濾波器具有奇對稱或者偶對稱的係數,偶對稱時逆序排列還是他本身。
2、pState指向狀態變數陣列,這個陣列用於函式內部計算資料的快取。
3、blockSize 這個引數的大小沒有特殊要求,最小可以每次處理1個數據,最大可以每次全部處理完。
38.5.2 函式arm_fir_f32
函式原型:
void arm_fir_f32( const arm_fir_instance_f32 * S, const float32_t * pSrc, float32_t * pDst, uint32_t blockSize)
函式描述:
這個函式用於FIR濾波。
函式引數:
- 第1個引數是arm_fir_instance_f32型別結構體變數。
- 第2個引數是源資料地址。
- 第3個引數是濾波後的資料地址。
- 第4個引數是每次呼叫處理的資料個數,最小可以每次處理1個數據,最大可以每次全部處理完。
38.5.3 filterDesigner獲取高通濾波器係數
設計一個如下的例子:
訊號由50Hz正弦波和200Hz正弦波組成,取樣率1Kbps,現設計一個高通濾波器,截止頻率125Hz,取樣1024個數據,採用函式fir1進行設計(注意這個函式是基於視窗的方法設計FIR濾波,預設是hamming窗),濾波器階數設定為28。filterDesigner的配置如下:
配置好高通濾波器後,具體濾波器係數的生成大家參考本章第4小節的方法即可。
38.5.4 高通濾波器實現
通過工具箱filterDesigner獲得高通濾波器係數後在開發板上執行函式arm_fir_f32 來測試高通濾波器的效果。
#define TEST_LENGTH_SAMPLES 1024 /* 取樣點數 */ #define BLOCK_SIZE 1 /* 呼叫一次arm_fir_f32處理的取樣點個數 */ #define NUM_TAPS 29 /* 濾波器係數個數 */ uint32_t blockSize = BLOCK_SIZE; uint32_t numBlocks = TEST_LENGTH_SAMPLES/BLOCK_SIZE; /* 需要呼叫arm_fir_f32的次數 */ static float32_t testInput_f32_50Hz_200Hz[TEST_LENGTH_SAMPLES]; /* 取樣點 */ static float32_t testOutput[TEST_LENGTH_SAMPLES]; /* 濾波後的輸出 */ static float32_t firStateF32[BLOCK_SIZE + NUM_TAPS - 1]; /* 狀態快取,大小numTaps + blockSize - 1*/ /* 高通濾波器係數 通過fadtool獲取*/ const float32_t firCoeffs32HP[NUM_TAPS] = { 0.0018157335f, 0.001582013792f, -6.107207639e-18f, -0.003683975432f, -0.008045346476f, -0.008498443291f, -1.277260999e-17f, 0.01733288541f, 0.03401865438f, 0.0332348831f, -4.021742543e-17f, -0.06737889349f, -0.1516391635f, -0.2220942229f, 0.7486887574f, -0.2220942229f, -0.1516391635f, -0.06737889349f, -4.021742543e-17f, 0.0332348831f, 0.03401865438f, 0.01733288541f, -1.277260999e-17f, -0.008498443291f, -0.008045346476f, -0.003683975432f, -6.107207639e-18f, 0.001582013792f, 0.0018157335f }; /* ********************************************************************************************************* * 函 數 名: arm_fir_f32_hp * 功能說明: 呼叫函式arm_fir_f32_hp實現高通濾波器 * 形 參:無 * 返 回 值: 無 ********************************************************************************************************* */ static void arm_fir_f32_hp(void) { uint32_t i; arm_fir_instance_f32 S; float32_t *inputF32, *outputF32; /* 初始化輸入輸出快取指標 */ inputF32 = &testInput_f32_50Hz_200Hz[0]; outputF32 = &testOutput[0]; /* 初始化結構體S */ arm_fir_init_f32(&S, NUM_TAPS, (float32_t *)&firCoeffs32HP[0], &firStateF32[0], blockSize); /* 實現FIR濾波,這裡每次處理1個點 */ for(i=0; i < numBlocks; i++) { arm_fir_f32(&S, inputF32 + (i * blockSize), outputF32 + (i * blockSize), blockSize); } /* 列印濾波後結果 */ for(i=0; i<TEST_LENGTH_SAMPLES; i++) { printf("%f, %f\r\n", testOutput[i], inputF32[i]); } }
執行如上函式可以通過串列埠打印出函式arm_fir_f32濾波後的波形資料,下面通過Matlab繪製波形來對比Matlab計算的結果和ARM官方庫計算的結果。
對比前需要先將串列埠打印出的一組資料載入到Matlab中, arm_fir_f32的計算結果起名sampledata,載入方法在前面的教程中已經講解過,這裡不做贅述了。Matlab中執行的程式碼如下:
%**************************************************************************************** % FIR低通濾波器設計 %*************************************************************************************** fs=1000; %設定取樣頻率 1K N=320; %取樣點數 n=0:N-1; t=n/fs; %時間序列 f=n*fs/N; %頻率序列 x=sin(2*pi*50*t)+sin(2*pi*200*t); %50Hz和200Hz正弦波混合 b=fir1(28, 0.25); y=filter(b, 1, x); subplot(211); plot(t, y); title('Matlab FIR濾波後的波形'); grid on; subplot(212); plot(t, sampledata); title('ARM官方庫濾波後的波形'); grid on;
Matlab執行結果如下:
從上面的波形對比來看,matlab和函式arm_fir_f32計算的結果基本是一致的。為了更好的說明濾波效果,下面從頻域的角度來說明這個問題,Matlab上面執行如下程式碼:
%**************************************************************************************** % FIR高通濾波器設計 %*************************************************************************************** fs=1000; %設定取樣頻率 1K N=320; %取樣點數 n=0:N-1; t=n/fs; %時間序列 f=n*fs/N; %頻率序列 x=sin(2*pi*50*t)+sin(2*pi*200*t); %50Hz和200Hz正弦波混合 subplot(221); plot(t, x); %繪製訊號x的波形 xlabel('時間'); ylabel('幅值'); title('原始訊號'); grid on; subplot(222); y=fft(x, N); %對訊號x做FFT plot(f,abs(y)); xlabel('頻率/Hz'); ylabel('振幅'); title('原始訊號FFT'); grid on; y3=fft(sampledata, N); %經過FIR濾波器後得到的訊號做FFT subplot(223); plot(f,abs(y3)); xlabel('頻率/Hz'); ylabel('振幅'); title('濾波後訊號FFT'); grid on; b=fir1(28, 125/500, 'high'); %獲得濾波器係數,截止頻率125Hz,高通濾波。 [H,F]=freqz(b,1,512); %通過fir1設計的FIR系統的頻率響應 subplot(224); plot(F/pi,abs(H)); %繪製幅頻響應 xlabel('歸一化頻率'); title(['Order=',int2str(30)]); grid on;
Matlab顯示效果如下:
上面波形變換前的FFT和變換後FFT可以看出,50Hz的正弦波基本被濾除。
38.6 實驗例程說明(MDK)
配套例子:
V6-226_FIR高通濾波器設計(支援逐個資料的實時濾波)
實驗目的:
- FIR高通濾波器的實現,支援實時濾波。
實驗內容:
- 啟動一個自動重灌軟體定時器,每100ms翻轉一次LED2。
- 按下按鍵K1,列印原始波形資料和濾波後的波形資料。
使用AC6注意事項
特別注意附件章節C的問題。
上電後串列埠列印的資訊:
波特率 115200,資料位 8,奇偶校驗位無,停止位 1。
RTT方式列印資訊:
程式設計:
系統棧大小分配:
硬體外設初始化
硬體外設的初始化是在 bsp.c 檔案實現:
/* ********************************************************************************************************* * 函 數 名: bsp_Init * 功能說明: 初始化所有的硬體裝置。該函式配置CPU暫存器和外設的暫存器並初始化一些全域性變數。只需要呼叫一次 * 形 參:無 * 返 回 值: 無 ********************************************************************************************************* */ void bsp_Init(void) { /* STM32F429 HAL 庫初始化,此時系統用的還是F429自帶的16MHz,HSI時鐘: - 呼叫函式HAL_InitTick,初始化滴答時鐘中斷1ms。 - 設定NVIC優先順序分組為4。 */ HAL_Init(); /* 配置系統時鐘到168MHz - 切換使用HSE。 - 此函式會更新全域性變數SystemCoreClock,並重新配置HAL_InitTick。 */ SystemClock_Config(); /* Event Recorder: - 可用於程式碼執行時間測量,MDK5.25及其以上版本才支援,IAR不支援。 - 預設不開啟,如果要使能此選項,務必看V5開發板使用者手冊第8章 */ #if Enable_EventRecorder == 1 /* 初始化EventRecorder並開啟 */ EventRecorderInitialize(EventRecordAll, 1U); EventRecorderStart(); #endif bsp_InitKey(); /* 按鍵初始化,要放在滴答定時器之前,因為按鈕檢測是通過滴答定時器掃描 */ bsp_InitTimer(); /* 初始化滴答定時器 */ bsp_InitUart(); /* 初始化串列埠 */ bsp_InitExtIO(); /* 初始化擴充套件IO */ bsp_InitLed(); /* 初始化LED */ }
主功能:
主程式實現如下操作:
- 啟動一個自動重灌軟體定時器,每100ms翻轉一次LED2。
- 按下按鍵K1,列印原始波形資料和濾波後的波形資料。
/* ********************************************************************************************************* * 函 數 名: main * 功能說明: c程式入口 * 形 參: 無 * 返 回 值: 錯誤程式碼(無需處理) ********************************************************************************************************* */ int main(void) { uint8_t ucKeyCode; /* 按鍵程式碼 */ uint16_t i; bsp_Init(); /* 硬體初始化 */ PrintfLogo(); /* 列印例程資訊到串列埠1 */ PrintfHelp(); /* 列印操作提示資訊 */ for(i=0; i<TEST_LENGTH_SAMPLES; i++) { /* 50Hz正弦波+200Hz正弦波,取樣率1KHz */ testInput_f32_50Hz_200Hz[i] = arm_sin_f32(2*3.1415926f*50*i/1000) + arm_sin_f32(2*3.1415926f*200*i/1000); } bsp_StartAutoTimer(0, 100); /* 啟動1個100ms的自動重灌的定時器 */ /* 進入主程式迴圈體 */ while (1) { bsp_Idle(); /* 這個函式在bsp.c檔案。使用者可以修改這個函式實現CPU休眠和喂狗 */ if (bsp_CheckTimer(0)) /* 判斷定時器超時時間 */ { /* 每隔100ms 進來一次 */ bsp_LedToggle(2); /* 翻轉LED2的狀態 */ } ucKeyCode = bsp_GetKey(); /* 讀取鍵值, 無鍵按下時返回 KEY_NONE = 0 */ if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { case KEY_DOWN_K1: /* K1鍵按下 */ arm_fir_f32_hp(); break; default: /* 其它的鍵值不處理 */ break; } } } }
38.7 實驗例程說明(IAR)
配套例子:
V6-226_FIR高通濾波器設計(支援逐個資料的實時濾波)
實驗目的:
- FIR高通濾波器的實現,支援實時濾波。
實驗內容:
- 啟動一個自動重灌軟體定時器,每100ms翻轉一次LED2。
- 按下按鍵K1,列印原始波形資料和濾波後的波形資料。
上電後串列埠列印的資訊:
波特率 115200,資料位 8,奇偶校驗位無,停止位 1。
RTT方式列印資訊:
程式設計:
系統棧大小分配:
硬體外設初始化
硬體外設的初始化是在 bsp.c 檔案實現:
/* ********************************************************************************************************* * 函 數 名: bsp_Init * 功能說明: 初始化所有的硬體裝置。該函式配置CPU暫存器和外設的暫存器並初始化一些全域性變數。只需要呼叫一次 * 形 參:無 * 返 回 值: 無 ********************************************************************************************************* */ void bsp_Init(void) { /* STM32F429 HAL 庫初始化,此時系統用的還是F429自帶的16MHz,HSI時鐘: - 呼叫函式HAL_InitTick,初始化滴答時鐘中斷1ms。 - 設定NVIC優先順序分組為4。 */ HAL_Init(); /* 配置系統時鐘到168MHz - 切換使用HSE。 - 此函式會更新全域性變數SystemCoreClock,並重新配置HAL_InitTick。 */ SystemClock_Config(); /* Event Recorder: - 可用於程式碼執行時間測量,MDK5.25及其以上版本才支援,IAR不支援。 - 預設不開啟,如果要使能此選項,務必看V5開發板使用者手冊第8章 */ #if Enable_EventRecorder == 1 /* 初始化EventRecorder並開啟 */ EventRecorderInitialize(EventRecordAll, 1U); EventRecorderStart(); #endif bsp_InitKey(); /* 按鍵初始化,要放在滴答定時器之前,因為按鈕檢測是通過滴答定時器掃描 */ bsp_InitTimer(); /* 初始化滴答定時器 */ bsp_InitUart(); /* 初始化串列埠 */ bsp_InitExtIO(); /* 初始化擴充套件IO */ bsp_InitLed(); /* 初始化LED */ }
主功能:
主程式實現如下操作:
- 啟動一個自動重灌軟體定時器,每100ms翻轉一次LED2。
- 按下按鍵K1,列印原始波形資料和濾波後的波形資料。
/* ********************************************************************************************************* * 函 數 名: main * 功能說明: c程式入口 * 形 參: 無 * 返 回 值: 錯誤程式碼(無需處理) ********************************************************************************************************* */ int main(void) { uint8_t ucKeyCode; /* 按鍵程式碼 */ uint16_t i; bsp_Init(); /* 硬體初始化 */ PrintfLogo(); /* 列印例程資訊到串列埠1 */ PrintfHelp(); /* 列印操作提示資訊 */ for(i=0; i<TEST_LENGTH_SAMPLES; i++) { /* 50Hz正弦波+200Hz正弦波,取樣率1KHz */ testInput_f32_50Hz_200Hz[i] = arm_sin_f32(2*3.1415926f*50*i/1000) + arm_sin_f32(2*3.1415926f*200*i/1000); } bsp_StartAutoTimer(0, 100); /* 啟動1個100ms的自動重灌的定時器 */ /* 進入主程式迴圈體 */ while (1) { bsp_Idle(); /* 這個函式在bsp.c檔案。使用者可以修改這個函式實現CPU休眠和喂狗 */ if (bsp_CheckTimer(0)) /* 判斷定時器超時時間 */ { /* 每隔100ms 進來一次 */ bsp_LedToggle(2); /* 翻轉LED2的狀態 */ } ucKeyCode = bsp_GetKey(); /* 讀取鍵值, 無鍵按下時返回 KEY_NONE = 0 */ if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { case KEY_DOWN_K1: /* K1鍵按下 */ arm_fir_f32_hp(); break; default: /* 其它的鍵值不處理 */ break; } } } }
38.8 總結
本章節主要講解了FIR濾波器的高通實現,同時一定要注意線性相位FIR濾波器的群延遲問題,詳見本教程的第41章。
微信公眾號:armfly_com 安富萊論壇:www.armbbs.cn 安富萊淘寶:https://armfly.taobao.com