FIR 窗函数实现低通滤波
文章目录
- FIR 窗函数实现低通滤波
- 1.窗的分类与选择
- 2.matlab设计
- 3.代码编写
- 4.结果欣赏与群延迟
- 5.其他可能报错
- 5.1.adc采集的数据没问题,vofa打印成-nan
- 5.2.vofa打印的滤波数据为初始化的0
- 6.代码备忘
1.窗的分类与选择
2.matlab设计
主页命令行输入filterDesiner
进去后选择FIR,阶数理论上越大越好这里80,窗的选择由第一部分,我们就选择汉宁窗
本次设计我们简单点,将一个200Hz的方波信号滤成正弦,那么截至频率就选择为200,采样率3200足矣。
Fs 采样频率,也就是说在之后你ADC的采样频率也要是这么多
Fc 截止频率,注意汉宁窗的截止频率在-6db点的位置
生成后留意上方选单,其中有个群延迟响应记一下,我们后面讲
选择生成C 头文件
变量名自己随便设定,选用单精度浮点型,我们用的是f32函数,对应的就是单精度浮点
将生成的这段数据复制粘贴到代码中
3.代码编写
cubemx中配置ADC与dma单次采集,tim触发,dsp库导入clion,此处略过不讲,读者可以自行前往笔者过往博客翻看。
前半部分代码内容,宏定义了一个adc的采样点数,这里的采样点数需要是多于几个周期的,原因后文讲。这里宏定义懒得写那么多了,读者们可以多加,方便移植。
定义了标志位,adc存数据数组,输入fir数组,输出fir数组。
滤波器系数个数注意下比80多一个,fir状态变量暂存的数组长度注意。
块处理大小是后面一次fir处理的数据个数。
/* USER CODE BEGIN PD */
#define AdcNum 256
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
uint16_t adc_conv =0;//标志位
uint16_t adc_value[AdcNum] = {0};
//输入数据
float32_t inbuf[AdcNum] = {0};
//输出数据
float32_t outbuf[256]= {0};
uint16_t numTaps=81;//fir滤波器系数个数
//FIR滤波器状态变量暂存:数组的大小=numTaps+blocksize-1
float32_t pState[336]={0.0f};
//块处理大小
uint32_t blockSize=256;
/* USER CODE END PV */
之前matlab生成的头文件内容,将real32改成float类型变量,可以看到大量黄色warning,我们无需改成double型变量,改后fir32是不会工作的。
/* USER CODE BEGIN PFP */
float32_t pCoeffs[81] = {
3.88870479e-19,0.0002536820539,0.0005061632837,0.0007350782398,0.0009042214951,
0.0009640454082,0.0008594112005,0.0005441915127,-8.159095012e-19,-0.0007456119638,
-0.001607135055,-0.002440539422,-0.003057639115,-0.003255824093,-0.002859890461,
-0.001769107184,1.933912083e-18, 0.002285639057, 0.004776827525, 0.007039505057,
0.008573494852, 0.008895023726, 0.007633602712, 0.00462751044,-3.315839305e-18,
-0.00580064673, -0.0120098507, -0.01760983095, -0.02144382522, -0.02236903831,
-0.01942810044, -0.01201371197,4.433841939e-18, 0.01618272252, 0.03554584458,
0.05661869422, 0.077596955, 0.09654571861, 0.1116304249, 0.1213437766,
0.1246964261, 0.1213437766, 0.1116304249, 0.09654571861, 0.077596955,
0.05661869422, 0.03554584458, 0.01618272252,4.433841939e-18, -0.01201371197,
-0.01942810044, -0.02236903831, -0.02144382522, -0.01760983095, -0.0120098507,
-0.00580064673,-3.315839305e-18, 0.00462751044, 0.007633602712, 0.008895023726,
0.008573494852, 0.007039505057, 0.004776827525, 0.002285639057,1.933912083e-18,
-0.001769107184,-0.002859890461,-0.003255824093,-0.003057639115,-0.002440539422,
-0.001607135055,-0.0007456119638,-8.159095012e-19,0.0005441915127,0.0008594112005,
0.0009640454082,0.0009042214951,0.0007350782398,0.0005061632837,0.0002536820539,
3.88870479e-19
};
/* USER CODE END PFP */
接下来是两段函数
/* USER CODE BEGIN 0 */
void adc_start()
{
HAL_TIM_Base_Start(&htim8);
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_value, AdcNum);
while (!adc_conv);
adc_conv = 0;
}
void fir_start()
{
//adc采样值转换为电压值并存入输入数组
for(int i=0;i<AdcNum;i++)
{
inbuf[i]=(float)(adc_value[i]*3.3/4096);
}
//FIR实例化结构体
arm_fir_instance_f32 * S;
// 为FIR实例分配内存
S = (arm_fir_instance_f32 *)malloc(sizeof(arm_fir_instance_f32));
if (S == NULL)
{
// 内存分配失败,处理错误
return;
}
arm_fir_init_f32(S,numTaps,pCoeffs,pState,blockSize);
arm_fir_f32(S,inbuf,outbuf,blockSize);
free(S); // 释放内存
S = NULL; // 将指针设置为 NULL,以避免悬挂指针
for (int j = 0; j < AdcNum; ++j) {
printf("%d,%f,%f\n",j,outbuf[j],inbuf[j]);
}
}
/* USER CODE END 0 */
adc_start函数没啥好讲的
fir_start中,我们先将adc采样的数据转换成电压值存到一个数组中
初始化arm_fir_instance_f32结构体并为它分配内存(重要!!!)
实测,如果不给它分配内存那fir不跑,看其他例程没分配照样好好运行,不知何解。
释放掉结构体内存,然后打印数据
4.结果欣赏与群延迟
打印到vofa
会发现前几个周期的波形fir是衰减的
问AI
看看安富莱
照应前文,读者可以翻上去看下群延迟的点是40,所以说我们的采样点数需要多一点,否则波形还没正常fir就结束了
确实差不多
5.其他可能报错
5.1.adc采集的数据没问题,vofa打印成-nan
-nan意为not a number,很可能是将matlab生成的系数转换为double型了
5.2.vofa打印的滤波数据为初始化的0
没有为结构体分配内存
6.代码备忘
/**
******************************************************************************
* @file : dsp.h
* @author : Silencecmsj
* @brief : None
* @attention : None
* @date : 2024/7/26
******************************************************************************
* _ooOoo_
* o8888888o
* 88" . "88
* (| -_- |)
* O\ = /O
* ____/`---'\____
* .' \\| |// `.
* / \\||| : |||// \
* / _||||| -:- |||||- \
* | | \\\ - /// | |
* | \_| ''\---/'' | |
* \ .-\__ `-` ___/-. /
* ___`. .' /--.--\ `. . __
* ."" '< `.___\_<|>_/___.' >'"".
* | | : `- \`.;`\ _ /`;.`/ - ` : | |
* \ \ `-. \_ __\ /__ _/ .-` / /
* ======`-.____`-.___\_____/___.-`____.-'======
* /**
******************************************************************************
* @file : fir.h
* @author : Silencecmsj
* @brief : None
* @attention : None
* @date : 2024/7/26
******************************************************************************
* _ooOoo_
* o8888888o
* 88" . "88
* (| -_- |)
* O\ = /O
* ____/`---'\____
* .' \\| |// `.
* / \\||| : |||// \
* / _||||| -:- |||||- \
* | | \\\ - /// | |
* | \_| ''\---/'' | |
* \ .-\__ `-` ___/-. /
* ___`. .' /--.--\ `. . __
* ."" '< `.___\_<|>_/___.' >'"".
* | | : `- \`.;`\ _ /`;.`/ - ` : | |
* \ \ `-. \_ __\ /__ _/ .-` / /
* ======`-.____`-.___\_____/___.-`____.-'======
* `=---='
* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
******************************************************************************
*/
#ifndef FIR_TEST_FIR_H
#define FIR_TEST_FIR_H
#include "arm_math.h"
#include "stdlib.h"
/**
* @brief FIR filter configuration structure
*
* @param numTaps 滤波器系数个数
* @param pCoeffs 指向滤波器系数数组的指针
* @param blockSize 每次处理大小
* @param S ARM FIR filter instance
* @param inbuf 滤波输入数据,注意float型,为电压值,长度为blocksize
* @param outbuf 滤波输出数据,长度为blocksize
*
* @note 请在主函数中提前设置好各种参数,仅支持一次性处理完毕
*/
typedef struct
{
uint16_t numTaps; // 滤波器系数个数
float32_t *pCoeffs; // 指向滤波器系数数组的指针
uint16_t blockSize; // 块处理大小
arm_fir_instance_f32 *S; // ARM FIR filter instance
float32_t *inbuf; // 输入数组指针
float32_t *outbuf; // 输出数组指针
} FIR_Config;
/**
* @brief FFT configuration structure
*
* @param fftLength FFT 长度,应该为2的倍数
* @param pInputBuf 指向输入数组的指针,注意长度应该是fftlength
* @param pOutputBuf 指向输出缓冲区的指针,注意长度是fftlength+1
* @param S ARM CFFT 实例
*/
typedef struct
{
uint16_t fftLength; // FFT 长度
float32_t *pInputBuf; // 指向输入缓冲区的指针
float32_t *pOutputBuf; // 指向输出缓冲区的指针
arm_cfft_radix4_instance_f32 *S; // ARM CFFT 实例
}FFT_Config;
void adc_V_value(uint16_t adc_value[], float32_t adc_V[], uint16_t size,uint8_t adc_bit);//将ADC值转换为电压值
void fir(FIR_Config *config);//fir滤波函数
void fft(FFT_Config *config);//fft函数
#endif //FIR_TEST_FIR_H
/**
******************************************************************************
* @file : dsp.c
* @author : Silencecmsj
* @brief : None
* @attention : 使用前注意在主函数中提前将各种需要的参数设置好
* @date : 2024/7/26
******************************************************************************
* _ooOoo_
* o8888888o
* 88" . "88
* (| -_- |)
* O\ = /O
* ____/`---'\____
* .' \\| |// `.
* / \\||| : |||// \
* / _||||| -:- |||||- \
* | | \\\ - /// | |
* | \_| ''\---/'' | |
* \ .-\__ `-` ___/-. /
* ___`. .' /--.--\ `. . __
* ."" '< `.___\_<|>_/___.' >'"".
* | | : `- \`.;`\ _ /`;.`/ - ` : | |
* \ \ `-. \_ __\ /__ _/ .-` / /
* ======`-.____`-.___\_____/___.-`____.-'======
* `=---='
* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
******************************************************************************
*/
#include "dsp.h"
/**
* @brief 将ADC值转换为电压值
* @param adc_value adc实际采集到的数组
* @param adc_V adc转换后的电压值
* @param size 要转换的数组长度
* @param adc_bit adc位数
*
* @note 此处为节省单片机计算空间,直接给出对应位数的长度,因此暂时只支持8、10、12、14、16位的ADC
*/
void adc_V_value(uint16_t adc_value[], float32_t adc_V[], uint16_t size ,uint8_t adc_bit)//将ADC值转换为电压值
{
if (adc_bit == 12)
{
for (int i = 0; i < size; i++)
{
adc_V[i] = (float32_t)(adc_value[i] * 3.3 / 4096);
}
}
else if (adc_bit == 14)
{
for (int i = 0; i < size; i++)
{
adc_V[i] = (float32_t)(adc_value[i] * 3.3 / 16384);
}
}
else if (adc_bit == 16)
{
for (int i = 0; i < size; i++)
{
adc_V[i] = (float32_t)(adc_value[i] * 3.3 / 65536);
}
}
else if (adc_bit == 10)
{
for (int i = 0; i < size; i++)
{
adc_V[i] = (float32_t)(adc_value[i] * 3.3 / 1024);
}
}
else if (adc_bit == 8)
{
for (int i = 0; i < size; i++)
{
adc_V[i] = (float32_t)(adc_value[i] * 3.3 / 256);
}
}
}
/**
* @brief fir滤波函数
* @param config fir结构体参数
* @note numTaps-1必须是4的倍数;
*
* @note 例子:主函数中,宏定义numTaps、blockSize,定义pCoeffs,定义结构体,调用fir函数
*/
void fir(FIR_Config *config)
{
// Allocate memory for the state buffer
float32_t *pState = malloc((config->numTaps + config->blockSize - 1) * sizeof(float32_t));
if (pState == NULL)
{
// Handle error
return;
}
// Allocate memory for the filter instance
config->S = (arm_fir_instance_f32 *)malloc(sizeof(arm_fir_instance_f32));
if (config->S == NULL)
{
free(pState);
return;
}
// Initialize the filter
arm_fir_init_f32((config->S), config->numTaps, config->pCoeffs, pState, config->blockSize);
// Process the input buffer
arm_fir_f32((config->S), config->inbuf, config->outbuf, config->blockSize);
// Free allocated memory
free(pState);
free(config->S);
config->S = NULL; // Avoid dangling pointer
}
/**
* @brief FFT函数
* @param config FFT结构体参数
*/
void fft(FFT_Config *config)
{
// Allocate memory for the FFT instance
config->S = (arm_cfft_radix4_instance_f32 *)malloc(sizeof(arm_cfft_radix4_instance_f32));
if (config->S == NULL)
{
// Handle error
return;
}
float32_t *fft_inputbuf = malloc(config->fftLength * 2 * sizeof(float32_t));
if (fft_inputbuf == NULL)
{
return;
}
// Initialize the FFT
arm_cfft_radix4_init_f32(config->S, config->fftLength, 0, 1);
// Initialize the input buffer
for (int i = 0; i < config->fftLength; i++)
{
fft_inputbuf[i * 2] = config->pInputBuf[i];//adc采集到的实际的电压值
fft_inputbuf[i * 2 + 1] = 0;//涉及到fft原理实部也就是上一条有值,本项为虚部,置0。
}
// Perform the CFFT
arm_cfft_radix4_f32(config->S, fft_inputbuf);
// Calculate the magnitude spectrum
arm_cmplx_mag_f32(fft_inputbuf, config->pOutputBuf, config->fftLength / 2 + 1);
config->pOutputBuf[0] /= (float32_t )config->fftLength;
for (int j = 1; j < config->fftLength+1; ++j)
{
config->pOutputBuf[j] = 2 * config->pOutputBuf[j] / (float32_t )config->fftLength;
}
// Free allocated memory
free(config->S);
config->S = NULL; // Avoid dangling pointe
// Free the input buffer memory
free(fft_inputbuf);
config->pInputBuf = NULL; // Avoid dangling pointer
}
理论高度决定实践高度。