快速傅里叶变换,IIR数字滤波器使用教程

快速傅里叶变换,IIR数字滤波器使用教程

前置步骤

配置编译环境

img

  • 进入“管理运行环境”
  • 保证你的ARMCC版本为五代,并使用微库
    img
  • 进入CMSIS,勾选CORE,DSP
    img

img

  • 由于后续涉及到的头文件过多,所以需要开启On优化,否则会导致编译结果过大无法下载至烧录器
  • 另外需要在宏定义中写入以下内容,否则DSP库中部分函数无法启用:
USE_STDPERIPH_DRIVER,STM32F407xE,ARM_MATH_CM4,ARM_MATH_MATRIX,ARM_MATH_ROUNDING

宏定义ARM_MATH_CMx中文本取决于你的设备型号,查看方式如下
img
我的设备为STM32F407VET6,内核为Cortex-M4,因此要再添加:
ARM_MATH_CM4

检查文件

  • 请再三检查你的项目里是否包含以下文件,若存在就将他们从项目系统中移除并从自己的项目文件中删除
    img

img
这几个文件已经在CMSIS中包含了,若不删除可能导致重定义

FFT

简介

  • FFT是一种DFT的高效算法,称为快速傅里叶变换(fast Fourier transform)。傅里叶变换是时域一频域变换分析中最基本的方法之一。在数字处理领域应用的离散傅里叶变换(DFT:Discrete Fourier Transform)是许多数字信号处理方法的基础

前置步骤

  • 引用以下头文件
#include "math.h"
#include "arm_math.h"
#include "arm_const_structs.h"

定义变量

  • 需要按照格式输入数据(以时域向频域变换为例)
#define NPT 1024//数据尺寸,要求为2的n次方,这里以2的10次为例
float32_t data[NPT];//数据为双精度浮点,要求为一对复数(偶数项索引值为实部,奇数项索引为虚部)
float32_t data_out[NPT/2];//用于放置频域变换后结果的数组

处理数据

  • 在完成数据的初始化后,调用以下函数
arm_cfft_f32(&arm_cfft_sR_f32_len1024,data,0,1);
arm_cmplx_mag_f32(data,data_out,NPT/2);

代码详解

傅里叶变换函数

arm_cfft_f32

/**
  @brief         浮点复数快速傅里叶变换(FFT)的处理函数
  @param[in]     S              指向CFFT结构体的指针
  @param[in,out] p1             指向大小为<code>2*fftLen</code>的复数数据缓冲区的指针。在原地址内处理数据
  @param[in]     ifftFlag       选择变换方向的标识符
                   - value = 0: 正变换(时域向频域变换)
                   - value = 1: 逆变换(频域向时域变换)
  @param[in]     bitReverseFlag 启用/禁用输出位反转的标识符
                   - value = 0: 禁用输出翻转
                   - value = 1: 启用输出翻转
  @return        none
 */

void arm_cfft_f32(
  const arm_cfft_instance_f32 * S,
        float32_t * p1,
        uint8_t ifftFlag,
        uint8_t bitReverseFlag);

CFFT结构体

  • 该参数取决与原复数函数的尺寸大小,DSP库提供的选择如下,可根据自己数据的尺寸自行选择

定义存于arm_const_structs.c

/* Floating-point structs */
#if !defined(ARM_MATH_MVEF) || defined(ARM_MATH_AUTOVECTORIZE)


#if !defined(ARM_DSP_CONFIG_TABLES) || defined(ARM_ALL_FFT_TABLES) || (defined(ARM_TABLE_TWIDDLECOEF_F32_16) && defined(ARM_TABLE_BITREVIDX_FLT_16))
const arm_cfft_instance_f32 arm_cfft_sR_f32_len16 = {
  16, twiddleCoef_16, armBitRevIndexTable16, ARMBITREVINDEXTABLE_16_TABLE_LENGTH
};
#endif


#if !defined(ARM_DSP_CONFIG_TABLES) || defined(ARM_ALL_FFT_TABLES) || (defined(ARM_TABLE_TWIDDLECOEF_F32_32) && defined(ARM_TABLE_BITREVIDX_FLT_32))
const arm_cfft_instance_f32 arm_cfft_sR_f32_len32 = {
  32, twiddleCoef_32, armBitRevIndexTable32, ARMBITREVINDEXTABLE_32_TABLE_LENGTH
};
#endif

#if !defined(ARM_DSP_CONFIG_TABLES) || defined(ARM_ALL_FFT_TABLES) || (defined(ARM_TABLE_TWIDDLECOEF_F32_64) && defined(ARM_TABLE_BITREVIDX_FLT_64))
const arm_cfft_instance_f32 arm_cfft_sR_f32_len64 = {
  64, twiddleCoef_64, armBitRevIndexTable64, ARMBITREVINDEXTABLE_64_TABLE_LENGTH
};
#endif

#if !defined(ARM_DSP_CONFIG_TABLES) || defined(ARM_ALL_FFT_TABLES) || (defined(ARM_TABLE_TWIDDLECOEF_F32_128) && defined(ARM_TABLE_BITREVIDX_FLT_128))
const arm_cfft_instance_f32 arm_cfft_sR_f32_len128 = {
  128, twiddleCoef_128, armBitRevIndexTable128, ARMBITREVINDEXTABLE_128_TABLE_LENGTH
};
#endif

#if !defined(ARM_DSP_CONFIG_TABLES) || defined(ARM_ALL_FFT_TABLES) || (defined(ARM_TABLE_TWIDDLECOEF_F32_256) && defined(ARM_TABLE_BITREVIDX_FLT_256))
const arm_cfft_instance_f32 arm_cfft_sR_f32_len256 = {
  256, twiddleCoef_256, armBitRevIndexTable256, ARMBITREVINDEXTABLE_256_TABLE_LENGTH
};
#endif

#if !defined(ARM_DSP_CONFIG_TABLES) || defined(ARM_ALL_FFT_TABLES) || (defined(ARM_TABLE_TWIDDLECOEF_F32_512) && defined(ARM_TABLE_BITREVIDX_FLT_512))
const arm_cfft_instance_f32 arm_cfft_sR_f32_len512 = {
  512, twiddleCoef_512, armBitRevIndexTable512, ARMBITREVINDEXTABLE_512_TABLE_LENGTH
};
#endif

#if !defined(ARM_DSP_CONFIG_TABLES) || defined(ARM_ALL_FFT_TABLES) || (defined(ARM_TABLE_TWIDDLECOEF_F32_1024) && defined(ARM_TABLE_BITREVIDX_FLT_1024))
const arm_cfft_instance_f32 arm_cfft_sR_f32_len1024 = {
  1024, twiddleCoef_1024, armBitRevIndexTable1024, ARMBITREVINDEXTABLE_1024_TABLE_LENGTH
};
#endif

#if !defined(ARM_DSP_CONFIG_TABLES) || defined(ARM_ALL_FFT_TABLES) || (defined(ARM_TABLE_TWIDDLECOEF_F32_2048) && defined(ARM_TABLE_BITREVIDX_FLT_2048))
const arm_cfft_instance_f32 arm_cfft_sR_f32_len2048 = {
  2048, twiddleCoef_2048, armBitRevIndexTable2048, ARMBITREVINDEXTABLE_2048_TABLE_LENGTH
};
#endif

#if !defined(ARM_DSP_CONFIG_TABLES) || defined(ARM_ALL_FFT_TABLES) || (defined(ARM_TABLE_TWIDDLECOEF_F32_4096) && defined(ARM_TABLE_BITREVIDX_FLT_4096))
const arm_cfft_instance_f32 arm_cfft_sR_f32_len4096 = {
  4096, twiddleCoef_4096, armBitRevIndexTable4096, ARMBITREVINDEXTABLE_4096_TABLE_LENGTH
};

复数取模函数

  • 由于变换结果依旧为复数,人类难以阅读,所以需要使用取模函数计算绝对值
/**
  @brief         取模浮点数
  @param[in]     pSrc        输入的复数数据
  @param[out]    pDst        输出的取模结果
  @param[in]     numSamples  数据的样本数
  @return        none
 */
 void arm_cmplx_mag_f32(
  const float32_t * pSrc,
        float32_t * pDst,
        uint32_t numSamples);

IIR

简介

  • “递归滤波器”。递归滤波器,也就是IIR数字滤波器,顾名思义,具有反馈。

前置步骤

  • 引用以下头文件
#include <math.h>
#include "arm_math.h"
#include "arm_const_structs.h"
  • 定义以下数据
// 以Fs=100k, Fc=20k, 4阶 Butterworth为例
#define NPT 1024//数据尺寸为1024 
#define IIR_NUM_STAGES 2//2结节
float32_t filtered_data[NPT];//存放滤波后数据的数组
float32_t data[NPT];//存放原始数据的数组
//存放滤波器参数的数组
const float iir_coeffs[5 * IIR_NUM_STAGES] = {
    // Section 1
    0.136728f, 0.273456f, 0.136728f,
    0.451286f, -0.234616f,

    // Section 2
    0.136728f, 0.273456f, 0.136728f,
    0.451286f, -0.234616f
};
arm_biquad_casd_df1_inst_f32 iir_filter;//IIR滤波器结构体

调用函数

arm_biquad_cascade_df1_init_f32

  • 初始化IIR滤波器结构体
arm_biquad_cascade_df1_init_f32(
    &iir_filter,
    IIR_NUM_STAGES,
    (float32_t *)iir_coeffs,
    iir_state
);

arm_biquad_cascade_df1_f32

  • 进行滤波
arm_biquad_cascade_df1_f32(
    &iir_filter,
    data,
    filtered_data,
    NPT
);

代码详解

arm_biquad_casd_df1_inst_f32

存于filter_functions.h

  /**
   * @brief 浮点双二阶级联滤波器的结构体
   */
  typedef struct
  {
          uint32_t numStages;      /**< 二阶节数量 */
          float32_t *pState;       /**< 指向状态数组. */
    const float32_t *pCoeffs;      /**< 指向系数数组 */
  } arm_biquad_casd_df1_inst_f32;

arm_biquad_cascade_df1_init_f32

/**
  @brief         初始化浮点双二阶级联滤波器的结构体
  @param[in,out] S           指向浮点双二阶级联滤波器的结构体的指针
  @param[in]     numStages   浮点双二阶级联滤波器的结构体的阶数
  @param[in]     pCoeffs     指向系数数组
  @param[in]     pState      指向状态数组
  @return        none

  @par           系数与状态排序 系数按以下顺序存储在数组<code>pCoeffs</code>中:
  <pre>
      {b10, b11, b12, a11, a12, b20, b21, b22, a21, a22, ...}
  </pre>

  @par
                   其中<code>b1x</code>和<code>a1x</code>是第一阶段的系数, 
 <code>b2x</code>和<code>a2x</code>是第二阶段的系数, 
 以此类推。<code>pCoeffs</code>数组总共包含<code>5*numStages</code>个值。
  @par
                   <code>pState</code>是指向状态数组的指针。 
 每个双二阶环节都有4个状态变量:<code>x[n-1]</code>、<code>x[n-2]</code>、<code>y[n-1]</code>和<code>y[n-2]</code>。 
 状态变量在<code>pState</code>数组中的排列方式如下
  <pre>
      {x[n-1], x[n-2], y[n-1], y[n-2]}
  </pre>
                   首先是第一阶段的4个状态变量,然后是第二阶段的4个状态变量,以此类推。 
 状态数组的总长度为<code>4*numStages</code>个值。 
 在处理完每个数据块后,状态变量会被更新;系数则保持不变。
 
  @par             对于MVE代码,需要额外增加一个用于存储修改后系数的缓冲区。 
 其大小为numStages,且该缓冲区的每个元素类型均为arm_biquad_mod_coef_f32。 
 因此,其总大小为32乘以numStages个float32_t元素。 
 必须使用的初始化函数是arm_biquad_cascade_df1_mve_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);

arm_biquad_cascade_df1_f32

/**
  @brief         浮点双二阶级联滤波器的处理函数
  @param[in]     S         指向浮点双二阶级联滤波器的结构体的指针
  @param[in]     pSrc      指向输入数组的指针
  @param[out]    pDst      指向输出数组的指针
  @param[in]     blockSize  数据的样本数量
  @return        none
 */
 void arm_biquad_cascade_df1_f32(
  const arm_biquad_casd_df1_inst_f32 * S,
  const float32_t * pSrc,
        float32_t * pDst,
        uint32_t blockSize);

脚本

  • 由于系数的计算过于复杂,所以我写了一个python脚本,可按照需求生成对应系数数据
import numpy as np
from scipy import signal
import sys

def generate_cmsis_iir():
    """交互式生成CMSIS-DSP兼容的IIR滤波器系数"""
    print("="*60)
    print("STM32 CMSIS-DSP IIR滤波器系数生成工具")
    print("支持类型:1.低通(LPF) 2.高通(HPF) 3.带通(BPF) 4.带阻(BSF)")
    print("="*60)

    # -------------------------- 1. 选择滤波器类型 --------------------------
    type_map = {
        1: ("lowpass", "低通(LPF)"),
        2: ("highpass", "高通(HPF)"),
        3: ("bandpass", "带通(BPF)"),
        4: ("bandstop", "带阻(BSF)")
    }
    while True:
        try:
            ftype_idx = int(input("请选择滤波器类型 (1-4): "))
            if ftype_idx in type_map:
                ftype_str, ftype_name = type_map[ftype_idx]
                break
            else:
                print("输入无效,请输入1-4之间的数字")
        except ValueError:
            print("输入无效,请输入数字")

    # -------------------------- 2. 输入基础参数 --------------------------
    while True:
        try:
            fs = float(input(f"请输入采样率Fs (Hz,你的项目是100000): "))
            if fs <= 0:
                print("采样率必须大于0")
                continue
            break
        except ValueError:
            print("输入无效,请输入数字")

    while True:
        try:
            order = int(input(f"请输入滤波器阶数 (建议2/4/6/8,默认4): ") or "4")
            if order < 2:
                print("阶数至少为2")
                continue
            break
        except ValueError:
            print("输入无效,请输入整数")

    # -------------------------- 3. 输入频率参数 --------------------------
    nyq = fs / 2  # 奈奎斯特频率
    while True:
        try:
            if ftype_idx in [1, 2]:  # 低通/高通只需要一个截止频率
                fc = float(input(f"请输入截止频率Fc (Hz,需小于{nyq:.1f}): "))
                if 0 < fc < nyq:
                    freq_param = fc
                    break
                else:
                    print(f"截止频率必须在0~{nyq:.1f}Hz之间")
            else:  # 带通/带阻需要两个频率
                flo = float(input(f"请输入低频截止Flo (Hz,需小于{nyq:.1f}): "))
                fhi = float(input(f"请输入高频截止Fhi (Hz,需大于Flo且小于{nyq:.1f}): "))
                if 0 < flo < fhi < nyq:
                    freq_param = [flo, fhi]
                    break
                else:
                    print(f"频率必须满足:0 < Flo < Fhi < {nyq:.1f}Hz")
        except ValueError:
            print("输入无效,请输入数字")

    # -------------------------- 4. 生成SOS系数(核心步骤) --------------------------
    print("\n正在生成滤波器系数...")
    try:
        # 生成级联二阶节(SOS)格式系数,这是CMSIS-DSP最稳定的格式
        sos = signal.butter(
            order,
            freq_param,
            btype=ftype_str,
            analog=False,
            fs=fs,
            output='sos'
        )
    except Exception as e:
        print(f"系数生成失败:{e}")
        sys.exit(1)

    n_sections = sos.shape[0]  # 二阶节的数量,等于 ceil(阶数/2)
    print(f"生成成功!共{n_sections}个二阶节,请将IIR_NUM_STAGES定义为{n_sections}")

    # -------------------------- 5. 转换为CMSIS-DSP格式 --------------------------
    # SciPy SOS格式:[b0, b1, b2, a0, a1, a2],其中a0恒为1
    # CMSIS-DSP格式:每个二阶节按 [b0, b1, b2, -a1, -a2] 排列(注意a1/a2取反!)
    cmsis_coeffs = []
    for i in range(n_sections):
        b0, b1, b2 = sos[i, 0], sos[i, 1], sos[i, 2]
        a1, a2 = sos[i, 4], sos[i, 5]
        cmsis_coeffs.extend([b0, b1, b2, -a1, -a2])  # 关键:a1/a2取反

    # -------------------------- 6. 输出C语言数组 --------------------------
    print("\n" + "="*60)
    print("复制到你的C代码中的内容:")
    print("="*60)

    # 输出宏定义
    print(f"/* {ftype_name} 滤波器参数:Fs={fs}Hz, 阶数={order}")
    if ftype_idx in [1,2]:
        print(f"   截止频率={freq_param}Hz */")
    else:
        print(f"   通带/阻带:{freq_param[0]}Hz - {freq_param[1]}Hz */")
    
    print(f"#define IIR_NUM_STAGES {n_sections}")
    print(f"#define IIR_COEFF_LEN  (5 * IIR_NUM_STAGES)")
    print("\n/* CMSIS-DSP IIR系数数组(已自动处理a1/a2符号) */")
    print("const float iir_coeffs[IIR_COEFF_LEN] = {")
    
    # 按二阶节分组打印,方便阅读
    for i in range(n_sections):
        start = i * 5
        end = start + 5
        coeff_str = ", ".join([f"{x:.6f}f" for x in cmsis_coeffs[start:end]])
        if i == n_sections -1:
            print(f"    {coeff_str}  // Section {i+1}")
        else:
            print(f"    {coeff_str}, // Section {i+1}")
    print("};")

    # 输出状态数组定义提示
    print(f"\n/* 别忘了定义状态数组(大小必须为4*IIR_NUM_STAGES) */")
    print(f"float iir_state[4 * IIR_NUM_STAGES];")

    # 输出初始化代码示例
    print("\n/* 初始化滤波器(放在main函数初始化部分) */")
    print("arm_biquad_cascade_df1_init_f32(")
    print("    &iir_filter,")
    print("    IIR_NUM_STAGES,")
    print("    (float32_t*)iir_coeffs,")
    print("    iir_state")
    print(");")

    # -------------------------- 7. 绘制幅频响应(可选) --------------------------
    try:
        import matplotlib.pyplot as plt
        plot_flag = input("\n是否绘制幅频响应图?(Y/n,默认Y): ").lower() or "y"
        if plot_flag == "y":
            # 计算频率响应
            w, h = signal.sosfreqz(sos, worN=2000, fs=fs)
            # 转换为dB
            h_db = 20 * np.log10(np.maximum(abs(h), 1e-10))
            
            plt.figure(figsize=(10, 6))
            plt.plot(w, h_db, linewidth=2)
            plt.title(f"{ftype_name} 幅频响应 (Fs={fs}Hz, Order={order})")
            plt.xlabel("频率 (Hz)")
            plt.ylabel("幅值 (dB)")
            plt.grid(True, alpha=0.7)
            plt.axhline(-3, color='r', linestyle='--', label="-3dB点")
            
            # 标记关键频率
            if ftype_idx in [1,2]:
                plt.axvline(freq_param, color='g', linestyle='--', label=f"Fc={freq_param}Hz")
            else:
                plt.axvline(freq_param[0], color='g', linestyle='--', label=f"Flo={freq_param[0]}Hz")
                plt.axvline(freq_param[1], color='g', linestyle='--', label=f"Fhi={freq_param[1]}Hz")
            
            plt.axvline(nyq, color='gray', linestyle=':', label=f"奈奎斯特频率({nyq}Hz)")
            plt.legend()
            plt.tight_layout()
            plt.show()
    except ImportError:
        print("\n未安装matplotlib,跳过绘图。如需绘图请运行:pip install matplotlib")
    except Exception as e:
        print(f"\n绘图失败:{e}")

if __name__ == "__main__":
    generate_cmsis_iir()
posted @ 2026-07-15 01:35  奶龙大王  阅读(47)  评论(0)    收藏  举报