当前位置:   article > 正文

stm32f405采样正弦信号定时器激发双重ADC规则组加DMA转运通过FFT计算相位差_stm32f405 dma adc2

stm32f405 dma adc2

新手小白,自己记录代码,如果有错误,望各位大佬指正。

一:双重ADC配置

  1. void Adc_Init()
  2. {
  3. GPIO_InitTypeDef GPIO_InitStructure;
  4. ADC_CommonInitTypeDef ADC_CommonInitStructure;
  5. ADC_InitTypeDef ADC_InitStructure;
  6. RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
  7. RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
  8. RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC2, ENABLE);
  9. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; // adc 12 的通道
  10. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
  11. GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
  12. GPIO_Init(GPIOC, &GPIO_InitStructure);
  13. RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1, ENABLE);
  14. RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1, DISABLE);
  15. RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC2, ENABLE);
  16. RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC2, DISABLE); // 重置
  17. ADC_CommonInitStructure.ADC_Mode = ADC_DualMode_RegSimult;
  18. ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
  19. ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_2;
  20. ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2;
  21. ADC_CommonInit(&ADC_CommonInitStructure);
  22. ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
  23. ADC_InitStructure.ADC_ScanConvMode = DISABLE;
  24. ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
  25. ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising ;
  26. ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
  27. ADC_InitStructure.ADC_NbrOfConversion = 1; // 通道数
  28. ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO;
  29. ADC_Init(ADC1, &ADC_InitStructure);
  30. ADC_Init(ADC2, &ADC_InitStructure);
  31. ADC_RegularChannelConfig(ADC2, ADC_Channel_11, 1, ADC_SampleTime_3Cycles);
  32. ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_3Cycles);
  33. ADC_MultiModeDMARequestAfterLastTransferCmd(ENABLE); // 多路转化完后触发dma
  34. ADC_DMACmd(ADC1, ENABLE);
  35. ADC_Cmd(ADC1, ENABLE);
  36. ADC_Cmd(ADC2, ENABLE);
  37. }

初始化PC0和PC1,他们对应的通道是10和11。

然后对ADC的公共配置进行了一些设置。

  1. ADC_Mode = ADC_DualMode_RegSimult:这个配置设置了ADC的工作模式为双模式(Dual Mode),并且使用常规并行模式(Regular simultaneous mode)。这意味着ADC同时进行两个常规转换,即ADC1和ADC2同时进行转换。

  2. ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles:这个配置设置了两个采样阶段之间的延迟时间为5个时钟周期。这个延迟时间用于确保ADC电压采样的准确性。

  3. ADC_DMAAccessMode = ADC_DMAAccessMode_2:这个配置设置了DMA的访问模式为双模式(Dual Mode)。在双模式下,DMA可以同时访问两个ADC数据寄存器,即ADC1和ADC2的数据寄存器。

这里 ADC_DMAAccessMode这个参数需要注意一下,多重ADC模式下的DMA请求有三个模式,模式1是一次转换半个字也就是16位,先传输ADC1的数据再传输ADC2的数据

模式2是一次转运一个字也就是32位,会同时传输ADC1和ADC2的数据,ADC1数据占用高位半字,ADC2占用低位半字。

具体可以看下面这张图:

因为我设置的是模式2,所以后面处理数据的时候要注意一下,可以通过& 0xFFFF 位掩码操作,按位与操作将高位清零,只保留低16位的值,通过>>16右移操作符取得高16位的值。

  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_DataAlign = ADC_DataAlign_Right;

  ADC_InitStructure.ADC_NbrOfConversion = 1; // 通道数

  ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO;

讲一下这个

  1. ADC_Resolution = ADC_Resolution_12b:这个配置设置了ADC的分辨率为12位。这意味着ADC的转换结果将会以12位的精度进行表示。

  2. ADC_ScanConvMode = DISABLE:这个配置禁用了扫描模式。扫描模式允许ADC按照一组通道的顺序进行连续转换。禁用扫描模式后,ADC将只进行单通道的转换。

  3. ADC_ContinuousConvMode = DISABLE:这个配置禁用了连续转换模式。连续转换模式允许ADC在触发条件满足时连续进行转换。禁用连续转换模式后,ADC将只进行单次转换。

  4. ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising:这个配置设置了外部触发转换的边沿触发方式为上升沿触发。这意味着当外部触发信号的上升沿到来时,ADC将触发转换。

  5. ADC_DataAlign = ADC_DataAlign_Right:这个配置设置了ADC数据对齐方式为右对齐。右对齐方式表示ADC转换结果的有效位从低位开始。

  6. ADC_NbrOfConversion = 1:这个配置设置了转换的通道数为1。这意味着ADC将只转换一个通道。

  7. ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO:这个配置设置了外部触发转换的触发源为定时器3的TRGO事件。这意味着当定时器3触发时,ADC将开始转换。

ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising

ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO;

这两行很关键,因为要使用定时器触发,所以要使用 ADC_ExternalTrigConvEdge_Rising,而不能使用ADC_ExternalTrigConvEdge_None。

还有一种就是使用ADC_ExternalTrigConvEdge_None,就没有配置外部触发,然后再定时器中断中手动触发 ADC_SoftwareStartConv软件触发

二:DMA配置

  1. void Dma_ADC_Init()
  2. {
  3. DMA_InitTypeDef DMA_InitStructure;
  4. NVIC_InitTypeDef NVIC_InitStructure;
  5. RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
  6. DMA_DeInit(DMA2_Stream0);
  7. DMA_InitStructure.DMA_BufferSize = sampledot;
  8. DMA_InitStructure.DMA_Channel = DMA_Channel_0;
  9. DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
  10. DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable;
  11. DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
  12. DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&sampledata; // 要存入的值 DMA_InitStructure.DMA_MemoryBurst=DMA_MemoryBurst_Single;
  13. DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
  14. DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  15. DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
  16. DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)0x40012308; // adc地址
  17. DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
  18. DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
  19. DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  20. DMA_InitStructure.DMA_Priority = DMA_Priority_High;
  21. DMA_Init(DMA2_Stream0, &DMA_InitStructure);
  22. DMA_ITConfig(DMA2_Stream0, DMA_IT_TC, ENABLE);
  23. DMA_Cmd(DMA2_Stream0, ENABLE);
  24. NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn; // DMA2_Stream0中断
  25. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 抢占优先级1
  26. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 子优先级1
  27. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // IRQ通道使能
  28. NVIC_Init(&NVIC_InitStructure);
  29. }

这个没什么好说的,查表可以看到ADC是DMA2的请求映射。

 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;

DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;

这行代码用于配置DMA的内存和外设数据大小,将其设置为字(Word)大小。如果你上面的多重ADC模式下的DMA请求配置为模式1,你就要设置成半字,模式2就设置成字

三:定时器激发配置

  1. void Tim3_Init(u16 arr, u16 psc)
  2. {
  3. TIM_TimeBaseInitTypeDef TIM_TimeBaseInitstruct;
  4. RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
  5. TIM_TimeBaseInitstruct.TIM_Period = arr;
  6. TIM_TimeBaseInitstruct.TIM_Prescaler = psc;
  7. TIM_TimeBaseInitstruct.TIM_CounterMode = TIM_CounterMode_Up;
  8. TIM_TimeBaseInitstruct.TIM_ClockDivision = TIM_CKD_DIV1;
  9. TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitstruct);
  10. // TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);
  11. TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);
  12. TIM_Cmd(TIM3, DISABLE);
  13. }
  1. TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);:选择定时器3的输出触发源为更新事件。也就是说,当定时器3发生更新事件时,会产生一个输出触发信号。当ADC接收到这个触发信号就会自动执行转换。

TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); 这行代码的作用是配置定时器3的更新中断使能。我这里不需要使用到中断,所以就注释了。

四:DMA中断处理数据

  1. void DMA2_Stream0_IRQHandler(void)
  2. {
  3. if(DMA_GetITStatus(DMA2_Stream0, DMA_IT_TCIF0)) //判断DMA传输完成中断
  4. {
  5. TIM_Cmd(TIM3,DISABLE);//失能时钟,进行计算
  6. //处理数据
  7. calculatePhase();
  8. DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_TCIF0);
  9. TIM_Cmd(TIM3,ENABLE);//使能时钟,进行计算
  10. }
  11. }

计算正弦波的相位差。

五:数据处理函数

  1. void calculatePhase()
  2. {
  3. for (i = 0; i < FFT_LENGTH; i++)
  4. {
  5. // Voltage[0] = ((double)(ADC_Value[0] >> 16) / 4096) * 3.3; // 计算对应的电压 >>16取高16位数据即ADC2数据
  6. // Voltage[1] = ((double)(ADC_Value[1] & 0xFFFF) / 4096) * 3.3; // 计算对应的电压 &0xffff取低16位数据即ADC1数据
  7. adcbuff1[i] = (double)(sampledata[2 * i] >> 16) * 3.3 / 4096;
  8. adcbuff2[i] = (double)(sampledata[2 * i] & 0xFFFF)* 3.3 / 4096;
  9. }
  10. for (i = 0; i < FFT_LENGTH; i++) // 生成信号序列
  11. {
  12. fft_inputbuf1[2 * i] = (float)(adcbuff1[i]); // 生成输入信号实部
  13. fft_inputbuf1[2 * i + 1] = 0; // 虚部全部为0
  14. }
  15. arm_cfft_radix4_f32(&scfft, fft_inputbuf1); // FFT计算(基4
  16. arm_cmplx_mag_f32(fft_inputbuf1, fft_outputbuf1, FFT_LENGTH); // 把运算结果复数求模得幅值
  17. max1 = fft_outputbuf1[1];
  18. maxb1 = 1;
  19. for (i = 1; i < FFT_LENGTH; i++)
  20. {
  21. if (fft_outputbuf1[i] > max1)
  22. {
  23. max1 = fft_outputbuf1[i];
  24. maxb1 = i;
  25. }
  26. } // 取最大幅值谐波
  27. for (i = 0; i < FFT_LENGTH; i++) // 生成信号序列
  28. {
  29. fft_inputbuf2[2 * i] = (float)(adcbuff2[i]); // 生成输入信号实部
  30. fft_inputbuf2[2 * i + 1] = 0; // 虚部全部为0
  31. }
  32. phase1 = atan2(fft_inputbuf1[2 * (maxb1) + 1], fft_inputbuf1[2 * (maxb1)]) * 180 / PI + 90;
  33. arm_cfft_radix4_f32(&scfft, fft_inputbuf2); // FFT计算(基4
  34. arm_cmplx_mag_f32(fft_inputbuf2, fft_outputbuf2, FFT_LENGTH); // 把运算结果复数求模得幅值
  35. max2 = fft_outputbuf2[1];
  36. maxb2 = 1;
  37. for (i = 1; i < FFT_LENGTH; i++)
  38. {
  39. if (fft_outputbuf2[i] > max2)
  40. {
  41. max2 = fft_outputbuf2[i];
  42. maxb2 = i;
  43. }
  44. } // 取最大幅值谐波
  45. phase2 = atan2(fft_inputbuf2[2 * (maxb2) + 1], fft_inputbuf2[2 * (maxb2)]) * 180 / PI + 90;
  46. phasetrue = fabs(phase1 - phase2);
  47. }

这个傅里叶变换我暂时还不懂,是直接copy网上大佬的,等我懂了再来解释一下。 

五:主函数 

  1. uint32_t* MyADC_GetValue(void)
  2. {
  3. for(uint8_t i=0; i<2; i++)
  4. {
  5. MyADC_Value[i] = sampledata[i];
  6. }
  7. //返回ADC的采样值
  8. return MyADC_Value;
  9. }
  10. int main(void)
  11. {
  12. Dma_ADC_Init();
  13. Adc_Init();
  14. Tim3_Init(fft_arr-1,fft_psc-1);
  15. TIM_Cmd(TIM3,ENABLE);
  16. while (1)
  17. {
  18. /* code */
  19. ADC_Value = MyADC_GetValue(); // 获取ADC的采样值
  20. Voltage[0] = ((double)(ADC_Value[0] >>16 ) / 4096) * 3.3; // 计算对应的电压
  21. Voltage[1] = ((double)(ADC_Value[1] & 0xFFFF ) / 4096) * 3.3; // 计算对应的电压
  22. }
  23. }

 我这里是为了验证采集的数据是否存在问题,我用正点DS100输出的电压总比我采集到的电压小0.04V,不知道哪里有问题,如果有知道的大佬可以指点一下,不胜感激。

最后,我的代码有可能存在错误的地方,如果有希望各位大佬可以指出来,谢谢。

六:补充

记录一下怎么调用DSP库实现FFT变换,通过1,2步骤出现3就代表成功,然后在头文件包含arm_math.h,最后定义一下内核就能使用了。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/人工智能uu/article/detail/930554
推荐阅读
相关标签
  

闽ICP备14008679号