赞
踩
新手小白,自己记录代码,如果有错误,望各位大佬指正。
- 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的公共配置进行了一些设置。
ADC_Mode = ADC_DualMode_RegSimult
:这个配置设置了ADC的工作模式为双模式(Dual Mode),并且使用常规并行模式(Regular simultaneous mode)。这意味着ADC同时进行两个常规转换,即ADC1和ADC2同时进行转换。
ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles
:这个配置设置了两个采样阶段之间的延迟时间为5个时钟周期。这个延迟时间用于确保ADC电压采样的准确性。
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;
讲一下这个
ADC_Resolution = ADC_Resolution_12b
:这个配置设置了ADC的分辨率为12位。这意味着ADC的转换结果将会以12位的精度进行表示。
ADC_ScanConvMode = DISABLE
:这个配置禁用了扫描模式。扫描模式允许ADC按照一组通道的顺序进行连续转换。禁用扫描模式后,ADC将只进行单通道的转换。
ADC_ContinuousConvMode = DISABLE
:这个配置禁用了连续转换模式。连续转换模式允许ADC在触发条件满足时连续进行转换。禁用连续转换模式后,ADC将只进行单次转换。
ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising
:这个配置设置了外部触发转换的边沿触发方式为上升沿触发。这意味着当外部触发信号的上升沿到来时,ADC将触发转换。
ADC_DataAlign = ADC_DataAlign_Right
:这个配置设置了ADC数据对齐方式为右对齐。右对齐方式表示ADC转换结果的有效位从低位开始。
ADC_NbrOfConversion = 1
:这个配置设置了转换的通道数为1。这意味着ADC将只转换一个通道。
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软件触发
- 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);
- }
TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);
:选择定时器3的输出触发源为更新事件。也就是说,当定时器3发生更新事件时,会产生一个输出触发信号。当ADC接收到这个触发信号就会自动执行转换。
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);
这行代码的作用是配置定时器3的更新中断使能。我这里不需要使用到中断,所以就注释了。
- 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,最后定义一下内核就能使用了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。