目录
一、硬件设备
信号发生器(用于测试)或者任意其他输入信号、STM32F407ZGT6系列单片机、若干杜邦线、ST-Link烧录器
二、大概思路
1.ADC频率采样所输入信号,DMA读取传输
2.使用STM官网所给的DSP库对信号进行FFT频谱处理
3.对频谱分析得的结果进一步分析,得到输入信号正弦波的频率大小
4.OLED屏或者TFT屏显示
三、主要程序
(一)ADC采样和DMA读取传输
.c文件
1.配置ADC的GPIO
#include "adc_dma_timer.h"
u16 ADC1_ConvertedValue[ ADC1_DMA_Size ];
//配置ADC的GPIO:PB1(ADC1的通道9)
void ADC_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB , ENABLE );
//PB0 - ADC1_IN8
//PB1 - ADC1_IN9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init( GPIOB , &GPIO_InitStructure );
}
2.配置ADC的TIM3
//配置ADC的TIM3
//Fre为ADC的采样频率
void TIM3_Config( u32 Fre )
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
u32 MD;
u16 div=1;
while( (SystemCoreClock/2/Fre/div)>65535 )
{
div++;
}
MD = SystemCoreClock/2/Fre/div - 1;
RCC_APB1PeriphClockCmd( RCC_APB1Periph_TIM3 , ENABLE ); //开启TIM3时钟
//TIM3配置
TIM_TimeBaseStructure.TIM_Period = MD ;
TIM_TimeBaseStructure.TIM_Prescaler = div-1;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit( TIM3 , &TIM_TimeBaseStructure );
TIM_SelectOutputTrigger( TIM3 , TIM_TRGOSource_Update );
TIM_ARRPreloadConfig( TIM3 , ENABLE );
TIM_Cmd( TIM3 , ENABLE ); //使能TIM3
}
3.配置ADC的DMA
ADC1对应的DMA2通道
#define ADC1_DR_ADDRESS ((uint32_t)0x4001204C) //DMA读取的ADC1地址
//ADC-DMA配置
//Size为单次传输的数据量
void ADC_DMA_Trig( u16 Size )
{
DMA2_Stream0->CR &= ~((uint32_t)DMA_SxCR_EN); //将DMA2的stream0线清零
DMA2_Stream0->NDTR = Size; //设置DMA2的stream0
DMA_ClearITPendingBit( DMA2_Stream0 ,DMA_IT_TCIF0|DMA_IT_DMEIF0|DMA_IT_TEIF0|DMA_IT_HTIF0|DMA_IT_TCIF0 );
ADC1->SR = 0;
DMA2_Stream0->CR |= (uint32_t)DMA_SxCR_EN;//使能DMA2的stream0线
}
//ADC—DMA相关配置
//ADC、DMA和NVIC中断的初始化
void ADC_Config(void)
{
ADC_InitTypeDef ADC_InitStructure;
ADC_CommonInitTypeDef ADC_CommonInitStructure;
DMA_InitTypeDef DMA_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
ADC_DeInit( );
DMA_DeInit( DMA2_Stream0 );
RCC_AHB1PeriphClockCmd( RCC_AHB1Periph_DMA2 , ENABLE );
RCC_APB2PeriphClockCmd( RCC_APB2Periph_ADC1 , ENABLE );
NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent; //独立模式·1
ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2; //2分频
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
ADC_CommonInit( &ADC_CommonInitStructure );
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //不使能循环扫描
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//配置为单次转换
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;选择上升沿触发
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO; //选择TIM3的ADC触发
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //右对齐
ADC_InitStructure.ADC_NbrOfConversion = 1;
ADC_Init( ADC1, &ADC_InitStructure );//初始化ADC
ADC_RegularChannelConfig( ADC1 , ADC_Channel_9 , 1, ADC_SampleTime_3Cycles);
ADC_DMARequestAfterLastTransferCmd( ADC1 , ENABLE );
ADC_DMACmd(ADC1, ENABLE);
ADC_Cmd(ADC1, ENABLE);
ADC_SoftwareStartConv(ADC1);
DMA_InitStructure.DMA_Channel = DMA_Channel_0;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)ADC1_DR_ADDRESS;//DMA读取的目的地
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)ADC1_ConvertedValue; //MDA输出的地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
DMA_InitStructure.DMA_BufferSize = ADC1_DMA_Size; //DMA 传输数量
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //单次传输(可以改为循环)
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; // 分组
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_Init( DMA2_Stream0 , &DMA_InitStructure );
DMA_Cmd( DMA2_Stream0 , DISABLE );
DMA_ClearITPendingBit( DMA2_Stream0 ,DMA_IT_TCIF0 );
DMA_ITConfig( DMA2_Stream0 , DMA_IT_TC , ENABLE );
}
//DMA中断配置
void DMA2_Stream0_IRQHandler( void )
{
DMA_ClearITPendingBit( DMA2_Stream0 ,DMA_IT_TCIF0|DMA_IT_DMEIF0|DMA_IT_TEIF0|DMA_IT_HTIF0|DMA_IT_TCIF0 );
DMA2->HIFCR = 0xffff;
DMA2->LIFCR = 0xffff;
}
.h文件
#ifndef _ADC_DMA_Timer_H
#define _ADC_DMA_Timer_H
#include "sys.h"
#include "STM32F4xx_ADC.h"
#define SAM_FRE 1024000//采样频率
#define ADC1_DMA_Size 1024 //采样点数
extern u16 ADC1_ConvertedValue[ ADC1_DMA_Size ];
void ADC_GPIO_Init(void);
void TIM3_Config( u32 Fre );
void ADC_Config( void );
void ADC_DMA_Trig( u16 Size );
#endif
(二)主函数(FFT变换)
在STM官网下载407系列单片机的DSP固件库,在Keil中添加相关的库
main.c
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "lcd.h"
#include "adc_dma_timer.h"
#include "key.h"
#include "timer.h"
#include "math.h"
#include "arm_math.h"
#include "lcd.h"
#include "lcd_init.h"
#define FFT_LENGTH 1024 //FFT长度
float fft_inputbuf[FFT_LENGTH*2]; //FFT输入数组
float fft_outputbuf[FFT_LENGTH]; //FFT输出数组
float fft_outputbufhalf[FFT_LENGTH/2];
u32 i=0;
int newvalue = 0;
int a;
int main(void)
{
arm_cfft_radix4_instance_f32 scfft;
u8 key,t=0;
float time;
u16 i;
int max1,max2 ;
float max;
int n=0;
float A1,A2;
//相关函数的初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
delay_init(168);
uart_init(115200);
LED_Init();
KEY_Init();
ADC_GPIO_Init();
TIM3_Config( SAM_FRE );
ADC_Config();
arm_cfft_radix4_init_f32(&scfft,FFT_LENGTH,0,1);//³õʼ»¯scfft½á¹¹Ì壬É趨FFTÏà¹Ø²ÎÊý
while(1)
{
key=KEY_Scan(0);
if(key==KEY0_PRES)
{ ADC_DMA_Trig( ADC1_DMA_Size ); // 开启ADC采集
delay_ms(20); // 延时20ms。让采样到的数据传输到数组中
//FFT(傅里叶变换)
for(i=0;i<FFT_LENGTH;i++)//生产信号序列
{
fft_inputbuf[2*i] = (float)ADC1_ConvertedValue[ i ]*3.3f/4096.0f;//ADC实部
//
fft_inputbuf[2*i+1]=0;//虚部为0
}
arm_cfft_radix4_f32(&scfft,fft_inputbuf); //进行FFT计算
arm_cmplx_mag_f32(fft_inputbuf,fft_outputbuf,FFT_LENGTH); //求模值
//将FFT计算的结果通过串口显示在电脑上
// printf("%f\n",fft_outputbufhalf[0]);
for(i = 0;i<FFT_LENGTH/2;i++)
{
fft_outputbufhalf[i]=fft_outputbuf[i];
fft_outputbufhalf[0]= 0.0;
printf("fft_outputbufhalf[%d]:%f\r\n",i,fft_outputbufhalf[i]);
}
// 适用排序法选出两个正弦的基波分量
for( i=0;i<512;i++)
{
if(max<fft_outputbufhalf[i])
{
max = fft_outputbufhalf[i];
max1 = i;
}
}
fft_outputbufhalf[max1]= 0.0,max = 0.0;
for( i=0;i<512;i++)
{
fft_outputbufhalf[0]= 0.0;
if(max<fft_outputbufhalf[i])
{
max =fft_outputbufhalf[i];
max2= i;
}
}
}
else delay_ms(10);
t++;
if((t%10)==0)LED0=!LED0;
}
}
(三)LCD显示
这里用的TFT屏进行显示
主函数中的max1、max2的大小乘以1K即为输入正弦信号的频率大小
四、附件功能(单片机DAC口输入对应的波形)或DDS复现信号
运用单片机的DAC口或DDS对识别的信号进行复现,使用DAC口输出信号需要用硬件电路进行处理才能得到稳定的信号。
五、FFT原理
正弦信号进行FFT变换后会有较为明显的特征,在所对应的频率处会有一个较大的值,这个值所对应的频率即为正弦信号的频率,这里是以10K的正弦信号为例。