使用STM32测量两列信号的幅度比及相位差

使用STM32测量两列信号的幅度比及相位差

Wed Aug 20 2025
940 words · 7 minutes

使用STM32测量两列信号的幅度比及相位差

STM32ADC同步采样

对于相位而言,我们采集的信号必须是同步的,否则讨论相位差毫无意义,而这也很容易理解:如果在采集过程就引入了时延,那计算所得的相位就包含了引入的时延。

这一部分主要讲解如何实现STM32ADC同步采样。

实现方式一

ADC 有一种同步模式,但配置较复杂,信号处理也不直接。已经有可用的教程,写得不错: 张十三的博客

实现方式二

下面介绍一种更加简单的方式:

只需要先开启两个ADC采样,然后开启定时器触发,这样定时器产生的触发信号同时到达两个ADC,两个ADC在接收到触发信号后,开始同步采样。 alt text

工程配置:

ADC1配置 alt text ADC1DMA配置 alt text ADC2配置及其DMA配置同ADC1

TIM15配置 alt text TIM15决定ADC采样率,根据实际需要配置即可

代码展示:

建立ADC数组并分配RAM空间

C
ALIGN_32BYTES (uint16_t adc1_data[FFT_LENGTH]) 	__attribute__((section(".ARM.__at_0x30000000")));
ALIGN_32BYTES (uint16_t adc2_data[FFT_LENGTH]) 	__attribute__((section(".ARM.__at_0x30020000")));
__IO uint8_t AdcConvEnd = 0;                                    //采集完成标志

ADC采集函数,调用即完成采样

C
void adc_init(void)
{

	MX_ADC1_Init();	 	
	MX_ADC2_Init();


	if (HAL_ADCEx_Calibration_Start(&hadc1, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED) != HAL_OK)//鐢靛帇鏍″噯
	{
			//printf("hadc1 error with HAL_ADCEx_Calibration_Start\r\n");
			Error_Handler();
	}

	if (HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc1_data, FFT_LENGTH) != HAL_OK)
	{
			//printf("hadc1 error with HAL_ADC_Start_DMA\r\n");
			Error_Handler();
	}
		if (HAL_ADCEx_Calibration_Start(&hadc2, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED) != HAL_OK)
	{
			//printf("hadc1 error with HAL_ADCEx_Calibration_Start\r\n");
			Error_Handler();
	}

	if (HAL_ADC_Start_DMA(&hadc2, (uint32_t *)adc2_data, FFT_LENGTH) != HAL_OK)
	{
			//printf("hadc1 error with HAL_ADC_Start_DMA\r\n");
			Error_Handler();
	}
	HAL_TIM_Base_Start(&htim15);
	while (!AdcConvEnd);
	AdcConvEnd = 0;
	HAL_ADC_Stop_DMA(&hadc2);
	HAL_ADC_Stop_DMA(&hadc1);
	HAL_TIM_Base_Stop(&htim15);
	
}

中断回调函数

C
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
	 AdcConvEnd = 1;
}

FFT计算幅值比及相位差

C
void FFT_Init1(float* fundamental_magnitude,float* fundamental_phase)
{

		for(int i=0; i<FFT_LENGTH; i++)
		{
			
			q1[i].real = adc1_data[i] *3.3/65536;		
			q1[i].imag = 0;
		}
		FFT(q1, 13);

		/* 计算幅度和相位 */

		for(uint32_t i=0; i<FFT_LENGTH/2; i++)
		{
			// 计算模值
			float real = q1[i].real;
			float imag = q1[i].imag;
			// 缓存中间结果,减少重复计算
			float real_square = real * real;
			float imag_square = imag * imag;
    
			arm_sqrt_f32(real_square + imag_square, &magnitude11[i]);

			phase11[i] = atan2f(imag, real);
		}

		/* 找出基频成分(幅度最大的非零频率) */
		uint32_t fundamental_idx = 0;
		float max_mag = 0;
		for(uint32_t i=2; i<FFT_LENGTH/2; i++)  // 跳过直流分量
		{
			if(magnitude11[i] > max_mag)
			{
				max_mag = magnitude11[i];
				fundamental_idx = i;
			}
		}

		/* 获取基频的幅度和相位 */
		*fundamental_magnitude = magnitude11[fundamental_idx];
		*fundamental_phase = phase11[fundamental_idx];  // 弧度
		
	
}

float FFT_Init2(float* fundamental_magnitude,float* fundamental_phase)
{

		for(uint32_t i=0; i<FFT_LENGTH; i++)
		{
			q1[i].real = adc2_data[i] *3.3/65536;		
			q1[i].imag = 0;
		}
		FFT(q1, 13);
		/* 计算幅度和相位 */

		for(uint32_t i=0; i<FFT_LENGTH/2; i++)
		{
			// 计算模值
			float real = q1[i].real;
			float imag = q1[i].imag;
			float real_square = real * real;
			float imag_square = imag * imag;
    
			arm_sqrt_f32(real * real + imag * imag, &magnitude22[i]);
		
			phase22[i] = atan2f(imag, real);
		}

		/* 找出基频成分(幅度最大的非零频率) */
		uint32_t fundamental_idx = 0;
		float max_mag = 0;
		for(uint32_t i=2; i<FFT_LENGTH/2; i++)  // 跳过直流分量
		{
			if(magnitude22[i] > max_mag)
			{
				max_mag = magnitude22[i];
				fundamental_idx = i;
			}
		}		
		/* 获取基频的幅度和相位 */
		*fundamental_magnitude = magnitude22[fundamental_idx];
		*fundamental_phase = phase22[fundamental_idx];  // 弧度
		return 240000000.0*fundamental_idx/(FFT_LENGTH*146.0);//返回被测信号频率
}

FFT算法参考之前的博客“更高性能的FFT/IFFT”

在上述代码中,有两点需要解释: 1、寻找基频,即找到该信号的特征频点

C
		for(uint32_t i=2; i<FFT_LENGTH/2; i++)  // 跳过直流分量
		{
			if(magnitude22[i] > max_mag)
			{
				max_mag = magnitude22[i];
				fundamental_idx = i;
			}
		}	

2、计算反正切

C
	phase22[i] = atan2f(imag, real);

FFT输出的每个频点都是复数(real + imag·j),可以表示为:

C
A·e^(jθ) = A·cosθ + j·A·sinθ

其中:

•A是幅度(通过sqrt(real² + imag²)计算)

•θ是相位(通过atan2(imag,real)计算)

普通atan(y/x)存在缺陷:

•无法区分象限(例如-1/-1和1/1的结果相同)

•当x=0时会除零错误

atan2(y,x)能正确处理所有情况:

C
atan2(1,1)   =  π/4  // 第一象限
atan2(1,-1)  = 3π/4  // 第二象限 
atan2(-1,-1) = -3π/4 // 第三象限
atan2(-1,1)  = -π/4  // 第四象限
atan2(1,0)   =  π/2  // y轴正方向

总结

C
        adc_init();
        FFT_Init1(&mag1,&phase1);	
        FFT_Init2(&mag2,&phase2);
        gain=mag2/mag1;
        phase=phase2-phase1;

gain即为幅度比, phase即为相位差。


Thanks for reading!

使用STM32测量两列信号的幅度比及相位差

Wed Aug 20 2025
940 words · 7 minutes