利用STM32ZET6 进行FFT采集频率

这篇具有很好参考价值的文章主要介绍了利用STM32ZET6 进行FFT采集频率。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

 文章来源地址https://www.toymoban.com/news/detail-856707.html

准备电赛的中,尝试了几种测量频率的方法,也参考了一些博主,没有一种可以测量范围很广的方法,而且大多数都要豆豆下载,本人属于看见要给钱就会直接撤退的,当然也不是批判官方,毕竟还是有许多好东西的。下面的代码都是我在准备比赛时写的,可以测量。如果大佬们有什么更好的方法可以指正一下。 (时隔第一次写这篇文章 说一下最终结果 2023年全国电子设计大赛 省一)在自己和队友摸索中得到这个成绩还是不错了。

平台:stm32F103

FFT(快速傅里叶变换)是一种高效的数字信号处理算法,用于将一个信号从时域(即时间域)转换为频域(即频率域),并提取信号中的频率信息。FFT算法是由美国数学家J.W. Cooley和J.W. Tukey于1965年首次提出的。

FFT算法可以大幅加速傅里叶变换的计算,将计算复杂度从O(n^2)降低到O(n log n)。这使得FFT成为数字信号处理、通信、图像处理等领域中广泛使用的算法之一。

在数字信号处理中,FFT算法通常用于谱分析、信号过滤、滤波器设计、图像处理等领域。例如,人们可以通过FFT算法将音频信号转换为频谱图,以便分析它的频率成分,或者通过FFT算法将图像转换为频域图像,并使用频域滤波器进行图像去噪或增强等处理操作。

学习一个新知识就像谈恋爱一样,慢慢了解才是最真诚的。

步入正题 我们先来一个最简单的一步一步深入了解,最终抱得美人归。

1.输入捕获模式

首先先引入一个简单的概念

fft stm32,stm32,单片机,嵌入式硬件本节使用的就是测周法  废话不多说直接上代码

#include "stm32f10x.h"                  // 头文件

void PWM_Init(void)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);  // 使能TIM2外设时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);  // 使能GPIOA外设时钟

    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  // GPIO引脚配置为复用推挽输出模式
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;  // 配置GPIOA的引脚0(TIM2通道1)
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  // GPIO速度设置为50MHz
    GPIO_Init(GPIOA, &GPIO_InitStructure);  // 初始化GPIOA端口

    TIM_InternalClockConfig(TIM2);  // 使用内部时钟作为TIM2的时钟源

    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;  // 时钟分频设为不分频
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;  // 计数器向上计数模式
    TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;  // 设定周期为100(ARR寄存器的值)
    TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;  // 预分频器设为719(PSC寄存器的值)
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);  // 初始化TIM2的时间基准设置

    TIM_OCInitTypeDef TIM_OCInitStructure;
    TIM_OCStructInit(&TIM_OCInitStructure);
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;  // 输出比较模式设为PWM模式1
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;  // 输出极性设为高电平有效
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;  // 输出使能
    TIM_OCInitStructure.TIM_Pulse = 0;  // 初始占空比设为0(CCR寄存器的值)
    TIM_OC1Init(TIM2, &TIM_OCInitStructure);  // 初始化TIM2的通道1输出比较配置

    TIM_Cmd(TIM2, ENABLE);  // 使能TIM2计数器
}

void PWM_SetCompare1(uint16_t Compare)  //调节ccr的值控制占空比 = ccr/arr
{
    TIM_SetCompare1(TIM2, Compare);  // 设置TIM2通道1的比较值
}

void PWM_SetPrescaler(uint16_t Prescaler) //改变psc大小 决定pwm频率
{
    TIM_PrescalerConfig(TIM2, Prescaler, TIM_PSCReloadMode_Immediate);  // 设置TIM2的预分频值
}

配置PWM的频率和占空比,相信有一点32知识的友友都能看懂。我就不做过多的介绍。不懂的可以去学习一下。

输入捕获可以对输入信号的上升沿、下降沿、双边沿进行捕获,用于测量脉宽、频率、占空比当相应的 ICx 信号检测到跳变沿后,将使用捕获/比较寄存器(TIMx_CCRx)来锁存计数器的值。通过检测 TIMx_CHx 上的边沿信号,在边沿信号发生跳变(比如上升沿/下降沿)的时候,将当前定时器的值(TIMx_CNT)存放到对应的通道的捕获/比较寄存(TIMx_CCRx)里面,完成一次捕获。同时还可以配置捕获时是否触发中断/DMA 等。通过对上升沿下降沿捕获的值来计算需要的值
 

#include "stm32f10x.h"                  // 包含设备头文件

void IC_Init(void)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);   // 使能TIM3定时器的时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);  // 使能GPIOA的时钟
    
    GPIO_InitTypeDef GPIO_InitStructure;  // 定义GPIO初始化结构体
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;  // GPIO模式为上拉输入
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;      // GPIO引脚为Pin_6
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  // GPIO速度为50MHz
    GPIO_Init(GPIOA, &GPIO_InitStructure);   // 初始化GPIOA端口
    
    TIM_InternalClockConfig(TIM3);  // 配置TIM3为内部时钟源
    
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;  // 定义定时器基本参数结构体
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;  // 设置时钟分频系数
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;  // 设置计数模式为向上计数
    TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;  // 设置自动重载寄存器(ARR)的值,即计数周期-1
    TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;  // 设置预分频器(PSC)的值
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;  // 设置重复计数器的值
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);  // 初始化TIM3
    
    TIM_ICInitTypeDef TIM_ICInitStructure;  // 定义输入捕获参数结构体
    TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;  // 设置输入捕获通道为通道1
    TIM_ICInitStructure.TIM_ICFilter = 0xF;  // 设置输入捕获滤波器的值
    TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;  // 设置输入信号的极性为上升沿触发
    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;  // 设置输入捕获预分频系数
    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;  // 设置输入捕获选择直接输入
    TIM_PWMIConfig(TIM3, &TIM_ICInitStructure);  // 配置TIM3为脉宽测量输入模式
    
    TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);  // 选择外部触发源为TI1FP1
    TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);  // 配置TIM3为从模式并将其复位
    
    TIM_Cmd(TIM3, ENABLE);  // 使能TIM3定时器
}

uint32_t IC_GetFreq(void)
{
    return 1000000 / (TIM_GetCapture1(TIM3) + 1);  // 返回捕获频率:1000000除以(TIM3的捕获寄存器1的值+1)
}

uint32_t IC_GetDuty(void)
{
    return (TIM_GetCapture2(TIM3) + 1) * 100 / (TIM_GetCapture1(TIM3) + 1);  // 返回占空比:(TIM3的捕获寄存器2的值+1)乘以100除以(TIM3的捕获寄存器1的值+1)
}

上述代码最重要的一个步骤便是计算返回频率。

uint32_t IC_GetFreq(void)
{
    return 1000000 / (TIM_GetCapture1(TIM3) + 1);  // 返回捕获频率:1000000除以(TIM3的捕获寄存器1的值+1)
}

从此段代码我们可以看出返回值的大小为72M时钟/PSC(预分频)=1MHZ,在此基础上除以输入捕获的值其中 TIM_GetCapture1(TIM3) 是用于获取 TIM3 定时器捕获寄存器 1(Capture Register 1)的值。该寄存器记录了输入信号引发的定时器计数器的值。在这段代码中,+ 1 是为了避免除以零的情况发生。如果不加 + 1,当捕获寄存器的值为 0 时,计算结果将导致除以零错误。通过增加 1,即使捕获寄存器的值为 0,也能避免除以零的错误,并且不会改变实际计算的结果。因此,表达式 TIM_GetCapture1(TIM3) + 1 的目的是获取捕获寄存器 1 的值,并保证计算的安全性。

(由上面代码可以很清楚的发现我们这里设置的是上升沿捕获)当然如果你想要其他方式触发也是可以的,这里我们简单看一下,我们可以跳转到定义去查看

/** @defgroup TIM_Input_Capture_Polarity 
  * @{
  */

#define  TIM_ICPolarity_Rising             ((uint16_t)0x0000)
#define  TIM_ICPolarity_Falling            ((uint16_t)0x0002)
#define  TIM_ICPolarity_BothEdge           ((uint16_t)0x000A)
#define IS_TIM_IC_POLARITY(POLARITY) (((POLARITY) == TIM_ICPolarity_Rising) || \
                                      ((POLARITY) == TIM_ICPolarity_Falling))
#define IS_TIM_IC_POLARITY_LITE(POLARITY) (((POLARITY) == TIM_ICPolarity_Rising) || \
                                           ((POLARITY) == TIM_ICPolarity_Falling)|| \
                                           ((POLARITY) == TIM_ICPolarity_BothEdge))       

 这段代码定义了用于定时器输入捕获极性的宏和相应的判断宏。

  • TIM_ICPolarity_Rising:上升沿触发模式,表示输入信号在上升沿触发捕获。
  • TIM_ICPolarity_Falling:下降沿触发模式,表示输入信号在下降沿触发捕获。
  • TIM_ICPolarity_BothEdge:双边沿触发模式,表示输入信号在上升沿和下降沿都会触发

代码中 TIM_PWMIConfig(TIM3, &TIM_ICInitStructure);  // 配置TIM3为脉宽测量输入模式得到占空比的函数就不做过多解释,感兴趣的小伙伴可以去了解一下PWMI的内涵,也就是

fft stm32,stm32,单片机,嵌入式硬件如果在直连过程中配置一个通道那么另一个通道就会是其的相反捕获方式

   占空比

uint32_t IC_GetDuty(void)
{
    return (TIM_GetCapture2(TIM3) + 1) * 100 / (TIM_GetCapture1(TIM3) + 1);  // 返回占空比:(TIM3的捕获寄存器2的值+1)乘以100除以(TIM3的捕获寄存器1的值+1)
}

具体而言,CCR2(TIM_GetCapture2(TIM3) + 1)代表高电平信号的结束时间,CCR1(TIM_GetCapture1(TIM3) + 1)代表一个周期的时间。将结束时间加1是为了避免除零错误。然后,将结束时间除以周期时间,得到一个小数值表示占空比。乘以100后,将小数值转换为百分比形式,并将结果作为uint32_t类型返回。这种计算方法常用于测量脉冲信号的占空比,例如在电子设备中对PWM(脉宽调制)信号进行控制和监测等应用场景。

好了输入捕获就讲到这里,接下来就是本节的重头戏。

2.FFT测频率

我所利用的方法也是FFT+ADC+DMA进行测量 ,借鉴一些博主的所测资料。

优缺点

1、fft方式测频率,峰峰值可以最低为20mV,输入捕获方式,峰峰值需要达到七、八百mV以上,才可以精准测量;
2、程序的测量精度利用信号发生器进行了验证(下面表格的数据基本是在峰峰值20mV下测得,峰峰值更大些精度会更高些。

fft stm32,stm32,单片机,嵌入式硬件

我们要明白用stm32f103自带的12位ADC进行数据采集,FFT之后如果要获取信号频率、幅度等信息还要知道采样频率Fs,因此一般都是用定时器触发ADC采集,再用DMA进行搬运,定时器时间可以自己设置,采样频率也就知道了。我使用定时器触发ADC采集,采样频率为被测频率的2倍。并且将值储存在一个1024大小的数组里。然后使用stm32官方自带的FFT库,调用一个函数,将1024个数据传进去,然后将出来的数据再进行一些简单的分析得到频率与幅值的信息。有关FFT的具体原理与数据意义这里不细讲。推荐CSDN的文章https://blog.csdn.net/zhaopeizhaopeipei/article/details/53908238
具体配置如下(只贴出关键部分):

定时器配置

/******************************************************************
函数名称:TIM2_PWM_Init(u16 arr,u16 psc)
函数功能:定时器2,PWM输出模式初始化函数
参数说明: arr:重装载值
		     psc:预分频值
备    注:通过TIM2-CH2的PWM输出触发ADC采样
*******************************************************************/ 	
void TIM2_PWM_Init(u16 arr,u16 psc)
{  
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;//定时器比较通道配置
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);	//使能定时器2时钟
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA  | RCC_APB2Periph_AFIO, ENABLE);  //使能GPIO外设和AFIO复用功能模块时钟
 
   //设置该引脚为复用输出功能,输出TIM2 CH2的PWM脉冲波形	GPIOA.1
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; //PA1,TIM_CH2
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIO
 
   //初始化TIM3
	TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
	TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值 
	TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
	
	//初始化TIM2 Channel 2 PWM模式	 
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式2
 	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能,可以选择是否启用比较输出
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性:TIM输出比较极性低
	TIM_OCInitStructure.TIM_Pulse=1000;//比较匹配值,用于设置比较通道的比较值 // CCR
	TIM_OC2Init(TIM2, &TIM_OCInitStructure);  //根据T指定的参数初始化外设TIM2

	TIM_CtrlPWMOutputs(TIM2, ENABLE);//使能PWM输出
	
	TIM_Cmd(TIM2, ENABLE);  //使能TIM2
}

将PSC和ARR配置为形参,便于修改采样频率。然后进行配置DMA缓存所采集到的ADC数据

#include "dma.h"
#include "usart.h"

/******************************************************************
函数名称:MYDMA1_Config()
函数功能:DMA1初始化配置
参数说明:DMA_CHx:DMA通道选择
		 cpar:DMA外设ADC基地址
		 cmar:DMA内存基地址
		 cndtrDMA通道的DMA缓存的大小
备    注:
*******************************************************************/
void MYDMA1_Config(DMA_Channel_TypeDef* DMA_CHx,u32 cpar,u32 cmar,u16 cndtr)
{
	DMA_InitTypeDef DMA_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
 	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);	//使能DMA传输
	
  DMA_DeInit(DMA_CHx);   //将DMA的通道1寄存器重设为缺省值
	DMA_InitStructure.DMA_PeripheralBaseAddr = cpar;  //DMA外设ADC基地址
	DMA_InitStructure.DMA_MemoryBaseAddr = cmar;  //DMA内存基地址
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;  //数据传输方向,从外设读取发送到内存//
	DMA_InitStructure.DMA_BufferSize = cndtr;  //DMA通道的DMA缓存的大小
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;  //外设地址寄存器不变 始终转运同一个ADC地址
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  //内存地址寄存器递增
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;  //数据宽度为16位
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //数据宽度为16位
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;  //工作在循环模式
	DMA_InitStructure.DMA_Priority = DMA_Priority_High; //DMA通道 x拥有高优先级 
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  //DMA通道x没有设置为内存到内存传输
	DMA_Init(DMA_CHx, &DMA_InitStructure);  //ADC1匹配DMA通道1
	
	DMA_ITConfig(DMA1_Channel1,DMA1_IT_TC1,ENABLE);	//使能DMA传输中断	
	
	//配置中断优先级
	NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0 ;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;		
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			
	NVIC_Init(&NVIC_InitStructure);	

	DMA_Cmd(DMA1_Channel1,ENABLE);//使能DMA通道
}
 

不难看出 cpar:DMA外设ADC基地址
               cmar:DMA内存基地址
               cndtrDMA通道的DMA缓存的大小

通过设置其地址便可以将ADC采集到的值转运到DMA中,随后在进行FFT变换。

主函数配置如下:

	MYDMA1_Config(DMA1_Channel1,(u32)&ADC1->DR,(u32)&currentadc,1);//将ADC采集的数据转运到DMA中

 配置DMA采样ADC值 将采集到的值通过STM32自带的DSP库进行FFT运算。

void DMA1_Channel1_IRQHandler(void)
{
	u16 i = 0;
	
  DMA_Cmd(DMA1_Channel1, DISABLE);
  adc_flag=1;
  DMA_ClearITPendingBit(DMA_IT_HT);
  DMA_ClearITPendingBit(DMA1_IT_TC1);
	for(i=0;i<NPT;i++)
		InBufArray[i] = ((signed short)(ADC_Value[i])) << 16;		
					 																												
	cr4_fft_1024_stm32(OutBufArray, InBufArray, NPT);
	//cr4_fft_256_stm32(OutBufArray, InBufArray, NPT);
	GetPowerMag();	
	DMA_Cmd(DMA1_Channel1, ENABLE);
}

这里贴出运算幅值代码(官方给出)

double sqr(double x)
{
  return x * x;
}

void GetPowerMag()
{
    signed short lX,lY;
    float X,Y,Mag;//实部,虚部,各频率幅值,最大幅值
    unsigned short i;
    for(i=0; i<NPT/2; i++)
    {
        lX  = (OutBufArray[i] << 16) >> 16;
        lY  = (OutBufArray[i] >> 16);
        
        //除以32768再乘65536是为了符合浮点数计算规律
        X = NPT * ((float)lX) / 32768;
        Y = NPT * ((float)lY) / 32768;
        Mag = sqrt(X * X + Y * Y) / NPT;
        if(i == 0)
            MagBufArray[i] = (unsigned long)(Mag * 32768);
        else
            MagBufArray[i] = (unsigned long)(Mag * 65536);
   }
}

此时FFT运算采集基础已经完成,接下来就可以进行运算了,通过幅值最大的点得到对应的频率

这里我就不做过多介绍了 由于作此篇文章是放假归来 发现没有保存和发布导致我在以前基础上进行了修改但是大致原理如此,我就把我们队的运算代码给出来吧(识别三角波和正弦波)

void select_max(void)   //发挥部分三角波与正弦波识别
{
	int i,j;
	float k1,m,m2;
  u16 temp2[5];    //缓存次大值
  u16 temp1[5];    //缓存最大值

 //LCD_ShowFloat(1,1,12,FreB,1,2);//浮点显示

	float aMax =0.0,aSecondMax = 0.0,aThirdMax = 0.0,afourdMax=0.0;
	float fMax =0.0,fThirdMax = 0.0,ffourdMaxx = 0.0;
	int nMax=0,nSecondMax=8,nThirdMax=0,nfourdMax;
	u8 firstf,secondf;
	aMax=MagBufArray[1];
//for ( i = 1; i < 100; i++)
	
	
	for ( i = 1; i < NPT/2; i++)//i必须是1,是0的话,会把直流分量加进去!!!!         //求最大值
	{
		
			if (MagBufArray[i]>=aMax)
			{
					aMax = MagBufArray[i]; 
					nMax=i;
					
			}
			
	}
				fMax=(72000000)/((NPT*(ARR+1)*(PSC+1))/nMax);
				FreA=(72000000)/((NPT*(ARR+1)*(PSC+1))/nMax);   // 小频率
				FreA+=2000;
				FreA=(FreA/5000)*5000;


	for ( i = 1; i<NPT/2; i++)                                                       //求次大值
	{
		
			if (i==nMax-4)i=nMax+4;
			if (MagBufArray[i]>aSecondMax)
			{
					aSecondMax = MagBufArray[i]; 
					nSecondMax=i;
			}
	}

      FreB=(72000000)/((NPT*(ARR+1)*(PSC+1))/nSecondMax);//大频率
	FreB+=2000;
	FreB=(FreB/5000)*5000;

//	FreB*=5;
			// LCD_ShowxNum(165,230,k,6,12,0);//A波频率
	
//******************************************频率 幅值 排序******************************************//	 

	
	
nMax<nSecondMax?(firstf=nMax,secondf=nSecondMax,aSecondMax = MagBufArray[nSecondMax],aMax = MagBufArray[nMax]):(firstf=nSecondMax,secondf=nMax,aSecondMax = MagBufArray[nMax],aMax = MagBufArray[nSecondMax]);
	//	nMax<nSecondMax?(firstf=nMax,secondf=nSecondMax,aSecondMax = MagBufArray[nSecondMax]):(firstf=nSecondMax,secondf=nMax,aSecondMax = MagBufArray[nMax]);
				   



for ( i=3*firstf-4; i<3*firstf+4; i++)     //temp值确定A波3次谐波采样范围            //求  
	{
			if (MagBufArray[i]>afourdMax)
			{
					afourdMax = MagBufArray[i]; 
					nfourdMax=i;
			}
	}
//**************************************************************************************************//	 
// 正弦  三角   EER  PIAO           OK 
//  20    25 	            20  40       
for ( i=3*secondf-30; i<3*secondf+30; i++)     //temp值确定A波3次谐波采样范围            //求  
	{
		

			if (MagBufArray[i]>aThirdMax)
			{
					aThirdMax = MagBufArray[i]; 
					nThirdMax=i;
			}
	}
					
	 fThirdMax=(72000000)/((NPT*(ARR+1)*(PSC+1))/nThirdMax); //3次谐波频率

//	//波形判断
	k1=fabs(3*fMax/fThirdMax);        // 基波/3次谐波

//  LCD_ShowxNum(15,170,nMax,4,12,0);//二大值点
//	LCD_ShowxNum(15,190, aMax,4,12,0);//最大值电压
//	
//  LCD_ShowxNum(62,170,nfourdMax,6,12,0);//A波3次谐波点
//	LCD_ShowxNum(65,190,afourdMax,4,12,0);//A波3次谐波点电压
//			
//	LCD_ShowxNum(115,170,nSecondMax,4,12,0);//大值点
//	LCD_ShowxNum(115,190,aSecondMax,4,12,0);//二大值电压
//	
//	LCD_ShowxNum(160,170,nThirdMax,6,12,0);//3次谐波点
//	LCD_ShowxNum(165,190,aThirdMax,4,12,0);//B波3次谐波点电压
//	
//  LCD_ShowChar(215,190,'V',12,0);   //最大值电压

	m=fabs((float)(aSecondMax/aThirdMax));  //幅度比值 判断波形
	
	m2=fabs((float)(aMax/afourdMax));  //幅度比值 判断波形
	
	LCD_ShowString(1,100,40,20,24,"B:");
	
	
//输入波形B的判断	
	if(m<12)  //阈值判断
	{ 
		LCD_ShowString(50,100,80,20,24,"SanJiao");
		flag = 1;  
		
	}
 else
 { 
	 LCD_ShowString(50,100,80,20,24,"    Sin"); 
     flag = 2;
 }
		LCD_ShowString(1,130,40,20,24,"A:");
 
//输入波形A的判断	
	if(m2<16)  //阈值判断
	{ 
		LCD_ShowString(50,130,80,20,24,"SanJiao");
		flag1 = 1;
	}
 else
 { 
	 LCD_ShowString(50,130,80,20,24,"    Sin"); 
   flag1 = 2;
 }
boxing[bo]=flag1;
boxing[bo+2]=flag;
bo++;
 
FREQ1 = FreB<FreA?FreB:FreA;
FREQ =  FreB>FreA?FreB:FreA;
 
if(bo==2){
	
	if(boxing[0]==boxing[1]&&boxing[1]==1){AD9833_SetFrequency(AD9833_REG_FREQ0, AD9833_OUT_TRIANGLE,AD9833_FREQ(FREQ1));}	
    if(boxing[0]==boxing[1]&&boxing[1]==2){AD9833_SetFrequency(AD9833_REG_FREQ0, AD9833_OUT_SINUS, AD9833_FREQ(FREQ1));}

	if(boxing[2]==boxing[3]&&boxing[3]==1){AD98331_SetFrequency(AD9833_REG_FREQ0, AD9833_OUT_TRIANGLE,AD98331_FREQ(FREQ));}
	if(boxing[2]==boxing[3]&&boxing[3]==2){AD98331_SetFrequency(AD9833_REG_FREQ0, AD9833_OUT_SINUS, AD98331_FREQ(FREQ));}
}
 

bo%=2; 



}                                 

这个代码呢只能帮我们达到省一的水平(太可恶了由于笔者想提前准备考研了很多东西没有精讲,需要代码的小伙伴可以联系我哦) 就是下面这个东西(十分简陋)由于没有指导老师全程由我和队友一步一步琢磨真的痛苦。

fft stm32,stm32,单片机,嵌入式硬件

笔者能力有限 望指正

 

到了这里,关于利用STM32ZET6 进行FFT采集频率的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 基于STM32F407实现离散傅里叶变换(FFT、DFT),计算指定频率的幅值

    前言: 本人的课题是关于EIT采集系统设计,所谓的EIT,简单的说就是往人体注入特定频率的电流信号,通过采集反馈的电压信号,进而使用成像算法重构人体内部的阻抗分布。由于采集到的电压包含其它频率的热噪声,为了只保留注入频率的信号成分,需要对采集到的电压信

    2024年02月16日
    浏览(56)
  • 基于STM32F4的FFT+测频率幅值相位差,波形显示,示波器,时域频域分析相关工程

    一入电赛深似海,此话不假,个人感觉很累,但是收获确实多。本人去年参加了国赛,电赛提前半个月就开始着手准备了,只记得那时候不是调试就是在调试的路上,也因此留下了宝贵的我姑且称之为“经验”,作为一名小白,借此机会跟各位老白和小白分享一下。我训练较

    2024年02月03日
    浏览(48)
  • 【STM32】STM32F103ZET6程序下载(串口方式)

    STM32 的程序下载有多种方法:USB、串口、JTAG、SWD 等,这几种方式,都可以用来给 STM32 下载代码。不过,我们最常用的,最经济的,就是通过串口给 STM32 下载代码。我们将向大家介绍,如何利用串口给 STM32 下载代码。 STM32 的串口下载一般是通过串口 1 下载的,STM32开发板,

    2024年02月04日
    浏览(56)
  • STM32F103ZET6 驱动 OLED

    目录 前言 OLED模块的基本了解 OLED驱动程序的开发 前言 ​ 大家好,这是我第一次发帖,由于,我的技术并不成熟,程序难免有编写不规范的地方,希望读者能够指正,也希望这篇帖子能够让读者对OLED模块有个大致的了解。很高兴能与大家交流。 OLED模块的基本了解 OLED模块的

    2024年02月14日
    浏览(42)
  • 矩阵键盘之门锁小项目(stm32f103zet6)

    今天,给大家分享的是自己弄着玩的一个矩阵键盘控制控制舵机模拟一个智能门锁的功能。 用到的硬件: 开发板我用的是原子哥的精英板(杀鸡用宰牛刀了)。 矩阵键盘(宝上十几块买的): 外加一个舵机和一个0.96的oled显示屏。 矩阵键盘: 有VCC,GND,SCL,SDO四条线,这个矩阵键

    2024年02月10日
    浏览(46)
  • STM32F103ZET6+IIC+SGP30气体传感

    为了加深对IIC协议的理解和应用,接下来,使用STM32驱动SGP30气体传感传感器 准备IIC协议 用的还是之前的那些代码,只是把GPIO引脚口改了一下而已~~~ 1、声明GPIO和IIC初始化 2、IIC数据线的输出模式 因为在IIC数据线输出数据前,需要将数据线设置为输出模式才行 3、IIC数据线的

    2023年04月08日
    浏览(30)
  • AD7606与STM32F103ZET6的串行通信

      本文是AD7606与STM32的串行通信的学习心得,可帮助你快速入门AD7606。   图一   图二   图三   图四   根据图一,一些引脚在置高或置低时的上升或下降沿会受时间影响,因此在编写代码时,一些对引脚的操作需要放在一起,且延时函数不能随意使用。   图

    2024年02月08日
    浏览(65)
  • STM32F103ZET6+IIC+BH1750光照强度传感

    为了加深对IIC协议的理解和应用,接下来,使用STM32驱动BH1750光照强度传感器 准备IIC协议 其实IIC协议还是之前的驱动温湿度传感的那个,只是把GPIO引脚口改了一下,同理,之后遇到使用IIC协议驱动的传感器,都可以用这个定义的IIC协议。 1、声明GPIO和IIC初始化 2、IIC数据线的

    2023年04月11日
    浏览(44)
  • MDK Keil5 创建Stm32工程-理论篇(这里以Stm32F103Zet6为例)

    整个工程可以粗略的划分为几个文件夹: BSP 底层驱动比如GPIOTimer等驱动文件 CMSIS 内核相关的文件 Firmware 生成的固件下载文件 Mycode 用户编写的相关文件,主要编写的文件都在这个文件夹里 Project 工程文件 startup 芯片启动文件 STM32F10x_FWLib 标准库文件 USER 主函数等文件 readm

    2023年04月24日
    浏览(86)
  • STM32---stm32f103c8t6与stm32f103zet6之间的代码移植转换

    目录 一、将c8t6转换为zet6 1、修改启动文件 2、修改芯片 3、将MD修改为HD  4、下载器的修改 5、修改完成,编译成功  二、将zet6转换为c8t6 最终结果如下:   将STM32F103C8改为STM32F103ZE   基本相同,反向即可。    

    2024年02月06日
    浏览(56)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包