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

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

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

第46章 STM32F407的IIR帶通濾波器實現(支援逐個資料的實時濾波)

本章節講解IIR帶通濾波器實現。

46.1 初學者重要提示

46.2 帶通濾波器介紹

46.3 IIR濾波器介紹

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

46.5 IIR帶通濾波器設計

46.6 實驗例程說明(MDK)

46.7 實驗例程說明(IAR)

46.8 總結

46.1 初學者重要提示

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

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

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

46.2 帶通濾波器介紹

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

46.3 IIR濾波器介紹

ARM官方提供的直接I型IIR庫支援Q7,Q15,Q31和浮點四種資料型別。其中Q15和Q31提供了快速版本。

直接I型IIR濾波器是基於二階Biquad級聯的方式來實現的。每個Biquad由一個二階的濾波器組成:

y[n] = b0 * x[n] + b1 * x[n-1] + b2 * x[n-2] + a1 * y[n-1] + a2 * y[n-2]

直接I型演算法每個階段需要5個係數和4個狀態變數。

這裡有一點要特別的注意,有些濾波器係數生成工具是採用的下面公式實現:

y[n] = b0 * x[n] + b1 * x[n-1] + b2 * x[n-2] - a1 * y[n-1] - a2 * y[n-2]

比如matlab就是使用上面的公式實現的,所以在使用fdatool工具箱生成的a係數需要取反才能用於直接I型IIR濾波器的函式中。

高階IIR濾波器的實現是採用二階Biquad級聯的方式來實現的。其中引數numStages就是用來做指定二階Biquad的個數。比如8階IIR濾波器就可以採用numStages=4個二階Biquad來實現。

如果要實現9階IIR濾波器就需要將numStages=5,這時就需要其中一個Biquad配置成一階濾波器(也就是b2=0,a2=0)。

46.4 Matlab工具箱filterDesigner生成IIR帶通濾波器係數

前面介紹FIR濾波器的時候,我們講解了如何使用filterDesigner生成C標頭檔案,從而獲得濾波器係數。這裡不能再使用這種方法了,主要是因為通過C標頭檔案獲取的濾波器係數需要通過ARM官方的IIR函式呼叫多次才能獲得濾波結果,所以我們這裡換另外一種方法。

下面我們講解如何通過filterDesigner工具箱生成濾波器係數。首先在matlab的命令視窗輸入filterDesigner就能開啟這個工具箱:

filterDesigner介面開啟效果如下:

IIR濾波器的低通,高通,帶通,帶阻濾波的設定會在下面一 一講解,這裡說一下設定後相應引數後如何生成濾波器係數。引數設定好以後點選如下按鈕:

點選Design Filter之後,注意左上角生成的濾波器結構:

預設生成的IIR濾波器型別是Direct-Form II, Second-Order Sections(直接II型,每個Section是一個二階濾波器)。這裡我們需要將其轉換成Direct-Form I, Second-Order Sections,因為本章使用的IIR濾波器函式是Direct-Form I的結構。

轉換方法,點選Edit->Convert Structure,介面如下,這裡我們選擇第一項,並點選OK:

轉換好以後再點選File-Export,第一項選擇Coefficient File(ASCII):

第一項選擇好以後,第二項選擇Decimal:

兩個選項都選擇好以後,點選Export進行匯出,匯出後儲存即可:

儲存後Matlab會自動開啟untitled.fcf檔案,可以看到生成的係數:

% Generated by MATLAB(R) 9.4 and Signal Processing Toolbox 8.0.
% Generated on: 15-Aug-2021 22:20:26

% Coefficient Format: Decimal

% Discrete-Time IIR Filter (real)                                                                 
% -------------------------------                                                                 
% Filter Structure    : Direct-Form I, Second-Order Sections                                      
% Number of Sections  : 2                                                                         
% Stable              : Yes                                                                       
% Linear Phase        : No                                                                        

                                                                                                 
SOS Matrix:                                                                                       
1  0  -1  1   1.127651872054164616798743736580945551395  0.470013145087532668853214090631809085608
1  0  -1  1  -0.774953058046049081397654845204669982195  0.367077500556684199750634434167295694351
                                                                                                  
Scale Values:                                                                                     
0.558156585760773649163013487850548699498                                                         
0.558156585760773649163013487850548699498                                                         

由於前面選擇的是4階IIR濾波,生成的結果就是由兩組二階IIR濾波係數組成,係數的對應順序如下:

SOS Matrix:                                                  
1   2   1   1   1.127651872054164616798743736580945551395  0.470013145087532668853214090631809085608        
b0  b1  b2  a0          a1                                                   a2
1    2   1   1   -0.774953058046049081397654845204669982195  0.367077500556684199750634434167295694351        
b0  b1  b2  a0        a1                                                     a2

注意,實際使用ARM官方的IIR函式呼叫的時候要將a1和a2取反。另外下面兩組是每個二階濾波器的增益,濾波後的結果要乘以這兩個增益數值才是實際結果:

0.558156585760773649163013487850548699498                                                         
0.558156585760773649163013487850548699498    

實際的濾波係數呼叫方法,看下面的例子即可。

46.5 IIR帶通濾波器設計

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

46.5.1 函式arm_biquad_cascade_df1_init_f32

函式原型:

void arm_biquad_cascade_df1_init_f32(
        arm_biquad_casd_df1_inst_f32 * S,
        uint8_t numStages,
  const float32_t * pCoeffs,
        float32_t * pState)

函式描述:

這個函式用於IIR初始化。

函式引數:

  • 第1個引數是arm_biquad_casd_df1_inst_f32型別結構體變數。
  • 第2個引數是2階濾波器的個數。
  • 第3個引數是濾波器係數地址。
  • 第4個引數是緩衝狀態地址。

注意事項:

結構體arm_biquad_casd_df1_inst_f32的定義如下(在檔案filtering_functions.h檔案):

typedef struct
{
  uint32_t numStages;      /**< number of 2nd order stages in the filter.  Overall order is 2*numStages. */
 float32_t *pState; /**< Points to the array of state coefficients.  The array is of length 4*numStages. */
 const float32_t *pCoeffs; /**< Points to the array of coefficients.  The array is of length 5*numStages */
} arm_biquad_casd_df1_inst_f32;
  1. numStages表示二階濾波器的個數,總階數是2*numStages。
  2. pState指向狀態變數陣列,這個陣列用於函式內部計算資料的快取,總大小4*numStages。
  3. 引數pCoeffs指向濾波因數,濾波因數陣列長度為5*numStages。但要注意pCoeffs指向的濾波因數應該按照如下的逆序進行排列:

{b10, b11, b12, a11, a12, b20, b21, b22, a21, a22, ...}

先放第一個二階Biquad係數,然後放第二個,以此類推。

46.5.2 函式arm_biquad_cascade_df1_f32

函式定義如下:

void arm_biquad_cascade_df1_f32(
      const arm_biquad_casd_df1_inst_f32 * S,
      float32_t * pSrc,
      float32_t * pDst,
      uint32_t blockSize)

函式描述:

這個函式用於IIR濾波。

函式引數:

  • 第1個引數是arm_biquad_casd_df1_inst_f32型別結構體變數。
  • 第2個引數是源資料地址。
  • 第3個引數是濾波後的資料地址。
  • 第4個引數是每次呼叫處理的資料個數,最小可以每次處理1個數據,最大可以每次全部處理完。

46.5.3 filterDesigner獲取帶通濾波器係數

設計一個如下的例子:

訊號由50Hz正弦波和200Hz正弦波組成,取樣率1Kbps,現設計一個巴特沃斯濾波器帶通濾波器,採用直接I型,截止頻率140Hz和400,取樣400個數據,濾波器階數設定為4。filterDesigner的配置如下:

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

46.5.4 帶通濾波器實現

通過工具箱filterDesigner獲得帶通濾波器係數後在開發板上執行函式arm_biquad_cascade_df1_f32來測試低通濾波器的效果。

#define numStages  2                /* 2階IIR濾波的個數 */
#define TEST_LENGTH_SAMPLES  400    /* 取樣點數 */
#define BLOCK_SIZE           1      /* 呼叫一次arm_biquad_cascade_df1_f32處理的取樣點個數 */


uint32_t blockSize = BLOCK_SIZE;
uint32_t numBlocks = TEST_LENGTH_SAMPLES/BLOCK_SIZE;       /* 需要呼叫arm_biquad_cascade_df1_f32的次數 */


static float32_t testInput_f32_50Hz_200Hz[TEST_LENGTH_SAMPLES]; /* 取樣點 */
static float32_t testOutput[TEST_LENGTH_SAMPLES];               /* 濾波後的輸出 */
static float32_t IIRStateF32[4*numStages];                      /* 狀態快取 */
      
/* 巴特沃斯帶通濾波器係數140Hz 400Hz*/                                                                                                                                         
const float32_t IIRCoeffs32BP[5*numStages] = {
    1.0f,  0.0f,  -1.0f,     -1.127651872054164616798743736580945551395f,  
-0.470013145087532668853214090631809085608f,      
    1.0f,  0.0f,  -1.0f,     0.774953058046049081397654845204669982195f,  
-0.367077500556684199750634434167295694351f                               
};                                              

/*
*********************************************************************************************************
*    函 數 名: arm_iir_f32_bp
*    功能說明: 呼叫函式arm_iir_f32_bp實現帶通濾波器
*    形    參:無
*    返 回 值: 無
*********************************************************************************************************
*/
static void arm_iir_f32_bp(void)
{
    uint32_t i;
    arm_biquad_casd_df1_inst_f32 S;
    float32_t ScaleValue;
    float32_t  *inputF32, *outputF32;
    
    /* 初始化輸入輸出快取指標 */
    inputF32 = &testInput_f32_50Hz_200Hz[0];
    outputF32 = &testOutput[0];
    
    
    /* 初始化 */
    arm_biquad_cascade_df1_init_f32(&S, numStages, (float32_t *)&IIRCoeffs32BP[0], 
(float32_t *)&IIRStateF32[0]);
    
    
    /* 實現IIR濾波,這裡每次處理1個點 */
    for(i=0; i < numBlocks; i++)
    {
        arm_biquad_cascade_df1_f32(&S, inputF32 + (i * blockSize),  outputF32 + (i * blockSize), 
 blockSize);
    }
            
    /*放縮係數 */
    ScaleValue = 0.558156585760773649163013487850548699498f * 0.558156585760773649163013487850548699498f; 
    
    /* 列印濾波後結果 */
    for(i=0; i<TEST_LENGTH_SAMPLES; i++)
    {
        printf("%f, %f\r\n", testInput_f32_50Hz_200Hz[i], testOutput[i]*ScaleValue);
    }
}

執行如上函式可以通過串列埠打印出函式arm_biquad_cascade_df1_f32濾波後的波形資料,下面通過Matlab繪製波形來對比Matlab計算的結果和ARM官方庫計算的結果。

對比前需要先將串列埠打印出的一組資料載入到Matlab中, arm_biquad_cascade_df1_f32的計算結果起名sampledata,載入方法在第13章13.6小結已經講解,這裡不做贅述了。Matlab中執行的程式碼如下:

fs=1000;            %設定取樣頻率 1K
N=400;               %取樣點數      
n=0:N-1;
t=n/fs;                %時間序列
f=n*fs/N;              %頻率序列

x1=sin(2*pi*50*t);
x2=sin(2*pi*200*t);     %50Hz和200Hz正弦波
subplot(211);
plot(t, x1);
title('濾波後的理想波形');
grid on;

subplot(212);
plot(t, sampledata);
title('ARM官方庫濾波後的波形');
grid on;

Matlab計算結果如下:

從上面的波形對比來看,matlab和函式arm_biquad_cascade_df1_f32計算的結果基本是一致的。為了更好的說明濾波效果,下面從頻域的角度來說明這個問題,Matlab上面執行如下程式碼:

fs=1000;                %設定取樣頻率 1K
N=400;                 %取樣點數      
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(211);
y=fft(x, N);                %對訊號x做FFT   
plot(f,abs(y));
xlabel('頻率/Hz');
ylabel('振幅');
title('原始訊號FFT');
grid on;

y3=fft(sampledata, N);    %經過IIR濾波器後得到的訊號做FFT
subplot(212);                               
plot(f,abs(y3));
xlabel('頻率/Hz');
ylabel('振幅');
title('IIR濾波後訊號FFT');
grid on;

Matlab計算結果如下:

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

46.6 實驗例程說明(MDK)

配套例子:

V5-231_IIR帶通濾波器(支援逐點實時濾波)

實驗目的:

  1. 學習IIR帶通濾波器的實現,支援實時濾波

實驗內容:

  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);    /* 翻轉LED的狀態 */
        }
        
        ucKeyCode = bsp_GetKey();    /* 讀取鍵值, 無鍵按下時返回 KEY_NONE = 0 */
        if (ucKeyCode != KEY_NONE)
        {
            switch (ucKeyCode)
            {
                case KEY_DOWN_K1:            /* K1鍵按下 */
                    arm_iir_f32_bp();
                    break;
                
    
                default:
                    /* 其它的鍵值不處理 */
                    break;
            }
        }

    }
}

46.7 實驗例程說明(IAR)

配套例子:

V5-231_IIR帶通濾波器(支援逐點實時濾波)

實驗目的:

  1. 學習IIR帶通濾波器的實現,支援實時濾波

實驗內容:

  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);    /* 翻轉LED的狀態 */
        }
        
        ucKeyCode = bsp_GetKey();    /* 讀取鍵值, 無鍵按下時返回 KEY_NONE = 0 */
        if (ucKeyCode != KEY_NONE)
        {
            switch (ucKeyCode)
            {
                case KEY_DOWN_K1:            /* K1鍵按下 */
                    arm_iir_f32_bp();
                    break;
                
    
                default:
                    /* 其它的鍵值不處理 */
                    break;
            }
        }

    }
}

46.8 總結

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

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