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:
If there are bugs in the program or you have suggestions for improvement, please leave a message. Thank you very much!