STM32 FFT DMA ADC THD

您所在的位置:网站首页 总谐波失真怎么计算 STM32 FFT DMA ADC THD

STM32 FFT DMA ADC THD

2024-07-15 07:34| 来源: 网络整理| 查看: 265

利用STM32 FFT算法计算THD 一、设备准备

——>粤嵌STM32F429IGT6开发板 1块 ——>串口调试助手

二、FFT算法意义

使用FFT算法,是为了获取信号在频域的相关参数,即信号的频谱。包括信号在频谱上各点的频率和该点的幅值。 由上面的调制信号和已调信号的频谱图,我们可以观察到正弦信号在未经过调制前,其频谱图仅仅在0频附近有一条谱线,这条谱线即为未调制信号的频谱。同时,我们可以观察到调制后的正弦信号的频谱发生了搬移,即从零频附近搬移到载波信号的频谱处。 首先让我们理解一个概念,信号的组成:任何连续测量的时序或信号,都可以表示为不同频率的正弦波信号的无限叠加。而根据该原理创立的傅立叶变换算法利用直接测量到的原始信号,以累加方式来计算该信号中不同正弦波信号的频率、振幅和相位。 其实,简单的理解,观察信号的角度从时域转换到频域,对观察者最大的好处是:在频域,信号的频谱图能够反映该信号所含的各种频率成分的信号及它们的幅值。 傅里叶级数的意义

三、DSP实际使用方法

我们使用ST公司提供的DSP库中的FFT算法。 使用步骤如下:

- STM32F4 DSP简介

STM32F4采用Cortex-M4内核,相比Cortex-M3系列除了内置硬件FPU单元(浮点运算单元),FPU是专用于浮点运算的处理器,在数字信号处理方面还增加了DSP指令集,支持诸如单周期乘加指令(MAC),优化的单指令多数据指令(SIMD),饱和算数等多种数字信号处理指令集。Cortex-M4执行所有的DSP指令都可以在单周期内完成,而Cortex-M3需要多个指令和多个周期才能完成同样的功能。

- 下载DSP_Lib源码包

找到DSP_Lib源码包,该文件夹目录结构如下图所示: 在这里插入图片描述 DSP_Lib源码包的Source文件夹是所有DSP库的源码,Examples文件夹是相对应的一些测试实例。这些测试实例都是带main函数的,也就是拿到工程中可以直接使用。 在这里插入图片描述 查阅原子STM32F4开发指南-寄存器版本,P699页,了解各种源码的功能介绍。

- 搭建DSP运行环境 只要DSP库运行环境搭建好了,使用DSP库里面的函数来做相关处理就非常简单。在MDK里面搭建STM32F4的DSP运行环境(使用.lib方式)是很简单的,分为3个步骤:

1、添加文件 在例程工程目录下新建:DSP_LIB文件夹,存放要添加的arm_correxM4lf_math.lib和相关头文件。 在这里插入图片描述 在这里插入图片描述

打开工程,新建DSP_LIB分组,并将arm_cortexM4lf_math.lib添加到工程里面。

在这里插入图片描述 如上面两图所示,这样添加文件就结束了。

2、添加头文件包含路径。 添加好了.lib文件后,我们要添加头文件包含路径,将第一步拷贝的Include文件夹和DSP_LIB文件夹,加入头文件包含路径,如下图所示。 在这里插入图片描述 3.添加全局变量宏 在这里插入图片描述 注意:这里两个宏之间用“,”隔开。这里我添加的宏包括:USE_STDPERIPH_DRIVER,STM32F429_439xx,ARM_MATH_CM4,__CC_ARM,ARM_MATH_MATRIX_CHECK,ARM_MATH_ROUNDING,如果没有在Target选项卡设置Code Generation选择use FPU,则必须在此处手动添加_FPU_USED。

这样,STM32F4DSP库运行环境就搭建完成了。

四、代码介绍

我采用在时域对信号进行采样,将时域采样数据进行4096点FFT变换,得到信号各频率成分的幅值,最后计算该信号的THD。

1.DMA介绍 DMA: 全称为: Direct Memory Access,即直接存储器访问。 DMA 传输方式无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路, 能使 CPU 的效率大为提高。 STM32 最多有 2 个 DMA 控制器(DMA2 仅存在大容量产品中), DMA1 有 7 个通道。 DMA2 有 5个通道。每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁起来协调各个 DMA 请求的优先权。

2.编程思想 编程采用ADC+DMA+定时器触发的方式,简单的理解就是定时器具有生成PWM信号的功能,该脉冲信号在上升沿会触发ADC采样,采集的数据会直接被存储在用户自定义的内存单元中,当每次采集完4096个点的采样数据后,才会被再一次传输到用户自定义内存单元,即进行采样数据的更新。

3.采样定理介绍 在进行模拟/数字信号的转换过程中,当采样频率fs.max大于信号中最高频率fmax的2倍时(fs.max>2fmax),采样之后的数字信号完整地保留了原始信号中的信息,一般实际应用中保证采样频率为信号最高频率的2.56~4倍;采样定理又称奈奎斯特定理。即当满足采样定理时,可以不失真的恢复原信号,或者说得到原始信号的有用信息。

采用ADC+DMA+定时器的方式实现信号的采样时,采样频率由定时器的频率决定,需要注意的是,定时器的频率必须小于ADC频率。关于这个问题可以看这里:STM32定时触发ADC 采样频率等问题总结

4、ADC配置

ADC_InitTypeDef ADC_InitStructure; ADC_CommonInitTypeDef ADC_CommonInitStructure; // 开启ADC时钟 RCC_APB2PeriphClockCmd(RHEOSTAT_ADC_CLK , ENABLE); // -------------------ADC Common 结构体 参数 初始化------------------------ // 独立ADC模式 ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent; // 时钟为fpclk x分频 ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4; // 禁止DMA直接访问模式 ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; // 采样时间间隔 ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles; ADC_CommonInit(&ADC_CommonInitStructure); // -------------------ADC Init 结构体 参数 初始化-------------------------- ADC_StructInit(&ADC_InitStructure); // ADC 分辨率 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; //数据右对齐 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //转换通道 1个 ADC_InitStructure.ADC_NbrOfConversion = 1; ADC_Init(RHEOSTAT_ADC, &ADC_InitStructure); //--------------------------------------------------------------------------- // 配置 ADC 通道转换顺序为1,第一个转换,采样时间为3个时钟周期 ADC_RegularChannelConfig(RHEOSTAT_ADC, RHEOSTAT_ADC_CHANNEL, 1,ADC_SampleTime_15Cycles); // 使能DMA请求 after last transfer (Single-ADC mode) ADC_DMARequestAfterLastTransferCmd(RHEOSTAT_ADC, ENABLE); // 使能ADC DMA ADC_DMACmd(RHEOSTAT_ADC, ENABLE); // 使能ADC ADC_Cmd(RHEOSTAT_ADC, ENABLE); //开始adc转换,软件触发 // ADC_SoftwareStartConv(RHEOSTAT_ADC); }

5、DMA配置

DMA_InitTypeDef DMA_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; // ------------------DMA Init 结构体参数 初始化-------------------------- // ADC1使用DMA2,数据流0,通道0,这个是手册固定死的 // 开启DMA时钟 RCC_AHB1PeriphClockCmd(RHEOSTAT_ADC_DMA_CLK, ENABLE); // 外设基址为:ADC 数据寄存器地址 DMA_InitStructure.DMA_PeripheralBaseAddr = RHEOSTAT_ADC_DR_ADDR; // 存储器地址,实际上就是一个内部SRAM的变量 DMA_InitStructure.DMA_Memory0BaseAddr = (u32)&ADC_ConvertedValue; // 数据传输方向为外设到存储器 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; // 缓冲区大小为,指一次传输的数据量 DMA_InitStructure.DMA_BufferSize = 4096; // 外设寄存器只有一个,地址不用递增 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_Circular; // DMA 传输通道优先级为高,当使用一个DMA通道时,优先级设置不影响 DMA_InitStructure.DMA_Priority = DMA_Priority_High; // 禁止DMA FIFO ,使用直连模式 DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; // FIFO 大小,FIFO模式禁止时,这个不用配置 DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; // 选择 DMA 通道,通道存在于流中 DMA_InitStructure.DMA_Channel = RHEOSTAT_ADC_DMA_CHANNEL; //初始化DMA流,流相当于一个大的管道,管道里面有很多通道 DMA_Init(RHEOSTAT_ADC_DMA_STREAM, &DMA_InitStructure); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); // 使能DMA流 DMA_ITConfig(DMA2_Stream0,DMA_IT_TC,ENABLE); DMA_Cmd(RHEOSTAT_ADC_DMA_STREAM, ENABLE);

6、定时器配置

一般情况下,定时器触发ADC转换并不需要中断,因此不需要配置定时器的中断优先级。

//使用通用定时器 触发ADC采样 TIM6,由于时钟频率不清楚,本身进行的配置 static void TIM_Mode_Config(void) { //NVIC_InitTypeDef NVIC_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; // 开启TIMx_CLK,x[6,7] RCC_APB1PeriphClockCmd(GENERAL_TIM_CLK, ENABLE); /* 累计 TIM_Period个后产生一个更新或者中断*/ //定时器初值,采样间隔0.05/1024s,初值为(0.05/1024)/(1/10M)=488 //TIM_TimeBaseStructure.TIM_Period = 81 //(原始设置的数值) TIM_TimeBaseStructure.TIM_Period = 3515; // 通用控制定时器时钟源TIMxCLK = HCLK/2=90MHz // 设定定时器频率为=TIMxCLK/(TIM_Prescaler+1)=10MHz TIM_TimeBaseStructure.TIM_Prescaler = 0; // 采样时钟分频 TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; // 计数方式 TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; // 初始化定时器TIMx, x[1,8] TIM_TimeBaseInit(GENERAL_TIM, &TIM_TimeBaseStructure); // 清除定时器更新中断标志位 // TIM_ClearFlag(GENERAL_TIM, TIM_FLAG_Update); // 开启定时器更新中断 // TIM_ITConfig(GENERAL_TIM,TIM_IT_Update,ENABLE); TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update); //使能定时器 //TIM_Cmd(GENERAL_TIM, ENABLE); //使能定时器 //TIM_Cmd(GENERAL_TIM, DISABLE); }

7、进行FFT

//在main.c文件中调用,进行ADC DMA 定时器的先相关初始化 void Rheostat_Init(void) { Rheostat_ADC_GPIO_Config(); Rheostat_ADC_Mode_Config(); // TIMx_NVIC_Configuration(); TIM_Mode_Config(); } //使能ADC DMA FFT功能,需要进行FFT运算时,调用该语句 void ENABLE_deinit(void) { TIM_Cmd(GENERAL_TIM, ENABLE); ADC_Cmd(RHEOSTAT_ADC, ENABLE); DMA_Cmd(RHEOSTAT_ADC_DMA_STREAM, ENABLE); } //失能ADC DMA FFT功能 void DISABLE_deinit(void) { TIM_Cmd(GENERAL_TIM, DISABLE); ADC_Cmd(RHEOSTAT_ADC, DISABLE); DMA_Cmd(RHEOSTAT_ADC_DMA_STREAM,DISABLE); } void DMA2_Stream0_IRQHandler(void) { u16 i; if(DMA_GetITStatus(DMA2_Stream0, DMA_IT_TCIF0)) //是否进入中断 { DISABLE_deinit(); sprintf(dispBuff,"%d fft is running... please wait",counts0++); LCD_ClearLine(LINE(2)); LCD_DisplayStringLine(LINE(2),(uint8_t* )dispBuff); //在第三行显示THD if(DMA_GetCurrentMemoryTarget(DMA2_Stream0) == DMA_Memory_0)// { for(i=0;i


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3