完整版教程下載地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=94547
第37章 STM32F407的FIR低通濾波器實作(支持逐個資料的實時濾波)
本章節講解FIR低通濾波器實作,
目錄
37.1 初學者重要提示
37.2 低通濾波器介紹
37.3 FIR濾波器介紹
37.4 Matlab工具箱filterDesinger生成低通濾波器C頭檔案
37.5 FIR低通濾波器設計
37.5.1 函式arm_fir_init_f32
37.5.2 函式arm_fir_f32
37.5.3 filterDesigner獲取低通濾波器系數
37.5.4 低通濾波器實作
37.6 實驗例程說明(MDK)
37.7 實驗例程說明(IAR)
37.8 總結
37.1 初學者重要提示
1、 本章節提供的低通濾波器支持實時濾波,每次可以濾波一個資料,也可以多個資料,不限制大小,但要注意以下兩點:
- 所有資料是在同一個采樣率下依次采集的資料,
- 每次過濾資料個數一旦固定下來,運行中不可再修改,
2、 FIR濾波器的群延遲是一個重要的知識點,詳情在本教程第41章有詳細說明,
37.2 低通濾波器介紹
允許低頻信號通過,而減弱高于截止頻率的信號通過,比如混合信號含有50Hz + 200Hz信號,我們可通過低通濾波器,過濾掉200Hz信號,讓50Hz信號通過,

37.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小節所講的直接型結構,
37.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打開幫助檔案進行學習,

37.5 FIR低通濾波器設計
本章使用的FIR濾波器函式是arm_fir_f32,使用此函式可以設計FIR低通,高通,帶通和帶阻
濾波器,
37.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個資料,最大可以每次全部處理完,
37.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個資料,最大可以每次全部處理完,
37.5.3 filterDesigner獲取低通濾波器系數
設計一個如下的例子:
信號由50Hz正弦波和200Hz正弦波組成,采樣率1Kbps,現設計一個低通濾波器,截止頻率125Hz,采樣1024個資料,采用函式fir1進行設計(注意這個函式是基于視窗的方法設計FIR濾波,默認是hamming窗),濾波器階數設定為28,filterDesigner的配置如下:

配置好低通濾波器后,具體濾波器系數的生成大家參考本章第4小節的方法即可,
37.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 firCoeffs32LP[NUM_TAPS] = {
-0.001822523074f, -0.001587929321f, 1.226008847e-18f, 0.003697750857f, 0.008075430058f,
0.008530221879f, -4.273456581e-18f, -0.01739769801f, -0.03414586186f, -0.03335915506f,
8.073562366e-18f, 0.06763084233f, 0.1522061825f, 0.2229246944f, 0.2504960895f,
0.2229246944f, 0.1522061825f, 0.06763084233f, 8.073562366e-18f, -0.03335915506f,
-0.03414586186f, -0.01739769801f, -4.273456581e-18f, 0.008530221879f, 0.008075430058f,
0.003697750857f, 1.226008847e-18f, -0.001587929321f, -0.001822523074f
};
/*
*********************************************************************************************************
* 函 數 名: arm_fir_f32_lp
* 功能說明: 呼叫函式arm_fir_f32_lp實作低通濾波器
* 形 參:無
* 返 回 值: 無
*********************************************************************************************************
*/
static void arm_fir_f32_lp(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 *)&firCoeffs32LP[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,加載方法在第13章13.6小結已經講解,這里不做贅述了,Matlab中運行的代碼如下:
%****************************************************************************************
% FIR低通濾波器設計
%***************************************************************************************
fs=1000; %設定采樣頻率 1K
N=1024; %采樣點數
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=1024; %采樣點數
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); %繪制信號Mix_Signal的波形
xlabel('時間');
ylabel('幅值');
title('原始信號');
grid on;
subplot(222);
y=fft(x, N); %對信號 Mix_Signal做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, 0.25); %28階FIR低通濾波器,截止頻率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可以看出,200Hz的正弦波基本被濾除,
37.6 實驗例程說明(MDK)
配套例子:
V5-225_FIR低通濾波器設計(支持逐個資料的實時濾波)
實驗目的:
- 學習FIR低通濾波器的實作,支持實時濾波
實驗內容:
- 啟動一個自動重裝軟體定時器,每100ms翻轉一次LED2,
- 按下按鍵K1,列印原始波形資料和濾波后的波形資料,
使用AC6注意事項
特別注意附件章節C的問題
上電后串口列印的資訊:
波特率 115200,資料位 8,奇偶校驗位無,停止位 1,

RTT方式列印資訊:

程式設計:
系統堆疊大小分配:

硬體外設初始化
硬體外設的初始化是在 bsp.c 檔案實作:
/*
*********************************************************************************************************
* 函 數 名: bsp_Init
* 功能說明: 初始化所有的硬體設備,該函式配置CPU暫存器和外設的暫存器并初始化一些全域變數,只需要呼叫一次
* 形 參:無
* 返 回 值: 無
*********************************************************************************************************
*/
void bsp_Init(void)
{
/*
STM32F407 HAL 庫初始化,此時系統用的還是F407自帶的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_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); /* 翻轉LED的狀態 */
}
ucKeyCode = bsp_GetKey(); /* 讀取鍵值, 無鍵按下時回傳 KEY_NONE = 0 */
if (ucKeyCode != KEY_NONE)
{
switch (ucKeyCode)
{
case KEY_DOWN_K1: /* K1鍵按下 */
arm_fir_f32_lp();
break;
default:
/* 其它的鍵值不處理 */
break;
}
}
}
}
37.7 實驗例程說明(IAR)
配套例子:
V5-225_FIR低通濾波器設計(支持逐個資料的實時濾波)
實驗目的:
- 學習FIR低通濾波器的實作,支持實時濾波
實驗內容:
- 啟動一個自動重裝軟體定時器,每100ms翻轉一次LED2,
- 按下按鍵K1,列印原始波形資料和濾波后的波形資料,
上電后串口列印的資訊:
波特率 115200,資料位 8,奇偶校驗位無,停止位 1,

RTT方式列印資訊:

程式設計:
系統堆疊大小分配:

硬體外設初始化
硬體外設的初始化是在 bsp.c 檔案實作:
/*
*********************************************************************************************************
* 函 數 名: bsp_Init
* 功能說明: 初始化所有的硬體設備,該函式配置CPU暫存器和外設的暫存器并初始化一些全域變數,只需要呼叫一次
* 形 參:無
* 返 回 值: 無
*********************************************************************************************************
*/
void bsp_Init(void)
{
/*
STM32F407 HAL 庫初始化,此時系統用的還是F407自帶的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_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); /* 翻轉LED的狀態 */
}
ucKeyCode = bsp_GetKey(); /* 讀取鍵值, 無鍵按下時回傳 KEY_NONE = 0 */
if (ucKeyCode != KEY_NONE)
{
switch (ucKeyCode)
{
case KEY_DOWN_K1: /* K1鍵按下 */
arm_fir_f32_lp();
break;
default:
/* 其它的鍵值不處理 */
break;
}
}
}
}
37.8 總結
本章節主要講解了FIR濾波器的低通實作,同時一定要注意線性相位FIR濾波器的群延遲問題,詳見本教程的第41章,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/292227.html
標籤:其他
