STM32 notes: high precision pulse width meter dual input capture + DMA mode

Posted by sellfisch on Tue, 01 Feb 2022 14:12:47 +0100

This paper introduces how to use STM32F107VC(Waveshare Open107V experimental board) to realize high-precision pulse width meter (duty cycle).

Development environment:

IDE: STM32CubeIDE 1.8

Firmware library: stm32cube_ FW_ F1_ V1. eight point four

Function generation: RIGOL DG5072 function signal generator generates 0-3.3V square wave, 10KHz

Hardware: Waveshare Open107V, STM32F107VC, crystal oscillator 25MHz, working frequency 72MHz

Idea:

*Use the input capture function of the timer to measure the duty cycle of the square wave, and the clock is the system clock 72MHz.

*Set Channel 1 (CH1) as the rising edge capture and Channel 2 (CH2) as the falling edge capture, and the signal is input to CH1 and CH2 at the same time

*Enable DMA of CH1 and CH2 respectively, and send the corresponding capture value to the corresponding memory array tcBuf[2][1024] at the capture time. In this way, on the rising edge of the signal, the comparison register of CH1 automatically obtains the current count value and stores it in the array tcBuf[0][n] through DMA. Then, on the falling edge of the signal, the comparison register of CH2 automatically obtains the current count value and stores it in the array tcBuf[1][n] through DMA

*TIM2 timing interrupt is used to calculate the pulse width, frequency value and duty cycle every 1 second and send it to serial port 1. One group of data can be used, or multiple groups can be averaged, or filtered to improve accuracy.

*When serial port 1 receives the "DATA" instruction, stop the global interrupt, stop DMA, and send tcBuf[0][0...1023] and tcBuf[1][0...1023] back to the computer in turn. Then start DMA, start global interrupt and continue.

Test results:

Duty cycle (%)Pulse width (us)Frequency (Hz)Error (%)
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 

be careful:
* for frequencies below 1kHz, one cycle exceeds the count of TIM1(16bit), which requires more complex processing. This program is not implemented. If you do, can you share it? thank!
* the higher the frequency, the lower the accuracy.
* theoretical resolution of 10kHz signal: 1 / 7200 * 100% = 0.014%, and the experimental results are in good agreement.

Key codes:

uint16_t tcBuf[2][BUF_SIZE] = {0}; //Global variable, tcBuf[0] [] stores the rising edge TC, and tcBuf[1] [] stores the falling edge TC


//TIM1 in main function_ DMA initialization without interrupt
  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);


//Calculate the pulse width, frequency and duty cycle, send it to the serial port and call it every 1 second
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");
}


//The serial port instruction in Main while(1) sends the tcBuf content back to the computer
		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();
		}

For detailed code, see:

High precision pulse width meter dual input capture + DMA mode - hardware development document resources - CSDN Download

If there are bugs in the program or you have suggestions for improvement, please leave a message. Thank you very much!
    
 

Topics: Single-Chip Microcomputer stm32 ARM