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

STM32f407hal库定时器触发三重ADC交替采样+DMA+FFT学习笔记

使用stm32f407vet6
使用stm32f407vet6
使用stm32f407vet6

一.时钟配置

时钟就使用外部高速时钟了在这里插入图片描述
时钟树的配置就要开始考虑ADC了,查看手册,ADC的最大时钟周期是36Mhz
在这里插入图片描述
ADC挂在APB2时钟下,为了达到最大采样率,如下配置时钟树
在这里插入图片描述
对ADC时钟2分频刚好是36Mhz
在这里插入图片描述

二.定时器配置

定时器选择TIM8,选择内部时钟到TRGO触发ADC这条路
在这里插入图片描述这里要注意分频和重装载值的设置,也要结合ADC采样来看
再看一下手册:在这里插入图片描述
在最快单个ADC转换即实现最大单个ADC采样率的情况下ADC的采样时间是3个ADC时钟周期,转换时间是12个ADC时钟周期,想要实现ADC的均匀采样,如下图
在这里插入图片描述
那么定时器就要每15/144000000 触发一下ADC采样,就不给定时器分频了直接用定时器144Mhz时钟周期来计算重装载值来配置(选择向上计数)

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

看下配置
在这里插入图片描述注意打开自动重装,TRGO(触发输出)设置成更新事件

三.ADC和DMA配置

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

再看一下DMA的配置
在这里插入图片描述
同样也只需要在ADC1配置好就行
在这里插入图片描述
记得在ADC1配置使能DMA的连续传输
在这里插入图片描述
配置就完成了

四.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[i]&0x0000ffff); 
        adc_buffer[2*i+1] =(float)( (adc_dma_buffer[i]&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[i] = 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[i]*adc_buffer[i]);
    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;
}

串口配置:
在这里插入图片描述
可以看这两篇:
【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[i]);
		
}
}
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[i]>fund_wave)
		{
			fund_wave_index=i;
			fund_wave=fft_modulus[i];
		}
	}
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[i] = 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[i]);
		
		}
		a=2;
      fft_data_process();//fft准备,加窗,添加虚部,虚部为0
		for(uint16_t i=0;i<2*ADC_DMA_SIZE;i++)
		{
		    printf("%.2f\n",fft_buffer[i]);
		
		}
			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的正弦波进行了测试:
在这里插入图片描述
在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值