1. 程式人生 > 其它 >【STM32F407的DSP教程】第40章 STM32F407的FIR帶阻濾波器實現(支援逐個資料的實時濾波)

【STM32F407的DSP教程】第40章 STM32F407的FIR帶阻濾波器實現(支援逐個資料的實時濾波)

完整版教程下載地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=94547

第40章 STM32F407的FIR帶阻濾波器實現(支援逐個資料的實時濾波)

本本章節講解FIR帶阻濾波器實現。

40.1 初學者重要提示

40.2 帶阻濾波器介紹

40.3 FIR濾波器介紹

40.4 Matlab工具箱filterDesigner生成帶阻濾波器C標頭檔案

40.5 FIR帶阻濾波器設計

40.6 實驗例程說明(MDK)

40.7 實驗例程說明(IAR)

40.8 總結

40.1 初學者重要提示

1、 本章節提供的帶阻濾波器支援實時濾波,每次可以濾波一個數據,也可以多個數據,不限制大小。但要注意以下兩點:

  • 所有資料是在同一個取樣率下依次採集的資料。
  • 每次過濾資料個數一旦固定下來,執行中不可再修改。

2、 FIR濾波器的群延遲是一個重要的知識點,詳情在本教程第41章有詳細說明。

40.2 帶阻濾波器介紹

減弱一個範圍內的頻率訊號通過,讓範圍之外的頻率訊號通過。比如混合訊號含有50Hz + 200Hz + 400Hz訊號,我們可通過帶通濾波器,讓50Hz + 400Hz訊號通過,而阻止200Hz訊號通過。

40.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小節所講的直接型結構。

40.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開啟幫助文件進行學習。

40.5 FIR帶通濾波器設計

本章使用的FIR濾波器函式是arm_fir_f32。使用此函式可以設計FIR低通,高通,帶通和帶阻

濾波器。

40.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個數據,最大可以每次全部處理完。

40.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個數據,最大可以每次全部處理完。

40.5.3 filterDesigner獲取帶阻濾波器係數

設計一個如下的例子:

訊號由50Hz正弦波和200Hz正弦波組成,取樣率1Kbps,現設計一個帶阻濾波器,截止頻率125Hz和300Hz,取樣1024個數據,採用函式fir1進行設計(注意這個函式是基於視窗的方法設計FIR濾波,預設是hamming窗),濾波器階數設定為28。filterDesigner的配置如下:

配置好帶阻濾波器後,具體濾波器係數的生成大家參考本章第4小節的方法即可。

40.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 firCoeffs32BS[NUM_TAPS] = {
    -0.003560454352f,  -0.0002683042258f,  0.001964005642f,   -0.001277366537f,   0.008085897192f,
    0.02002927102f,    -0.01026879996f,    -0.03190089762f,   -0.001673383522f,   -0.0334023945f,
    -0.06278027594f,   0.1240097657f,      0.2419839799f,     -0.07700803876f,    0.6521340013f,
    -0.07700803876f,   0.2419839799f,      0.1240097657f,     -0.06278027594f,    -0.0334023945f,
    -0.001673383522f,  -0.03190089762f,    -0.01026879996f,   0.02002927102f,     0.008085897192f,
    -0.001277366537f,  0.001964005642f,    -0.0002683042258f, -0.003560454352f
};

/*
*********************************************************************************************************
*    函 數 名: 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 *)&firCoeffs32BS[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=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, [125/500 300/500], 'stop');   %獲得濾波器係數,截止頻率125Hz和300,帶阻濾波。
y=filter(b, 1, x);                        %獲得濾波後的波形
subplot(211);
plot(t, y);
title('Matlab FIR濾波後的實際波形');
grid on;

subplot(212);
plot(t, sampledata);        %繪製ARM官方庫濾波後的波形。
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);   %繪製訊號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 300/500], 'stop');  %獲得濾波器係數,截止頻率125Hz和300Hz,帶阻濾波。     
[H,F]=freqz(b,1,160);                  %通過fir1設計的FIR系統的頻率響應
subplot(224);
plot(F/pi,abs(H));             %繪製幅頻響應
xlabel('歸一化頻率');        
title(['Order=',int2str(28)]);
grid on;

Matlab顯示效果如下:

上面波形變換前的FFT和變換後FFT可以看出,200Hz的正弦波基本被濾除。

40.6 實驗例程說明(MDK)

配套例子:

V5-228_FIR帶阻濾波器設計(支援逐個資料的實時濾波)

實驗目的:

  1. 學習FIR帶阻濾波器的實現,支援實時濾波

實驗內容:

  1. 啟動一個自動重灌軟體定時器,每100ms翻轉一次LED2。
  2. 按下按鍵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);    /* 翻轉LED2的狀態 */
        }
        
        ucKeyCode = bsp_GetKey();    /* 讀取鍵值, 無鍵按下時返回 KEY_NONE = 0 */
        if (ucKeyCode != KEY_NONE)
        {
            switch (ucKeyCode)
            {
                case KEY_DOWN_K1:            /* K1鍵按下 */
                    arm_fir_f32_bs();
                    break;
                
    
                default:
                    /* 其它的鍵值不處理 */
                    break;
            }
        }

    }
}

40.7 實驗例程說明(IAR)

配套例子:

V5-228_FIR帶阻濾波器設計(支援逐個資料的實時濾波)

實驗目的:

  1. 學習FIR帶阻濾波器的實現,支援實時濾波

實驗內容:

  1. 啟動一個自動重灌軟體定時器,每100ms翻轉一次LED2。
  2. 按下按鍵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);    /* 翻轉LED2的狀態 */
        }
        
        ucKeyCode = bsp_GetKey();    /* 讀取鍵值, 無鍵按下時返回 KEY_NONE = 0 */
        if (ucKeyCode != KEY_NONE)
        {
            switch (ucKeyCode)
            {
                case KEY_DOWN_K1:            /* K1鍵按下 */
                    arm_fir_f32_bs();
                    break;
                
    
                default:
                    /* 其它的鍵值不處理 */
                    break;
            }
        }

    }
}

40.8 總結

本章節主要講解了FIR濾波器的帶阻實現,同時一定要注意線性相位FIR濾波器的群延遲問題,詳見本教程的第41章。

微信公眾號:armfly_com 安富萊論壇:www.armbbs.cn 安富萊淘寶:https://armfly.taobao.com