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

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

一:双重ADC配置

void Adc_Init()
{
  GPIO_InitTypeDef GPIO_InitStructure;
  ADC_CommonInitTypeDef ADC_CommonInitStructure;
  ADC_InitTypeDef ADC_InitStructure;
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC2, ENABLE);
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; // adc 1和2 的通道
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
  GPIO_Init(GPIOC, &GPIO_InitStructure);

  RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1, ENABLE);
  RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1, DISABLE);
  RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC2, ENABLE);
  RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC2, DISABLE); // 重置
  ADC_CommonInitStructure.ADC_Mode = ADC_DualMode_RegSimult;
  ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
  ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_2;
  ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2;
  ADC_CommonInit(&ADC_CommonInitStructure);

  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;
  ADC_Init(ADC1, &ADC_InitStructure);
  ADC_Init(ADC2, &ADC_InitStructure);
  ADC_RegularChannelConfig(ADC2, ADC_Channel_11, 1, ADC_SampleTime_3Cycles);
  ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_3Cycles);

  ADC_MultiModeDMARequestAfterLastTransferCmd(ENABLE); // 多路转化完后触发dma
  ADC_DMACmd(ADC1, ENABLE);
  ADC_Cmd(ADC1, ENABLE);
  ADC_Cmd(ADC2, ENABLE);
}

先初始化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配置

void Dma_ADC_Init()
{

  DMA_InitTypeDef DMA_InitStructure;
  NVIC_InitTypeDef NVIC_InitStructure;
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
  DMA_DeInit(DMA2_Stream0);
  DMA_InitStructure.DMA_BufferSize = sampledot;
  DMA_InitStructure.DMA_Channel = DMA_Channel_0;
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
  DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable;
  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
  DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&sampledata; // 要存入的值	DMA_InitStructure.DMA_MemoryBurst=DMA_MemoryBurst_Single;
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)0x40012308; // adc地址
  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;

  DMA_Init(DMA2_Stream0, &DMA_InitStructure);
  DMA_ITConfig(DMA2_Stream0, DMA_IT_TC, ENABLE);
  DMA_Cmd(DMA2_Stream0, ENABLE);

  NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn;   // DMA2_Stream0中断
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 抢占优先级1
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;        // 子优先级1
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;           // IRQ通道使能
  NVIC_Init(&NVIC_InitStructure);
}

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

 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;

DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;

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

三:定时器激发配置

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

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

四:DMA中断处理数据

void DMA2_Stream0_IRQHandler(void)  
{
  	if(DMA_GetITStatus(DMA2_Stream0, DMA_IT_TCIF0))  //判断DMA传输完成中断  
    {
		  TIM_Cmd(TIM3,DISABLE);//失能时钟,进行计算
          //处理数据
          calculatePhase();

          DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_TCIF0);
		  TIM_Cmd(TIM3,ENABLE);//使能时钟,进行计算
    }
}

计算正弦波的相位差。

五:数据处理函数

void calculatePhase()
{
  for (i = 0; i < FFT_LENGTH; i++)
  {
    // Voltage[0] = ((double)(ADC_Value[0] >> 16) / 4096) * 3.3;    // 计算对应的电压      >>16取高16位数据即ADC2数据
    // Voltage[1] = ((double)(ADC_Value[1] & 0xFFFF) / 4096) * 3.3; // 计算对应的电压  &0xffff取低16位数据即ADC1数据
    adcbuff1[i] = (double)(sampledata[2 * i] >> 16) * 3.3 / 4096;
    adcbuff2[i] = (double)(sampledata[2 * i] & 0xFFFF)* 3.3 / 4096;
  }
  for (i = 0; i < FFT_LENGTH; i++) // 生成信号序列
  {
    fft_inputbuf1[2 * i] = (float)(adcbuff1[i]); // 生成输入信号实部
    fft_inputbuf1[2 * i + 1] = 0;                // 虚部全部为0
  }
  arm_cfft_radix4_f32(&scfft, fft_inputbuf1);                   // FFT计算(基4)
  arm_cmplx_mag_f32(fft_inputbuf1, fft_outputbuf1, FFT_LENGTH); // 把运算结果复数求模得幅值

  max1 = fft_outputbuf1[1];
  maxb1 = 1;
  for (i = 1; i < FFT_LENGTH; i++)
  {
    if (fft_outputbuf1[i] > max1)
    {
      max1 = fft_outputbuf1[i];
      maxb1 = i;
    }
  } // 取最大幅值谐波

  for (i = 0; i < FFT_LENGTH; i++) // 生成信号序列
  {
    fft_inputbuf2[2 * i] = (float)(adcbuff2[i]); // 生成输入信号实部
    fft_inputbuf2[2 * i + 1] = 0;                // 虚部全部为0
  }
  phase1 = atan2(fft_inputbuf1[2 * (maxb1) + 1], fft_inputbuf1[2 * (maxb1)]) * 180 / PI + 90;
  arm_cfft_radix4_f32(&scfft, fft_inputbuf2);                   // FFT计算(基4)
  arm_cmplx_mag_f32(fft_inputbuf2, fft_outputbuf2, FFT_LENGTH); // 把运算结果复数求模得幅值

  max2 = fft_outputbuf2[1];
  maxb2 = 1;
  for (i = 1; i < FFT_LENGTH; i++)
  {
    if (fft_outputbuf2[i] > max2)
    {
      max2 = fft_outputbuf2[i];
      maxb2 = i;
    }
  } // 取最大幅值谐波
  phase2 = atan2(fft_inputbuf2[2 * (maxb2) + 1], fft_inputbuf2[2 * (maxb2)]) * 180 / PI + 90;
  phasetrue = fabs(phase1 - phase2);
}

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

五:主函数 

uint32_t* MyADC_GetValue(void)
{

	for(uint8_t i=0; i<2; i++)
	{
		MyADC_Value[i] = sampledata[i];
	}

	//返回ADC的采样值
	return MyADC_Value;
}
int main(void)
{
  Dma_ADC_Init();
  Adc_Init();
  Tim3_Init(fft_arr-1,fft_psc-1);
  TIM_Cmd(TIM3,ENABLE);

  while (1)
  {
    /* code */

    ADC_Value = MyADC_GetValue();					  // 获取ADC的采样值
		Voltage[0] = ((double)(ADC_Value[0] >>16 ) / 4096) * 3.3; // 计算对应的电压
		Voltage[1] = ((double)(ADC_Value[1] & 0xFFFF ) / 4096) * 3.3; // 计算对应的电压
  }
  
}

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

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

六:补充

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

  • 19
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值