一、用STM32进行FFT计算与数学运算的过程
1. 信号采集
首先,我们需要使用STM32的ADC模块来采集模拟信号,比如三相交流电。ADC将模拟信号(如电压或电流)转换为数字信号,供后续处理。
采样数量:FFT的计算通常需要2的整数次幂的采样点数(如1024、2048)。采样点数越多,频率分辨率越高。
采样频率:采样频率必须至少是信号频率的两倍(奈奎斯特定理)。例如,分析50Hz的信号时,采样频率应至少为100Hz,但通常使用更高的采样频率,下的例子采集50hz的交流信号,采用的是2500hz,以保证计算精度。
2. FFT变换
采集到的时域数据通过FFT算法进行处理,转换为频域信息。ARM-DSP库中有现成的FFT函数,可以简化计算过程。关于FFT可以查看上一篇关于FFT的介绍:STM32上实现FFT算法精准测量正弦波信号的幅值、频率和相位差(标准库)
3. 运算结果:幅值、频率和均方根、平均值、最大值、最小值、峰峰值与标准差
幅值:信号的振幅大小,表示每个频率成分的强度。基波的幅值代表主要的电压或电流值。
频率:FFT能帮助我们识别信号中的不同频率成分,如电网中的50Hz基波及其他谐波。
均方根(RMS)运算公式:RMS = sqrt( (1/N) * Σ(x_i^2) )
其中,N 是数据点的数量,x_i 是每个数据点的值,Σ 表示求和。
平均值运算公式:平均值 = (1/N) * Σ(x_i)
其中,N 是数据点的数量,x_i 是每个数据点的值。
最大值运算公式:最大值 = max(x_i)
其中,x_i 是数据点集合中的每个值,max 表示取最大值操作。
最小值运算公式:
最小值 = min(x_i)
其中,x_i 是数据点集合中的每个值,min 表示取最小值操作。
峰峰值运算公式:
峰峰值 = 最大值 - 最小值
其中,最大值和最小值分别是从数据点集合中计算得出的。
标准差运算公式:
标准差 = sqrt( (1/N) * Σ((x_i - 平均值)^2) )
其中,N 是数据点的数量,x_i 是每个数据点的值,平均值是之前计算得出的数据点的平均值。
4. 处理流程概述
整个处理流程如下:
- 信号采集:定时器触发ADC采样交流电信号。
- 采样与ADC转换:STM32的ADC将模拟信号转为数字信号。
- DMA传输:使用DMA自动传输采样数据到内存。
- FFT计算:利用FFT将时域数据转换为频域数据。
- 结果提取:从FFT结果中提取幅值、频率等信息。
二、采样频率设定-定时器3(TIM3)配置
1.时钟源:定时器3的时钟源被设置为84MHz。这意味着定时器的时钟频率是84,000,000 Hz。
2.预分频器(Prescaler):预分频器的值被设置为3(通过4-1得到,因为预分频器的值通常是通过设置其寄存器的一个字段来实现的,该字段的值是所需分频数减1)。预分频器的作用是将定时器的时钟频率降低到一个更低的频率,以便能够更容易地实现所需的计数周期。因此,经过预分频后的时钟频率为84,000,000 / 4 = 21,000,000 Hz。
3.计数器周期(AutoReload Register):AutoReload Register的值被设置为8399(通过8400-1得到)。这个寄存器定义了定时器计数到多少时会产生一个更新事件(或称为溢出事件),并重新从0开始计数。因此,定时器的计数周期是8400个时钟周期。
4.采样频率:
采样率(Fs)是指每秒对信号进行采样的次数。采样率是2500Hz,意味着每秒采样2500个点。
信号频率(F)是指信号的周期性变化的速率。测量信号频率是50Hz,意味着信号每秒变化50个周期。
为了计算FFT的采样频率,使用了两个宏定义:fft_arr
和fft_psc
。
-
fft_arr
:这个宏被定义为8400,它实际上代表了AutoReload Register的值加1(即计数周期的实际值)。这个值用于计算定时器溢出一次所需的时间。 -
fft_psc
:这个宏被定义为4,它代表了预分频器的值加1(即预分频的实际倍数)。以下公式来计算FFT的采样频率:
const uint32_t fft_sample_freq = 84000000 / (fft_arr * fft_psc);
将fft_arr和fft_psc的值代入公式中,得到:
fft_sample_freq = 84000000 / (8400 * 4) = 84000000 / 33600 = 2500 Hz
FFT的采样频率是2500 Hz。这意味着定时器每0.0004秒(即400微秒)溢出一次,从而为FFT提供一个新的采样点。
一个信号周期内的采样点数(N)可以通过以下公式计算:
N = Fs / F
将已知的采样率(Fs = 2500Hz)和信号频率(F = 50Hz)代入公式中,我们可以得到:
N = 2500Hz / 50Hz = 50
因此,在2500Hz的采样率下,50Hz信号一个周期内将有50个采样点。
三、ADC配置
ADC1配置,common settings :independent,当ADC配置为“independent”模式时,它意味着该ADC模块是独立工作的,不与其他ADC模块共享任何资源或配置。
external trigger conversion source :timer3 tirgger out event。Timer3是一个定时器模块,它可以生成定时中断或触发输出事件。当Timer3达到某个预设的条件(如计数到某个值)时,它会触发一个事件,这个事件被用作ADC1的转换触发源。
中断DMA
四、程序实现
硬件:正点原子探索者 V3 STM32F407 开发板,下面是核心代码,代码中使用了ARM提供的数学库(arm_math.h
)来实现FFT算法,以及STM32的标准库库来配置定时器、ADC和DMA。通过这些配置,系统能够高效地采集和处理模拟信号,分析其频谱特性。以下为核心代码,完整代码请在资源下载。
/*
*********************************************************************************************************
* 函 数 名: WaveProcess
* 功能说明: //WaveProcess 功能说明: 波形通道均方根,平均值,最大值,最小值,峰峰值,频率,标准差,幅值,相位的计算
* 形 参: 无。
* 返 回 值: 无
*********************************************************************************************************
*/
void WaveProcess(void)
{
uint16_t i;
data.WaveMin = 4095;
uint16_t idex=0; //用于将采集到的数据赋值给fft_inputbuf[2*idex]的计数
float tempdata=0;
uint8_t temp[40];
uint16_t freamplen; // freamp长度的一半
/* 自动触发模式才计算FFT */
arm_cfft_radix4_init_f32(&scfft,FFT_LENGTH,0,1);//初始化scfft结构体,设定FFT相关参数 //FFT_LENGTH 4096
//adc1 pa5
for(idex=0;idex<FFT_LENGTH;idex++) //adc1 fft1 //FFT_LENGTH==4096
{
fft_inputbuf[2*idex]= (uint16_t)g_adc_dma_buf[idex]*(3.3/4096); //生成输入信号实部
fft_inputbuf[2*idex+1]=0;//虚部全部为0
}
arm_cfft_radix4_f32(&scfft,fft_inputbuf); //FFT计算(基4)
arm_cmplx_mag_f32(fft_inputbuf,fft_outputbuf,FFT_LENGTH); //把运算结果复数求模得幅值
freamplen=fft_getpeak(fft_inputbuf,fft_outputbuf+1,freamp,FFT_LENGTH/2,10,5,0.2); //寻找基波和谐波
data.WaveOffset=fft_outputbuf[0]/FFT_LENGTH;//偏置电压
data.WaveFreq=freamp[0];//频率
data.WaveAmplitude=freamp[1]+data.WaveOffset;//幅度
data.WavePhase=freamp[2];//相位
freamp[0]=0;freamp[1]=0;freamp[2]=0;
/* 求4096个数值的最大值和最小值 */
for (i = 0; i < FFT_LENGTH; i++)
{
data.WaveMean += g_adc_dma_buf[i];
data.WaveRMS += g_adc_dma_buf[i]*g_adc_dma_buf[i];
if(g_adc_dma_buf[i] < data.WaveMin)
{
data.WaveMin = g_adc_dma_buf[i];
}
if(g_adc_dma_buf[i] > data.WaveMax)
{
data.WaveMax = g_adc_dma_buf[i];
}
}
/* 求RMS 均方根(RMS,Root Mean Square)*/
data.WaveRMS = sqrt(data.WaveRMS/FFT_LENGTH)* 3.3f / 4095;
//data.WaveMean = data.WaveMean/FFT_LENGTH;
/* 求平均值 */
data.WaveMean = data.WaveMean/FFT_LENGTH* 3.3f / 4095;
/* 求标准差 */
for (i = 0; i < FFT_LENGTH; i++)
{
tempdata =g_adc_dma_buf[i];
data.WaveStd += pow((tempdata* 3.3f / 4095-data.WaveMean),2);
}
data.WaveStd = data.WaveStd/FFT_LENGTH;
data.WaveStd =sqrt(data.WaveStd);
/* 求最大值 */
data.WaveMax = data.WaveMax * 3.3f / 4095;
/* 求最小值 */
data.WaveMin = data.WaveMin *3.3f / 4095;
/* 求峰峰值 */
data.WavePkPk = data.WaveMax - data.WaveMin;
printf("fft_sample_freq:%d\r\n",fft_sample_freq); //fft采样频率
printf("WaveRMS:%.3f\r\n", data.WaveRMS); //数据打印,查看RMS结果
printf("WaveMean:%.3f\r\n",data.WaveMean);//数据打印,查看平均值
printf("WaveStd:%.3f\r\n",data.WaveStd);//数据打印,查看标准差
printf("WaveMax:%.3f\r\n", data.WaveMax);//数据打印,查看最大值
printf("WaveMin:%.3f\r\n", data.WaveMin);//数据打印,查看最小值
printf("WavePkPk:%.3f\r\n", data.WavePkPk);//数据打印,查看峰峰值
printf("WaveOffset:%.3f\r\n",data.WaveOffset); //偏置电压
printf("WaveFreq:%.3f\r\n",data.WaveFreq); //频率
printf("WaveAmplitude:%.3f\r\n",data.WaveAmplitude); //幅值
}
// 获取FFT峰值
int fft_getpeak(float *inputx,float *input,float *output,uint16_t inlen,uint8_t x,uint8_t N,float y) // intlen 输入数组长度,x寻找长度
{
int i,i2;
uint32_t idex; //不同于上一个函数中的,因为他们在不同的函数中被定义
float datas;
float sum;
int outlen=0;
for(i=0;i<inlen-x;i+=x)
{
arm_max_f32(input+i,x,&datas,&idex);
if( (input[i+idex]>=input[i+idex+1])&&(input[i+idex]>=input[i+idex-1])&&( (2*datas)/FFT_LENGTH )>y)
{
sum=0;
for(i2=i+idex-N;i2<i+idex+N;i2++)
{
sum+=input[i2];
}
if(1.5*sum/(2*N)<datas)
{
output[3*outlen+2] = atan2(inputx[2*(i+idex+1)+1],inputx[2*(i+idex+1)])*180/3.1415926f; //计算相位
output[3*outlen+1] = 1.0*(2*datas)/FFT_LENGTH; //计算幅度
output[3*outlen] = 1.0*fft_sample_freq*(i+idex+1)/FFT_LENGTH;//计算频率
outlen++;
}
else continue;
}
else continue;
}
return outlen;
}
-
定时器初始化 (
Tim3_Init
): 配置定时器3,用于控制ADC的采样频率。 -
ADC初始化 (
Adc_Init
): 配置两个ADC(ADC1和ADC2),用于采集模拟信号。ADC1和ADC2分别连接到不同的通道,采集不同的模拟信号。 -
DMA初始化 (
Dma_ADC_Init
): 配置DMA,用于将ADC采集的数据直接传输到内存中,减少CPU的负担。 -
数据初始化 (
Data_Init
): 调用上述初始化函数,完成系统的基本配置。 -
FFT峰值获取函数 (
fft_getpeak
): 该函数用于在FFT结果中寻找峰值,这些峰值代表了信号中的基波(主要频率成分)。下面是核心中的核心。arm_cfft_radix4_f32(&scfft,fft_inputbuf); //fft运算 arm_cmplx_mag_f32(fft_inputbuf,fft_outputbuf,FFT_LENGTH); //把运算结果复数求模得幅值 freamplen=fft_getpeak(fft_inputbuf,fft_outputbuf+1,freamp,FFT_LENGTH/2,10,5,0.2); //寻找基波和谐波
-
DMA中断服务函数 (
DMA2_Stream0_IRQHandler
): 当DMA传输完成时,该函数会被调用。它负责执行FFT算法,计算信号的偏置电压、幅值、频率、均方根、平均值、最大值、最小值、峰峰值与标准差的相关信息并用串口1打印。
五、现象
按下复位键,串口输出采集的4096个数据点,通过excel图表显示。的确一个周期内将有50个采样点。
1.信号发生器输入信号源,信号1:50Hz的信号的,幅度1V,偏置电压0.5V,相位0°;
2.串口收到测量结果与输入信号源的实际接近。
3.信号2:50Hz的信号的,幅度2V,偏置电压1V,相位0°;
六、总结
通过上述介绍,我们探讨了如何使用STM32微控制器执行FFT计算,以提取信号的幅值、频率、均方根、平均值、最大值、最小值、峰峰值与标准差,一些示波器测量的参数。
希望这些内容能够为大家提供有价值的参考和指导。在实际应用中,理解和运用FFT的原理和技巧,将有助于我们更有效地处理和分析各种复杂的信号。