一、问题引入
在进行数字信号处理时,我遇到了一个困惑:对于同一个100Hz的信号,使用不同的采样率采集会得到不同数量的采样点。那么,FIR滤波器是如何处理这种情况的?具体来说:
-
如果使用1MHz采样率采集100Hz信号:
1秒钟采集1,000,000个点
一个周期采集10,000个点
-
如果使用10kHz采样率采集100Hz信号:
1秒钟采集10,000个点
一个周期采集100个点
这就引发了几个关键问题:
- 不同采样率下的数据如何影响滤波效果?
- 如何选择合适的采样率?
- FIR滤波器的参数应该如何设置?
二、相关参数
1. 归一化频率
归一化频率是将实际频率相对于奈奎斯特频率(采样频率的一半)进行标准化的结果:
归一化频率 = 实际频率/(采样频率/2)
- 取值范围:0~1
- 0对应直流分量(0Hz)
- 1对应奈奎斯特频率(Fs/2)
这里比如说我要用1000hz的频率去采信号,希望对这个信号100hz以上的频率进行滤波。设计一个这样的低通滤波器就需要计算我们的归一化截止频率。
这里的归一化截止频率就是100/(1000/2)=0.2
打开matlab 输入fdatool,即可进入数字滤波器设置界面
图片中的wc((Normalized Cutoff Frequency):归一化截止频率)就是归一化截止频率。
当然除了使用归一化截止频率也可以直接使用
设置fs( (Sampling Frequency):采样频率),fc((Cutoff Frequency):截止频率)
这里点击频率设定里的单位,可以看到
这里就可以选择归一化,或者直接设置采样频率fs,截止频率fc
根据需要选择采样频率,设置截止频率,或计算好归一化系数后点击生成滤波器即可
2.fir滤波器的阶数
典型阶数选择
| 阶数范围 | 特点 | 应用场景 |
|---------|------|---------|
| 20-40 | 资源少,延迟小 | 简单滤波 |
| 40-80 | 性能资源平衡 | 常用范围 |
| 80-256 | 窄过渡带,延迟大 | 高性能要求 |
三、将数字滤波器导出为C头文件代码
点击左上角的编辑,点击转换结构,会出现
选择直接星FIR点击确定
设置好相关参数后,点击设计滤波器
设计完成后点击左上角的目标,选择生成c头文件
保存即可
打开.h文件可以看到生成的代码
/*
* Filter Coefficients (C Source) generated by the Filter Design and Analysis Tool
* Generated by MATLAB(R) 24.1 and Signal Processing Toolbox 24.1.
* Generated on: 03-Mar-2025 17:34:57
*/
/*
* 离散时间 FIR 滤波器(实数)
* ----------------
* 滤波器结构 : 直接型 FIR 转置
* 滤波器长度 : 81
* 稳定 : 是
* 线性相位 : 是 (Type 1)
*/
/* General type conversion for MATLAB generated C-code */
#include "tmwtypes.h"
/*
* Expected path to tmwtypes.h
* D:\ranjian\Matlab2024a\matlab\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 = 81;
const real32_T B[81] = {
-1.248479932e-18,-0.0006325327558,-0.000422134588,0.0004692059301,0.0008627952193,
-1.794930437e-18,-0.001159708714,-0.0008386048721,0.0009822372813, 0.001859114505,
-3.351089814e-18,-0.002520596841,-0.001803148654, 0.002078216989, 0.003859200282,
-5.680047021e-18, -0.00502270367,-0.003522193991, 0.003983821254, 0.007270423695,
-8.427239153e-18,-0.009186808951,-0.006366339978, 0.007131042425, 0.0129177114,
-1.11744317e-17, -0.01620636135, -0.01124048699, 0.01264583599, 0.02310281433,
-1.350338849e-17, -0.02995507978, -0.02135082521, 0.02493774705, 0.04796636105,
-1.505954766e-17, -0.07404191047, -0.06162585691, 0.09309853613, 0.302559495,
0.400341481, 0.302559495, 0.09309853613, -0.06162585691, -0.07404191047,
-1.505954766e-17, 0.04796636105, 0.02493774705, -0.02135082521, -0.02995507978,
-1.350338849e-17, 0.02310281433, 0.01264583599, -0.01124048699, -0.01620636135,
-1.11744317e-17, 0.0129177114, 0.007131042425,-0.006366339978,-0.009186808951,
-8.427239153e-18, 0.007270423695, 0.003983821254,-0.003522193991, -0.00502270367,
-5.680047021e-18, 0.003859200282, 0.002078216989,-0.001803148654,-0.002520596841,
-3.351089814e-18, 0.001859114505,0.0009822372813,-0.0008386048721,-0.001159708714,
-1.794930437e-18,0.0008627952193,0.0004692059301,-0.000422134588,-0.0006325327558,
-1.248479932e-18
};
这样就成功将它导出了
四、设计实例,将输入的矩形波滤波成为一个方波
这里我们使用1Mhz的采样频率,去采集一个矩形波。
将矩形波滤为一个正弦波,对矩形波做fft可得其基波及谐波,如以1Mhz采样,归一化截止频率为0.4,则对应频率为200Khz,则输入正弦波的频率的基波要<<200K,3次谐波的频率要大于200K,及输入频率在66.6K到200K时可以将它滤波为正弦波
1. 矩形波频谱特性
基波:50kHz(需要保留)
3次谐波:150kHz
5次谐波:250kHz
7次谐波:350kHz
2. 截止频率选择
Fs = 1e6; % 采样频率1MHz
Fc = 200e3; % 截止频率200kHz
wc = Fc/(Fs/2); % 归一化截止频率 = 0.4
3.生成滤波器数据
这里同上,先生成滤波器数据备用。
4.利用cubemx生成keil工程
adc配置
这里选择一个adc通道,配置为单端输入
选择分辨率为12位
这里使用定时器触发adc采集精准控制采样时间
adcDMA设置
添加adcdma,设置为normal
定时器配置
使用内部时钟
配置分频系数
这里我的主时钟是80M,80-1,采样频率为1Mhz
开启自动重装载和触发事件
到这里定时器配置完成
串口配置
串口这里配置为全双工,其他参数默认即可
DSP库添加
点击software packs
点击select components
勾选dsplibrary
然后在middleware and sorftware packs选择x-cube-algobuild
勾选dsp
然后生成工程代码
keil代码
在define添加这段代码
,ARM_MATH_CM4,ARM_MATH_MATRIX_CHECK,ARM_MATH_ROUNDING
串口重回定向(记得添加stdio.h标准输入输出输出库使用printf)
勾选微库
编写fir.h文件
// fir.h
#ifndef __FIR_H
#define __FIR_H
#include "main.h"
#include "arm_math.h"
#include "stdio.h"
#include "stdlib.h"
void firtest(void);
#endif
编写fir.c文件
// fir.c
#include "fir.h"
/* 滤波器参数说明
* 采样频率: 1MHz
* 截止频率: 200kHz (归一化频率0.4)
* 滤波器长度: 81 (80阶)
* 窗函数: 汉宁窗
* 结构: 直接型FIR转置
*/
const int BL = 81; // 滤波器长度
float32_t pCoeffs[81] = {
// MATLAB生成的滤波器系数
-1.248479932e-18,-0.0006325327558,-0.000422134588,
// ... 省略中间系数 ...
-0.000422134588,-0.0006325327558,-1.248479932e-18
};
// 全局变量定义
__IO uint8_t adcok = 0; // ADC完成标志
uint16_t numTaps = 81; // 滤波器系数个数
float32_t pState[336] = {0.0f}; // 状态缓存(numTaps + blockSize - 1)
uint32_t blockSize = 256; // 数据块大小
float32_t inbuf[256] = {0.0f}; // 输入缓存
uint16_t adcbuf[256] = {0}; // ADC数据缓存
float32_t outbuf[256] = {0.0f}; // 输出缓存
arm_fir_instance_f32 * S; // FIR实例指针
void firtest(void)
{
// 1. 延时等待系统稳定
HAL_Delay(3000);
// 2. 启动定时器和ADC采样
HAL_TIM_Base_Start_IT(&htim3);
HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED);
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adcbuf, 256);
while(adcok); // 等待采样完成
// 3. 数据格式转换(ADC原始值转换为电压值)
for(int i = 0; i < 256; i++) {
inbuf[i] = adcbuf[i]/4096.0f*3.3f;
printf("%f\n", inbuf[i]); // 打印输入数据
}
// 4. 分配FIR实例内存
S = (arm_fir_instance_f32 *)malloc(sizeof(arm_fir_instance_f32));
if (S == NULL) {
printf("Memory allocation error\n");
return;
}
printf("\n\n\n\n\n\n\n\n\n");
// 5. 初始化并执行滤波
arm_fir_init_f32(S, numTaps, pCoeffs, pState, blockSize);
arm_fir_f32(S, inbuf, outbuf, blockSize);
// 6. 输出滤波结果
for(int i = 0; i < 256; i++) {
printf("%f\n", outbuf[i]);
}
// 7. 释放资源
free(S);
S = NULL;
}
参数说明
- 滤波器设计参数:
- 采样频率:1MHz
- 截止频率:200kHz
- 归一化截止频率:0.4
- 阶数:80(81个系数)
- 窗函数:汉宁窗
- 缓存大小设计:
- 数据块大小:256点
- 状态缓存:336点(numTaps + blockSize - 1)
打印串口,数据可视化
将数据复制粘贴到excel表格
选中一列数据之后点击插入,选择折线图
滤波效果
这样就实现了滤波操作