OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 1646|回复: 18

STM32F407运行FFT的一点问题

[复制链接]

1

主题

16

帖子

0

精华

初级会员

Rank: 2

积分
60
金钱
60
注册时间
2022-1-18
在线时间
18 小时
发表于 2023-12-18 16:25:12 | 显示全部楼层 |阅读模式
我是用TIMER+ADC+DMA做的采样,采样频率250kHz,做了4096个点的fft,先用一个3.3v的直流量(开发板上的3.3v输出)做测试但发现完全不对:按道理说应该在0处有最大幅值3.3x4096=13516.8
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

1

主题

16

帖子

0

精华

初级会员

Rank: 2

积分
60
金钱
60
注册时间
2022-1-18
在线时间
18 小时
 楼主| 发表于 2023-12-18 16:26:28 | 显示全部楼层
程序是这样的:
#include "adc.h"

//uint16_t ADC1_Data[ADC1_CHN];                      //AD采集滤波后的值
uint16_t ADC1_ConvertedValue[ADC1_Count];//DMA缓存


//初始化ADC                                                                                                                          
void  Adc1_Init(void)
{   
    GPIO_InitTypeDef      GPIO_InitStructure;
    ADC_CommonInitTypeDef ADC_CommonInitStructure;
    ADC_InitTypeDef       ADC_InitStructure;
    DMA_InitTypeDef       DMA_InitStructure;
    NVIC_InitTypeDef      NVIC_InitStructure;

    //始化ADC1通道IO口
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;                                //模拟输入
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;                           //不带上下拉

    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);                     
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    //ADC设置
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);                         //使能ADCx时钟
    RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1,ENABLE);                                //ADCx复位
    RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1,DISABLE);                              //复位结束       

    ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;                    //独立模式
    ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;//两个采样阶段之间的延迟5个时钟
    ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;     //DMA失能(对于多通道ADC模式)
    ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;                 //预分频4分频。ADCCLK=PCLK2/4=84/4=21Mhz,ADC时钟最好不要超过36Mhz
    ADC_CommonInit(&ADC_CommonInitStructure);

    ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;                      //12位模式
    ADC_InitStructure.ADC_ScanConvMode = ENABLE;                                //扫描模式(多通道ADC采集要使能扫描模式)
    #if TIM1_CH1_MODE==0
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;                          //连续转换
    ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None; //禁止触发检测,使用软件触发
    #else
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;                         //模数转换工作在单次转换模式
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;       //外部触发转换开启
    ADC_InitStructure.ADC_ExternalTrigConvEdge =ADC_ExternalTrigConvEdge_Rising;//上升沿触发
    #endif
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;                      //右对齐       
    ADC_InitStructure.ADC_NbrOfConversion =ADC1_CHN;                              //x个转换规则序列
    ADC_Init(ADC1, &ADC_InitStructure);

    ADC_RegularChannelConfig(ADC1, ADC_Channel_8, 1, ADC_SampleTime_15Cycles );
//    ADC_RegularChannelConfig(ADC1, ADC_Channel_9, 2, ADC_SampleTime_15Cycles );

    ADC_DMACmd(ADC1, ENABLE);                                                   //使能ADC_DMA

    ADC_Cmd(ADC1, ENABLE);                                                      //开启AD转换器       

    //DMA设置
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);

    DMA_InitStructure.DMA_Channel = DMA_Channel_0;                              //选择通道号
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(ADC1->DR);           //外围设备地址,ADC_DR_DATA规则数据寄存器
    DMA_InitStructure.DMA_Memory0BaseAddr = (u32)&ADC1_ConvertedValue;           //DMA存储器地址,自己设置的缓存地址
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;                     //传输方向,外设->存储器
    DMA_InitStructure.DMA_BufferSize = ADC1_CHN * ADC1_Count;                       //DMA缓存大小
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;            //外设是否为增量模式
    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模式,普通模式、循环模式,还有双缓冲模式,需要特殊设置
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;                         //DMA优先级,高优先级
    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;                      //不使用FIFO
    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;           //FIFO阈值
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;                 //存储器突发,单次传输
    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;         //外设突发,单次传输
    DMA_Init(DMA2_Stream0, &DMA_InitStructure);                                 //初始化DMA2_Streamx

    ADC_DMARequestAfterLastTransferCmd(ADC1, ENABLE);                           //连续使能DMA
    DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_TC);                             //清除中断标志   
    DMA_ITConfig(DMA2_Stream0, DMA_IT_TC, ENABLE);                              //传输完成中断                                       
    DMA_Cmd(DMA2_Stream0, ENABLE);                                              //使能DMA

    //设置DMA中断
    NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn;                     //DMA2_Streamx中断
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;                     //抢占优先级1
    NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;                           //子优先级0
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                             //IRQ通道使能
    NVIC_Init(&NVIC_InitStructure);

    #if TIM1_CH1_MODE==0
    ADC_SoftwareStartConv(ADC1);                                                //软件启动ADCx
    #else
    TIM1_Pwm_Adc_Init(TIM1_Pwm_Init_Data);                                      //定时器初始化
    #endif
}
void TIM1_Pwm_Adc_Init(u16 arr,u16 psc)//TIM1_Pwm_Init(7200,0)WM周期=(arr+1)*(psc+1)/Tclk=7200*1/72000000s=0.1ms
{  
        GPIO_InitTypeDef         GPIO_InitStructure;
        TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
        TIM_OCInitTypeDef        TIM_OCInitStructure;

        TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);               //设置缺省值,这一步最好加上防止放到串口初始化后出问题
        TIM_OCStructInit(&TIM_OCInitStructure);                       //设置缺省值,这一步最好加上防止放到串口初始化后出问题
        //TIM_ICStructInit(&TIM_ICInitStructure);                     //设置缺省值,这一步最好加上防止放到串口初始化后出问题

        RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);                //TIMx时钟使能
       
        TIM_TimeBaseStructure.TIM_Prescaler    =psc;                  //定时器分频
        TIM_TimeBaseStructure.TIM_CounterMode  =TIM_CounterMode_Up;   //向上计数模式
        TIM_TimeBaseStructure.TIM_Period       =arr;                  //自动重装载值
        TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
        TIM_TimeBaseInit(TIM1,&TIM_TimeBaseStructure);                //初始化定时器
       
        //初始化TIMx 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输出比较极性低
       
  //初始化TIMx Channelx
        TIM_OCInitStructure.TIM_Pulse = (arr+1)/2;                    //不为0和最大值,pwm占空比不为0和100 都可以
        TIM_OC1Init(TIM1, &TIM_OCInitStructure);                      //根据T指定的参数初始化外设TIMx OCx
        TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);             //使能TIMx在CCR1上的预装载寄存器

    TIM_ARRPreloadConfig(TIM1,ENABLE);                            //ARPE使能
    TIM_Cmd(TIM1, ENABLE);                                        //使能TIMx

    TIM_CtrlPWMOutputs(TIM1, ENABLE);                             //使能TIMx的PWM输出,高级定时器需要
}


void DMA2_Stream0_IRQHandler(void)  
{
    if(DMA_GetITStatus(DMA2_Stream0, DMA_IT_TCIF0))  //判断DMA传输完成中断  
    {
        DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_TCIF0);
    }
}
回复 支持 反对

使用道具 举报

1

主题

16

帖子

0

精华

初级会员

Rank: 2

积分
60
金钱
60
注册时间
2022-1-18
在线时间
18 小时
 楼主| 发表于 2023-12-18 16:26:55 | 显示全部楼层
#ifndef _ADC_H_
#define _ADC_H_
#include "./sys/sys.h"

#define ADC1_CHN         1                 //要使用的ADC通道数
#define ADC1_Count      4096                                //AD滤波采样次数
#define TIM1_CH1_MODE    1                  //1:定时器触发
#if TIM1_CH1_MODE==1       
#define  TIM1_Pwm_Init_Data  335,1      
#endif                                      

//extern vu16 ADC1_Data[ADC1_CHN];                      //AD采集滤波后的值
extern uint16_t ADC1_ConvertedValue[ADC1_Count];//DMA缓存

void Adc1_Init(void);                                 //ADC通道初始化
#if TIM1_CH1_MODE==1
void    TIM1_Pwm_Adc_Init(u16 arr,u16 psc);
#endif
回复 支持 反对

使用道具 举报

1

主题

16

帖子

0

精华

初级会员

Rank: 2

积分
60
金钱
60
注册时间
2022-1-18
在线时间
18 小时
 楼主| 发表于 2023-12-18 16:27:20 | 显示全部楼层
#include "stm32f4xx.h"
#include "./adc/adc.h"
#include <stdio.h>
#include "./usart/usart.h"
#include "stm32f4xx.h"
#include "arm_const_structs.h"
#include "stm32f4xx_conf.h"
#include "arm_math.h"
#include "./FFT/FFT.h"
#include "./SRAM/SRAM.h"

#define FFT_length 4096
#define SRAM_BASE_ADDR ((u32)(0x68000000))
extern uint16_t ADC1_ConvertedValue[ADC1_Count];
float AD1[4096];
float fft_outputbuf[4096],fft_inputbuf[4096*2]__attribute__((at(SRAM_BASE_ADDR)));//存放输入输出幅值


/*延时函数*/

static u8  fac_us=0;                                                        //us延时倍乘数
//延时nus
//nus为要延时的us数.
void delay_init(void)
{
        SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);        //选择外部时钟  HCLK/8
        fac_us=SystemCoreClock/8000000;                                //为系统时钟的1/8
       
}

void delay_us(u32 nus)
{               
        u32 temp;                     
        SysTick->LOAD=nus*fac_us;                                         //时间加载                           
        SysTick->VAL=0x00;                                                //清空计数器
        SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;        //开始倒数          
        do
        {
                temp=SysTick->CTRL;
        }while((temp&0x01)&&!(temp&(1<<16)));                //等待时间到达   
        SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;        //关闭计数器
        SysTick->VAL =0X00;                                               //清空计数器         
}
/****************************************/

int main(void)
{
        /* 在这里添加你的程序 */
       
        delay_init();
       
  Debug_USART_Config();
        FSMC_Config();
  Adc1_Init();
        arm_cfft_radix4_instance_f32 scfft;
        arm_cfft_radix4_init_f32(&scfft,4096,0,1);
       
  delay_us(100);

        while(1)
        {
                for(int idex=0;idex<4096;idex++)
                {       
                        fft_inputbuf[2*idex]=(u16)(ADC1_ConvertedValue[idex])*(3.3/4096);    //生成输入信号实部
                        fft_inputbuf[2*idex+1]=0;//虚部全部为0
                }
//                for(int i=0;i<4096;i++)//生成信号序列
//                        {
//                                 fft_inputbuf[2*i]=100+
//                                                   10*arm_sin_f32(2*PI*i/4096)+
//                                                                   30*arm_sin_f32(2*PI*i*4/4096)+
//                                                   50*arm_cos_f32(2*PI*i*200/4096);        //生成输入信号实部
//                                 fft_inputbuf[2*i+1]=0;//虚部全部为0
//                        }
    arm_cfft_radix4_f32(&scfft,fft_inputbuf);  //fft运算
                arm_cmplx_mag_f32(fft_inputbuf,fft_outputbuf,4096);
                       
                float32_t Sum_f=0;
                for(int i1=0; i1<4096; i1++)
                {
                        printf("fft_outputbuf[%d]: %f\r\n",i1,fft_outputbuf[i1]);
                }
      for( int I=11;I<201;I++)
      {
        Sum_f=0+fft_outputbuf[I];
      }
      printf("Current: %.4f\r\n",Sum_f);
        }
               
}
回复 支持 反对

使用道具 举报

1

主题

16

帖子

0

精华

初级会员

Rank: 2

积分
60
金钱
60
注册时间
2022-1-18
在线时间
18 小时
 楼主| 发表于 2023-12-18 16:29:52 | 显示全部楼层
输出是这样的,不太对
QQ图片20231218162912.png
回复 支持 反对

使用道具 举报

1

主题

16

帖子

0

精华

初级会员

Rank: 2

积分
60
金钱
60
注册时间
2022-1-18
在线时间
18 小时
 楼主| 发表于 2023-12-19 10:10:20 | 显示全部楼层
自顶一下
回复 支持 反对

使用道具 举报

1

主题

16

帖子

0

精华

初级会员

Rank: 2

积分
60
金钱
60
注册时间
2022-1-18
在线时间
18 小时
 楼主| 发表于 2023-12-22 14:32:56 | 显示全部楼层
来个大佬看看吧
回复 支持 反对

使用道具 举报

3

主题

1906

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
4100
金钱
4100
注册时间
2018-8-14
在线时间
695 小时
发表于 2023-12-22 14:53:05 | 显示全部楼层
我认为问题应该在这一句
fft_inputbuf[2*idex]=(u16)(ADC1_ConvertedValue[idex])*(3.3/4096);  
改成这样试试
fft_inputbuf[2*idex]=(float)ADC1_ConvertedValue[idex]*3.3/4096;    //生成输入信号实部
回复 支持 反对

使用道具 举报

1

主题

16

帖子

0

精华

初级会员

Rank: 2

积分
60
金钱
60
注册时间
2022-1-18
在线时间
18 小时
 楼主| 发表于 2023-12-22 16:20:58 | 显示全部楼层
edmund1234 发表于 2023-12-22 14:53
我认为问题应该在这一句
fft_inputbuf[2*idex]=(u16)(ADC1_ConvertedValue)*(3.3/4096);  
改成这样试试
...

谢谢大佬的解答,改掉了这边的问题再测试了一下,发现现在直流量没有问题了,但是我测试一个直流偏置为1.5v幅值为1v频率为20kHz的正弦波的时候发现还是不太对。按照采样规则,4096个点每一个格的频率分辨率大概在61Hz,那么20kHz对应的应该是第328个点,但是极大值出现在了1966,幅值对不上我倒是可以理解为出现频谱泄露(,大佬能不能再帮忙看看啥问题,采样频率我拿示波器测试了定时器PWM波的频率是250kHz没错
QQ图片20231222161530.png
QQ图片20231222161438.png
回复 支持 反对

使用道具 举报

1

主题

16

帖子

0

精华

初级会员

Rank: 2

积分
60
金钱
60
注册时间
2022-1-18
在线时间
18 小时
 楼主| 发表于 2023-12-22 16:23:22 | 显示全部楼层
采样频率测量
QQ图片20231222162243.png
回复 支持 反对

使用道具 举报

3

主题

1906

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
4100
金钱
4100
注册时间
2018-8-14
在线时间
695 小时
发表于 2023-12-22 17:04:09 | 显示全部楼层
蒼蒼 发表于 2023-12-22 16:20
谢谢大佬的解答,改掉了这边的问题再测试了一下,发现现在直流量没有问题了,但是我测试一个直流偏置为1. ...

算错了吧, 第一格频率是0(即直流分量), 最后一格是取样频率/2, 再算算
回复 支持 反对

使用道具 举报

1

主题

16

帖子

0

精华

初级会员

Rank: 2

积分
60
金钱
60
注册时间
2022-1-18
在线时间
18 小时
 楼主| 发表于 2023-12-22 17:20:30 | 显示全部楼层
edmund1234 发表于 2023-12-22 17:04
算错了吧, 第一格频率是0(即直流分量), 最后一格是取样频率/2, 再算算

每一格对应的采样频率是250000/4096=125000/2048=61.035,那么20000/61=327.8,那么在327格左右得到最大值,这样算有错嘛orz
回复 支持 反对

使用道具 举报

1

主题

16

帖子

0

精华

初级会员

Rank: 2

积分
60
金钱
60
注册时间
2022-1-18
在线时间
18 小时
 楼主| 发表于 2023-12-22 17:22:57 | 显示全部楼层
蒼蒼 发表于 2023-12-22 17:20
每一格对应的采样频率是250000/4096=125000/2048=61.035,那么20000/61=327.8,那么在327格左右得到最大值 ...

因为4096个点前后2048个点对称,所以只看前2048个点,然后奈奎斯特采样定理250kHz的采样频率最大能分辨125kHz的频率,所以125000/2048=61
回复 支持 反对

使用道具 举报

3

主题

1906

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
4100
金钱
4100
注册时间
2018-8-14
在线时间
695 小时
发表于 2023-12-23 00:36:06 | 显示全部楼层
蒼蒼 发表于 2023-12-22 17:20
每一格对应的采样频率是250000/4096=125000/2048=61.035,那么20000/61=327.8,那么在327格左右得到最大值 ...

是我搞错, 20KHz 应该在[328], 你是怎样注入20KHz信号的?
回复 支持 反对

使用道具 举报

1

主题

16

帖子

0

精华

初级会员

Rank: 2

积分
60
金钱
60
注册时间
2022-1-18
在线时间
18 小时
 楼主| 发表于 2023-12-23 20:30:24 | 显示全部楼层
edmund1234 发表于 2023-12-23 00:36
是我搞错, 20KHz 应该在[328], 你是怎样注入20KHz信号的?

就是用信号发生器产生了一个20kHz的正弦波
回复 支持 反对

使用道具 举报

1

主题

16

帖子

0

精华

初级会员

Rank: 2

积分
60
金钱
60
注册时间
2022-1-18
在线时间
18 小时
 楼主| 发表于 2023-12-23 20:31:16 | 显示全部楼层
蒼蒼 发表于 2023-12-23 20:30
就是用信号发生器产生了一个20kHz的正弦波

通过杜邦线连接IO口采样
回复 支持 反对

使用道具 举报

1

主题

16

帖子

0

精华

初级会员

Rank: 2

积分
60
金钱
60
注册时间
2022-1-18
在线时间
18 小时
 楼主| 发表于 2023-12-25 12:09:05 | 显示全部楼层
再顶一顶
回复 支持 反对

使用道具 举报

8

主题

31

帖子

0

精华

初级会员

Rank: 2

积分
72
金钱
72
注册时间
2022-1-6
在线时间
38 小时
发表于 2024-9-8 09:25:53 | 显示全部楼层
楼主解决了吗,我测试也感觉问题好大,离谱的是那个点都没有峰值
回复 支持 反对

使用道具 举报

12

主题

156

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
2548
金钱
2548
注册时间
2017-12-16
在线时间
187 小时
发表于 2024-9-9 12:15:40 | 显示全部楼层
楼主没说说  结果,我想学学
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-11-22 08:41

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表