[STM32F4] STM32F407三重ADC交替采样+DMA+FFT学习笔记

[复制链接]
1070|24
Zuocidian 发表于 2025-9-6 08:01 | 显示全部楼层 |阅读模式
一.时钟配置
时钟就使用外部高速时钟了

7668068baa1403e9e8.png


时钟树的配置就要开始考虑ADC了,查看手册,ADC的最大时钟周期是36Mhz

9549668baa147750b6.png


ADC挂在APB2时钟下,为了达到最大采样率,如下配置时钟树

7866468baa14f0cfa9.png


对ADC时钟2分频刚好是36Mhz

2484168baa1569ea89.png


二.定时器配置
定时器选择TIM8,选择内部时钟到TRGO触发ADC这条路

3803068baa15e7aae3.png


这里要注意分频和重装载值的设置,也要结合ADC采样来看
再看一下手册:

9303968baa1691fe33.png


在最快单个ADC转换即实现最大单个ADC采样率的情况下ADC的采样时间是3个ADC时钟周期,转换时间是12个ADC时钟周期,想要实现ADC的均匀采样,如下图

8848068baa1733f6be.png


那么定时器就要每15/144000000 触发一下ADC采样,就不给定时器分频了直接用定时器144Mhz时钟周期来计算重装载值来配置(选择向上计数)

X/144=15/36
X=15*144/36=60

看下配置

615868baa17c8d355.png


注意打开自动重装,TRGO(触发输出)设置成更新事件

三.ADC和DMA配置
配置三重ADC首先要把三个ADC都打开才能选三重交替的模式(ADC的交替模式只用于规则组),ADC2和ADC3在ADC1选好模式后不用再做更改,三个ADC选同一个通道就好,可以看下这篇:STM32——三重ADC交替采样—极限采样率7.2M,ok看下配置:

3301068baa1848153d.png


再看一下DMA的配置

4592968baa18b83366.png


同样也只需要在ADC1配置好就行

6697868baa191baac1.png


记得在ADC1配置使能DMA的连续传输

4329668baa1993f2b2.png


配置就完成了

四.FFT
几个数据:
Fs:采样率,即ADC采样率,根据上面的配置为7.2M
N:采样点数
Ts:采样一个点所花时间(Ts=1/Fs)
T:采样N个点所花时间(T=N*Ts)
dF:频谱分辨率,即两个数据点的间隔代表多少频率(dF=1/T=Fs/N)
采样的分辨率越小,越能捕捉到细微的频率变化,减少栅栏效应。在分辨率确定的情况下,能够提供的fft数组越大越好。

fft时经常会看到1024,2048这样的数组长度,这是因为这里fft是一种基2的算法,数组长度为2的幂使其可以反复对半拆分,提高运算效率。我们用到7.2M的采样率,想要达到比较细致的分辨率就需要很大的数组了,就给一个4096极限的fft数组,先看一下怎么进行DMA采集数据的转换。

把DMA传输过来的数据存在uint32_t的数组里,从上面那种DMA传输模式可以看出它是把两个通道的ADC数据数据拼在一起了,正常ADC采集的顺序是 ADC1 ADC2 ADC3 ADC1 ADC2 ADC3 ,然后DMA每次把第一个数存低位,下一个存高位,再下一个存下一个DMA数据的低位,以此类推,最后的DMA数据是(ADC2 ADC1)(ADC1 ADC3)(ADC3 ADC2)…现在把DMA拼接的数据拆回去,fft用float运算,这里就直接转换成float型了,如下(进行宏定义#define ADC_DMA_SIZE 2048,则DMA数据拆开后为4096个数据

void dma_data_process(void)
{
  for(uint16_t i = 0;i<ADC_DMA_SIZE;i++)
        {
        adc_buffer[2*i] = (float)(adc_dma_buffer&0x0000ffff);
        adc_buffer[2*i+1] =(float)( (adc_dma_buffer&0xFFFF0000)>>16);
               
        }
}


再来说加窗处理,可以先看看这一篇傅里叶变换学习笔记(二)——栅栏效应、频谱泄漏与加窗
加窗是一种防值频谱泄露的方法,频谱泄露,就比如,现在的ADC处理分辨率约为1.757k,如果来一个10K的正弦波,它的基频10K不是恰好落在 FFT 的任何一个频率点上,其能量不会集中在某一个点,而是会向相邻的频率点(主要是 8.785kHz 和 10.542kHz,可能会向更远频点扩散)扩散,需要进行加窗处理,加窗会加宽主瓣降低旁瓣,如果两个频率接近的信号混在一起,加窗之后可能会无法分清两个信号,不同情况去选择加不加窗,这里就选择汉宁窗了,直接对fft数组乘一个系数就好,fft前数据拆成实部虚部,计算汉宁窗系数在主函数进行,只计算一次,(汉宁窗系数对fft每个点去乘,相当于只乘了实部)对数据再进行一次处理,如下

hannwindow在主函数只算一次,不放fft_process里
                for(int i=0; i<2*ADC_DMA_SIZE; i++)
                {hanning = 0.5f * (1 - cosf(2*PI*i/(2*ADC_DMA_SIZE-1)));}       


void fft_data_process(void)
{
  for(uint16_t i = 0;i<2*ADC_DMA_SIZE;i++){
    fft_buffer[2*i] =(float)(hanning*adc_buffer);
    fft_buffer[2*i+1] = 0.0f;  
}
}




五.补充
添加好DSP库就可以开始补充代码了,首先打开定时器,ADC,(不需要打开ADC1)

  HAL_ADC_Start(&hadc2);
  HAL_ADC_Start(&hadc3);

  HAL_ADCEx_MultiModeStart_DMA(&hadc1,(uint32_t*)adc_dma,ADC_DMA_SIZE);

  HAL_TIM_Base_Start(&htim8);  




然后写好ADC_DMA的中断函数,在中断关掉TIM8然后置一个标志位,在主函数里进行fft,然后可以发串口,也可以DAC输出一下。

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
    HAL_TIM_Base_Stop(&htim8);
    dma_flag = true;
}


7951068baa12610433.png


串口配置:

可以看这两篇:
【VOFA+速成】半小时入门VOFA+简明教程(快速上手一款强力的串口助手)
STM32 printf重定向(串口输出)
波形显示函数,串口重定向,在fft处理完之后直接调用

void waveform_display(void)
{
  uint16_t i;
  for(i=0;i<ADC_DMA_SIZE*2;i++) {
    printf("%.2f\n",fft_modulus);
               
}
}
int fputc(int ch, FILE *f)

{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 100);
return ch;
}




顺便找一下基波

uint16_t search_fund_wave(void)
{
                for(uint16_t i=5;i<ADC_DMA_SIZE;i++)
        {
                if(fft_modulus>fund_wave)
                {
                        fund_wave_index=i;
                        fund_wave=fft_modulus;
                }
        }
return fund_wave_index;
}




主函数

int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_ADC1_Init();
  MX_ADC2_Init();
  MX_ADC3_Init();
  MX_TIM8_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  HAL_ADC_Start(&hadc2);
  HAL_ADC_Start(&hadc3);

  HAL_ADCEx_MultiModeStart_DMA(&hadc1,(uint32_t*)adc_dma_buffer,ADC_DMA_SIZE);//DMAmode2

  HAL_TIM_Base_Start(&htim8);
//hannwindow在主函数只算一次,不放fft_process里
                for(int i=0; i<2*ADC_DMA_SIZE; i++)
                {hanning = 0.5f * (1 - cosf(2*PI*i/(2*ADC_DMA_SIZE-1)));}       
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */

  while (1)
  {
                //ADC采集完成
    if(adc_dma_flag){
      dma_data_process();//DMA32位拆成16位
                        for(uint16_t i=0;i<2*ADC_DMA_SIZE;i++)
                {
                    printf("%d\n",adc_buffer);
               
                }
                a=2;
      fft_data_process();//fft准备,加窗,添加虚部,虚部为0
                for(uint16_t i=0;i<2*ADC_DMA_SIZE;i++)
                {
                    printf("%.2f\n",fft_buffer);
               
                }
                        a=1;
                //进行fft
      arm_cfft_f32(&arm_cfft_sR_f32_len4096,fft_buffer, 0, 1);
                        arm_cmplx_mag_f32(fft_buffer, fft_modulus, 4096);
      waveform_display();
                //找基波(得出频率)
                freq=search_fund_wave();
      adc_dma_flag=0;
      HAL_TIM_Base_Start(&htim8);
//      HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1,(const uint32_t *)fft_modulus, 2*ADC_DMA_SIZE, DAC_ALIGN_12B_R);
//      HAL_TIM_Base_Start(&htim7);
    }
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}





这里用50Khz的正弦波进行了测试:

238868baa114a61c1.png


————————————————
版权声明:本文为CSDN博主「西西xixi5」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/2401_84391088/article/details/150423997

51xlf 发表于 2025-9-21 12:50 | 显示全部楼层
单个 ADC 的最大采样率为 2.4Msps
jimmhu 发表于 2025-9-21 14:48 | 显示全部楼层
通过合理配置 ADC、DMA 和定时器,并结合 DSP 库的 FFT 函数,可以高效地完成信号处理任务。
iyoum 发表于 2025-9-21 15:59 | 显示全部楼层
使用CMSIS-DSP库:包含FFT函数,支持复数和实数FFT。
pentruman 发表于 2025-9-21 16:42 | 显示全部楼层
避免单个ADC频繁切换通道导致的延迟
averyleigh 发表于 2025-9-21 17:11 | 显示全部楼层
通过​​软件触发ADC转换​​,并利用DMA将数据存入双缓冲区。关键是协调三个ADC的采样时序,避免冲突。
robertesth 发表于 2025-9-21 18:04 | 显示全部楼层
根据奈奎斯特定理,采样率需大于信号最高频率的2倍
sdlls 发表于 2025-9-21 18:48 | 显示全部楼层
使用 DSP 库进行 FFT 计算
10299823 发表于 2025-9-21 19:29 | 显示全部楼层
使用两个DMA缓冲区交替采集与处理,避免数据丢失。
jonas222 发表于 2025-9-21 21:01 | 显示全部楼层
DMA传输速率与ADC采样速率匹配
backlugin 发表于 2025-9-21 21:54 | 显示全部楼层
合理选择采样率和FFT点数,避免频率混叠和计算量过大。
bartonalfred 发表于 2025-9-21 22:14 | 显示全部楼层
可以实现高速信号采集与频谱分析。
dspmana 发表于 2025-9-21 22:50 | 显示全部楼层
点数越多,频率分辨率越高              
vivilyly 发表于 2025-9-22 13:04 | 显示全部楼层
采用非循环模式              
pmp 发表于 2025-9-22 14:25 | 显示全部楼层
通过 DMA 将 ADC 采集的数据存储到内存中。
wilhelmina2 发表于 2025-9-22 15:56 | 显示全部楼层
STM32F407的三重ADC交替采样+DMA+FFT组合应用,是实时信号处理的核心技术。
bartonalfred 发表于 2025-9-22 17:05 | 显示全部楼层
FFT可以分析故障特征频率。              
hudi008 发表于 2025-9-22 18:17 | 显示全部楼层
将ADC转换完成中断设为高优先级,确保快速响应
chenci2013 发表于 2025-9-22 18:38 | 显示全部楼层
调用 CMSIS-DSP 库的arm_rfft_fast_f32实现 1024 点 FFT。
i1mcu 发表于 2025-9-22 19:31 | 显示全部楼层
若对实时性要求极高,可使用定点数FFT
您需要登录后才可以回帖 登录 | 注册

本版积分规则

77

主题

240

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部