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的正弦波进行了测试: