新手小白,自己记录代码,如果有错误,望各位大佬指正。
一:双重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的公共配置进行了一些设置。
-
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软件触发
二: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);
}
-
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,最后定义一下内核就能使用了。