STM32笔记:高精度脉冲宽度计 双输入捕获+DMA方式

本文介绍如何用STM32F107VC(Waveshare Open107V实验板)实现高精度的脉冲宽度计(占空比)。

开发环境:

IDE:STM32CubeIDE 1.8

固件库:STM32Cube_FW_F1_V1.8.4

函数发生:RIGOL DG5072函数信号发生器,产生0-3.3V的方波,10KHz

硬件:Waveshare Open107V,STM32F107VC, 晶振25MHz,工作频率72MHz

思路:

*利用定时器的输入捕获功能测量方波的占空比,时钟为系统时钟72MHz。

*将Channel 1 (CH1)设为上升沿捕获,将Channel 2 (CH2)设为下降沿捕获,信号同时输入到CH1 和CH2.

*分别启用CH1和CH2的DMA,在捕获时刻将对应的捕获值送到对应的内存数组tcBuf[2][1024]。这样,在信号上升沿,CH1的比较寄存器自动获得当前计数值,并通过DMA存入到数组tcBuf[0][n],随后,在信号下降沿,CH2的比较寄存器自动获得当前计数值,通过DMA存入到数组tcBuf[1][n].

*用TIM2定时中断,每1秒钟计算一次脉冲宽度、频率值和占空比,发送到串口1。使用一组数据即可,或者可以多组取平均,或者进行滤波以提高精度。

*在串口1收到"DATA"指令时,停止全局中断,停止DMA,将tcBuf[0][0...1023]和tcBuf[1][0...1023]依次发回计算机。然后开启DMA,开启全局中断,继续。

测试结果:

占空比(%) 脉冲宽度(us) 频率(Hz) 误差(%)
50.014  50.014  10000.000  0.028 
49.993  50.000  9998.611  0.014 
50.000  50.000  10000.000  0.000 
50.000  50.000  10000.000  0.000 
50.021  50.014  10001.389  0.042 
49.993  50.000  9998.611  0.014 
50.000  50.000  10000.000  0.000 
50.014  50.014  10000.000  0.028 
50.014  50.014  10000.000  0.028 
49.993  50.000  9998.611  0.014 
50.000  50.000  10000.000  0.000 
50.014  50.014  10000.000  0.028 
50.000  50.000  10000.000  0.000 

注意:
        *对于1kHz以下的频率,一个周期超过了一次TIM1(16bit)的计数,需要进行更为复杂的处理,本程序没有实现,如果您做了,能否分享一下?感谢!
        *频率越高,精度越低。
        *10kHz信号的理论分辨率:1/7200 * 100% = 0.014%,实验结果符合的很好。

关键代码:  


uint16_t tcBuf[2][BUF_SIZE] = {0}; //全局变量,tcBuf[0][]存放上升沿TC,tcBuf[1][]存放下降沿TC


//main 函数中的TIM1_DMA初始化,没有使用中断
  HAL_TIM_IC_Start_DMA( &htim1,  TIM_CHANNEL_1, (uint32_t*)&(tcBuf[0]), BUF_SIZE);
  HAL_TIM_IC_Start_DMA( &htim1,  TIM_CHANNEL_2, (uint32_t*)&(tcBuf[1]), BUF_SIZE);


//计算脉冲宽度,频率和占空比,发送到串口,每1秒被调用一次
void GetPWM(void)
{
	uint16_t lenPulse = 0, lenPeriod;
	float pr = 0;
	lenPeriod = (tcBuf[0][1] > tcBuf[0][0]) ?  (tcBuf[0][1] - tcBuf[0][0]) : (0xFFFF - tcBuf[0][0] + tcBuf[0][1]);
	lenPulse =  (tcBuf[1][0] > tcBuf[0][0]) ?  (tcBuf[1][0] - tcBuf[0][0]) : (0xFFFF - tcBuf[0][0] + tcBuf[1][0]);
	if(lenPeriod != 0)
	{
		if(lenPulse > lenPeriod)
		{
			lenPulse = 0xFFFF - lenPulse;
			pr = 100.0f - (float)lenPulse * 100.0f / lenPeriod;
		}
		else pr = (float)lenPulse * 100.0f / lenPeriod;

		printf("Duty Ratio: %.3f\n", pr);

		pr = (float)lenPulse / 72.0f;

		printf("Pulse width: %.3f us\n", pr);

		pr = 72000000.0f / lenPeriod;

		printf("Period: %.1f Hz\n\n", pr);
	}
	else printf("Period: 0 Hz. No Input?? \n\n");
}


//Main while(1)中的串口指令相应程序段,将tcBuf内容发回计算机
		if(buf_uart1.index >= 1)
		{
			BSP_LED1_Blink(3, 100);//For delay and indication
			//HAL_Delay(300);
			strx = strstr((const char*)buf_uart1.buf,(const char*)"DATA");
			printf("%s\n", buf_uart1.buf);
			if(strx)
			{
				__disable_irq();
				  HAL_TIM_IC_Stop_DMA( &htim1,  TIM_CHANNEL_1);		//DMA should be stopped!!
				  HAL_TIM_IC_Stop_DMA( &htim1,  TIM_CHANNEL_2);
				printf("Rising Edge:\n");
				for(i = 0; i < BUF_SIZE; i++)
				{
					printf("%d\n", tcBuf[0][i]);
				}
				printf("Falling Edge:\n");
				for(i = 0; i < BUF_SIZE; i++)
				{
					printf("%d\n", tcBuf[1][i]);
				}
				  HAL_TIM_IC_Start_DMA( &htim1,  TIM_CHANNEL_1, (uint32_t*)&(tcBuf[0]), BUF_SIZE);
				  HAL_TIM_IC_Start_DMA( &htim1,  TIM_CHANNEL_2, (uint32_t*)&(tcBuf[1]), BUF_SIZE);
				__enable_irq();
			}
			Clear_Buffer_UART1();
		}

详细代码请见:

高精度脉冲宽度计双输入捕获+DMA方式-硬件开发文档类资源-CSDN下载

抛砖引玉,如果程序有Bug或者您有改进意见,请您积极留言,非常感谢!
    
 

上一篇:串口重定向printf(串口回车错行问题解决方法)


下一篇:STM32-IO引脚复用-原理和使用