使用STM32定时器进行精准高低频测频测试

记录一下STM32稍显艰难的学习过程、、

使用的STM32开发板:微雪的Open407Z

相关资料:Open407Z-C – Waveshare Wiki

MCU型号:STM32F407ZE

本次想要实现一个单片机学习中很普遍也很简单的测频功能,但是实操起来还得进行很多定时器的以及中断的相关操作。。

/****以下为仅使用一个定时器进行捕获的代码,效果较差,可略过****/

首先想到的是用一个定时器进行输入捕获进行测频。本人想偷懒,于是沿用MSP430的那一套逻辑。(MSP430相关部分详细见下链接)

MSP430G2553 频率、占空比、脉冲宽度测量_Krism0912的博客-CSDN博客_msp430测量频率

但是实际效果并不理想,测量的误差非常大,尽管如此,仍然放出一部分代码供参考。

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
		
 if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_7)== GPIO_PIN_SET)
			{
				if(Forward_Posedge_Flag)//First rising edge captured
				{
					TIM3->CNT=0;
					Forward_Posedge_Flag = 0;
				}
				else if(Forward_Posedge_Flag ==0 && Negedge_Flag==1) 
                                                //Second rising edge captured
				{
						HAL_TIM_Base_Stop_IT(&htim3);
						HAL_TIM_IC_Stop_IT(&htim3, TIM_CHANNEL_2);
					
						Posedge_data=TIM3->CCR2;
					
						Posedge_data=Posedge_data+50000*(num1+num2);
						Negedge_data = Negedge_data+ 50000*num1;
						fre = 2000000.0 / Posedge_data ;
						duty_cycle = ((double)Negedge_data )/ ((double)Posedge_data);
						pulse_width = ( (double)Negedge_data) / 2.0 ; //unit:us

						LCD_Display();
						HAL_Delay(800);
					
						Parameter_Init();


						HAL_TIM_Base_Start_IT(&htim3);
						HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_2);
				}
			}
			else // Falling Edge was captured
			{
					if(Forward_Posedge_Flag==0 && Negedge_Flag==0)
					{
							Negedge_data = TIM3->CCR2;
							Negedge_Flag = 1;
					}
			}
}

通过设置TIM3的CHANNEL2进行输入信号的双边沿捕获,以期计算频率和占空比。

上述代码中判断上升沿和下降沿,在if中用的判断语句是

HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_7)== GPIO_PIN_SET

感觉实际上STM32的寄存器里面应该有捕获信号极性的相关寄存器位,但是我暂时没找到。

测量结果不太理想,10kHz会测得10200Hz之类。

P.S信号源升级了,精度绝对没有问题。

无奈,尝试使用等精度测频法。

/******************************以下为等精度测频法**************************/

等精度测频法以前试过在FPGA和MSP430上试过一段时间(见以前的博客),但是这次还是打算从零开始配置。

本次使用的等精度测频法的时序逻辑如下:

(1)在捕捉到一个输入信号上升沿后开启测量闸门(软件闸门);

(2)开启后测量一段时间的信号,且自定义测量的最短时间,该时间硬件闸门控制;

(3)硬件闸门时间到后,硬件闸门关闭,但相关计数器仍继续工作,测量继续,直至捕获到下一个输入信号上升沿后,结束测量并锁存数据;

(4)测量结束后计算相关数据,重新初始化各个参数,准备进行下一次测量。

等精度测量法最为重要的就是一定要使测量时间为被测信号的周期的整数倍,这样才能保证精度。如此一来,实际的测量时间将与实际输入信号的频率有关,更多相关细节网上有很多,这里不再说了。

最终测量将会获得标准信号计数值Nx和被测信号计数值Nb,由此算得实际信号频率。

参考以上步骤,选取STM32定时器资源:

一个定时器用于控制闸门信号(最小测量时间),该定时器只需要实现一个定时功能,定时到后进入中断处理事件置位某个flag标志,以在下一个上升沿结束测量;

一个定时器用于计数标准信号Nx,使用输入捕获功能,捕获到一个上升沿时加1即可;

一个定时器用于计数标准信号,这里标准信号来自于内部时钟,故也仅需普通计数器模式即可,也不需要中断。

总配置:

定时器 模式 Prescaler Counter Period Global Interrupt
TIM2 \ 0 0xFFFFFFFF ×
TIM3(CH2) Input capture direct mode 84-1 0
TIM5 \ 8400-1 10000-1

说明:

TIM2的 Counter Period到32位 (2^32-1>4290M),而TIM2的时钟频率最大为84M,计数值完全够用。如果您的位数不够用可以考虑打开Global Interrupt,设置一个总计数器,在溢出中断里加上Period的值即可。

TIM3 CHANNEL2 为输入捕获模式,不需要设置Counter Period,当然 Prescaler也应该可以随便设置一下。输入的极性设置为上升沿(下降沿也同理)。

 TIM5采用最简单的设置,只需对其分频即可。

 考虑到TIM5挂在APB1总线上,时钟频率为84MHz,则分频后硬件闸门频率为:

                                                  84M/8400/10000=1Hz

也就是最小测量时间在这里设置成了1s。您完全可以更改上述参数设置您需要的时长。

接下来是代码部分,main.c见下

/**
  ******************************************************************************
  * File Name          : main.c
  * Description        : Main program body
  ******************************************************************************
  *
  * COPYRIGHT(c) 2022 STMicroelectronics
  *
  * Redistribution and use in source and binary forms, with or without modification,
  * are permitted provided that the following conditions are met:
  *   1. Redistributions of source code must retain the above copyright notice,
  *      this list of conditions and the following disclaimer.
  *   2. Redistributions in binary form must reproduce the above copyright notice,
  *      this list of conditions and the following disclaimer in the documentation
  *      and/or other materials provided with the distribution.
  *   3. Neither the name of STMicroelectronics nor the names of its contributors
  *      may be used to endorse or promote products derived from this software
  *      without specific prior written permission.
  *
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *
  ******************************************************************************
  */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "stm32f4xx_hal.h"
#include "tim.h"
#include "gpio.h"
#include "fsmc.h"

/* USER CODE BEGIN Includes */
#include "openx07z_c_lcd.h"
#include "Myfunction.h"
#include "Fre_function.h"
/* USER CODE END Includes */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
 

uint16_t GATE_OPEN,GATE_READY2CLOSE;
uint32_t Nx,Nb;

double fre;
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void Error_Handler(void);

/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/

/* USER CODE END PFP */

/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration----------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* Configure the system clock */
  SystemClock_Config();

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_FSMC_Init();
  MX_TIM3_Init();
  MX_TIM2_Init();
  MX_TIM5_Init();

  /* USER CODE BEGIN 2 */
	///LCD SIZE 320*240
  BSP_LCD_Init();
  BSP_LCD_SetBackColor(LCD_COLOR_WHITE);
  BSP_LCD_SetTextColor(LCD_COLOR_BLUE);
  BSP_LCD_SetFont(&Font20);
  BSP_LCD_Clear(LCD_COLOR_WHITE);
  Parameter_Init();
  BSP_LCD_DisplayStringAt(0, 0, (uint8_t *)"Ready to measure", CENTER_MODE );

  HAL_Delay(1000);
	

  HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_2);    //启动输入捕获
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */

  }
  /* USER CODE END 3 */

}

/** System Clock Configuration
*/
void SystemClock_Config(void)
{

  RCC_OscInitTypeDef RCC_OscInitStruct;
  RCC_ClkInitTypeDef RCC_ClkInitStruct;

    /**Configure the main internal regulator output voltage
    */
  __HAL_RCC_PWR_CLK_ENABLE();

  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

    /**Initializes the CPU, AHB and APB busses clocks
    */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 4;
  RCC_OscInitStruct.PLL.PLLN = 168;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 4;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

    /**Initializes the CPU, AHB and APB busses clocks
    */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
  {
    Error_Handler();
  }

    /**Configure the Systick interrupt time
    */
  HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);

    /**Configure the Systick
    */
  HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);

  /* SysTick_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}

/* USER CODE BEGIN 4 */
//输入中断触发的回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{

	if(!GATE_OPEN) //闸门未开启,捕获到上升沿信号后应该开启闸门以开始测量
	{
		//启动闸门 TIM5 开始计数标准信号
//		HAL_TIM_Base_Start(&htim5);
		__HAL_TIM_CLEAR_IT(&htim5, TIM_IT_UPDATE);
		HAL_TIM_Base_Start_IT(&htim5);
		HAL_TIM_Base_Start(&htim2);
		GATE_OPEN=1;
	}
	else
	{
		//测频中,捕捉到一次信号 输入计数值加1
		Nx++;
	}
	
	if(GATE_OPEN&&GATE_READY2CLOSE)
	{
		//关闭闸门 停止捕捉 
		
		HAL_TIM_Base_Stop(&htim2);
		HAL_TIM_IC_Stop_IT(&htim3, TIM_CHANNEL_2);
		HAL_TIM_Base_Stop_IT(&htim5);
		//锁存Nx Nb数据 计算频率值 LCD显示
		
		Nb=__HAL_TIM_GetCounter(&htim2); //		或者 Nb=TIM2->CNT;
		fre=((double)Nx)/((double)Nb)*84000000.0;
		HAL_Delay(500);
		LCD_Display();
		
		//准备下一次测量
		Parameter_Init();
		__HAL_TIM_SetCounter(&htim2,0);  //注意!!!!!!!计数值不会因为定时器暂停而清零
		HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_2);    //启动输入捕获
		
	}
	
}   

/**
  * @brief  Period elapsed callback in non blocking mode
  * @param  htim: TIM handle
  * @retval None
  */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	//TIM5溢出, 1s测量时间到,下一个输入信号上升沿将结束测量
		if(TIM5 == htim->Instance)
    {
			GATE_READY2CLOSE=1;
    }

}
/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @param  None
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler */
  /* User can add his own implementation to report the HAL error return state */
  while(1) 
  {
  }
  /* USER CODE END Error_Handler */
}

#ifdef USE_FULL_ASSERT

/**
   * @brief Reports the name of the source file and the source line number
   * where the assert_param error has occurred.
   * @param file: pointer to the source file name
   * @param line: assert_param error line source number
   * @retval None
   */
void assert_failed(uint8_t* file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
    ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */

}

#endif

/**
  * @}
  */

/**
  * @}
*/

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

完完全全按照自己设想的逻辑写代码即可,并不复杂。

有一点稍微注意一下:停止TIM2获取Nb值后需要将CNT值清零,因为HAL_TIM_Base_Stop(&htim2); 并不会清零该值。

/********************下面部分与测频无关,不看也可*********************/

计划用LCD显示数据。

对于320*240的TFT液晶显示器,官方资料里有很多函数可以拿来直接用,全在openx07z_c_lcd.c文件里。而且根据其中基本函数,也可以自己造一些简单的功能函数。

比如在指定位置显示一个指定位数的双精度浮点数:

void BSP_LCD_Show_double(uint16_t x,uint16_t y,double f,int n);

#include "stm32f4xx_hal.h"
#include "openx07z_c_lcd.h"
#include "Myfunction.h"
#include "../Fonts/fonts.h"

extern LCD_DrawPropTypeDef DrawProp;

//x,y是坐标;f是要显示的数字,n是展示的小数位数 
void BSP_LCD_Show_double(uint16_t x,uint16_t y,double f,int n)
{
    unsigned int temp,i,j;
    uint16_t num[10];  //预留的存储空间,不够时更改即可
	  uint16_t size;
	
// 按我自己的喜好调整的字符间隔。可更改。 
		switch(DrawProp.pFont->Width)
		{
			case 17:case 14:case 11:size=DrawProp.pFont->Width -3; break; //Font24,Font20,Font16
			case 7: size=DrawProp.pFont->Width -2; break;  //Font12;
			case 5: size=DrawProp.pFont->Width; break;  //Font8;
			default:size=DrawProp.pFont->Width;
		}
        temp = (int)f;
        if(temp!=0)
        {
            for(i=0;temp!=0;i++)      //计算整数位数
                temp /=10;
            temp = (int)f;
					
            for(j=i;j>0;j--)
            {
                num[j] = temp%10+'0';
                temp /=10;
            }
						
            for(j=1;j<=i;j++)           //依次展示
            {
                BSP_LCD_DisplayChar(x,y,num[j]); 
								x+=size;
            }
        }
        else
        {
            BSP_LCD_DisplayChar(x,y,'0'); 
				   	x+=size;
        }


        BSP_LCD_DisplayChar(x,y,'.'); x+=size;   

        f -=(int)f;
        for(i=0;i<n;i++)
        {
            f*=10;
            BSP_LCD_DisplayChar(x,y,(int)f+'0'); x+=size;  
            f-=(int)f;
        }
}

当然上述功能也可以直接通过sprintf函数实现,想怎么用就怎么用。 

/****************************下面是测试效果*********************************/

0.5Hz

1Hz

 50kHz
  500kHz

测试的频率范围:0.5Hz-500kHz

测量误差在0.02% (万分之二)左右 

/**************************************END**********************************/

/********************************更新**********************************/

/***************************高频测量部分***************************/

  上述等精度测频方法,由于使用了定时器的捕获功能,捕获到输入信号上升沿时需要进入中断将Nx的值进行加一操作,而本身进入中断进行处理就会需要执行大量语句,进而耗费大量的时间和资源,从而当频率过高时,需要进入中断的次数过多,无法完成测频功能,如上所述,上限约为500kHz。

今日有幸了解到,将待测信号作为外部时钟输入,可以在不进入中断的情况下对其进行计数,有机会测量更高的频率。

首先先看看该方法能否测高频。

将TIM3原有的的输入捕获设置进行更改,并将其从模式设置为外部输入时钟模式1

 具体设置:不分频(Prescaler=0);

                   CounterPeriod设置为0xFFFF(65535);

                    上升沿触发;

此外,输入通道将为TIM3的CHANNEL_1;

上图来自:DS8626: Arm® Cortex®-M4 32b MCU+FPU, 210DMIPS, up to 1MB Flash/192+4KB RAM, USB OTG HS/FS, Ethernet, 17 TIMs, 3 ADCs, 15 comm. interfaces & camera

Pinouts and pin description 部分 

 上图来自:RM0090 中文参考手册 通用定时器框图

剩余两个定时器仍保持原样,不做更改(即TIM2:计数基频值Nb,TIM5控制闸门时间为1s)。

由于是高频,实际采样时间完全可以设置为1s。

比如:高频时1秒计数值Nx约为500k,等精度测频会延长测频时间至测量完整的整数个待测信号整个周期,实际测频时间可能为1s*(1+1/500k)。高频时完全可以忽略该差别。但是低频就不可以咯(1Hz输入可能得测2秒)。

回到高频测量,如上所述,在TIM5的中断里即可结束整个测频过程并开始计算。这样完全抛弃了等精度测频的方法

完整代码:

/**
  ******************************************************************************
  * File Name          : main.c
  * Description        : Main program body
  ******************************************************************************
  *
  * COPYRIGHT(c) 2022 STMicroelectronics
  *
  * Redistribution and use in source and binary forms, with or without modification,
  * are permitted provided that the following conditions are met:
  *   1. Redistributions of source code must retain the above copyright notice,
  *      this list of conditions and the following disclaimer.
  *   2. Redistributions in binary form must reproduce the above copyright notice,
  *      this list of conditions and the following disclaimer in the documentation
  *      and/or other materials provided with the distribution.
  *   3. Neither the name of STMicroelectronics nor the names of its contributors
  *      may be used to endorse or promote products derived from this software
  *      without specific prior written permission.
  *
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *
  ******************************************************************************
  */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "stm32f4xx_hal.h"
#include "tim.h"
#include "gpio.h"
#include "fsmc.h"

/* USER CODE BEGIN Includes */
#include "openx07z_c_lcd.h"
#include "Myfunction.h"
#include "Fre_function.h"
/* USER CODE END Includes */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
 
uint32_t Nx,Nb;

double fre;
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void Error_Handler(void);

/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/

/* USER CODE END PFP */

/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration----------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* Configure the system clock */
  SystemClock_Config();

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_FSMC_Init();
  MX_TIM2_Init();
  MX_TIM5_Init();
  MX_TIM3_Init();

  /* USER CODE BEGIN 2 */
	__HAL_TIM_CLEAR_IT(&htim5, TIM_IT_UPDATE);
	__HAL_TIM_CLEAR_IT(&htim3, TIM_IT_UPDATE);
    BSP_LCD_Init();
    BSP_LCD_SetBackColor(LCD_COLOR_WHITE);
    BSP_LCD_SetTextColor(LCD_COLOR_BLUE);
	BSP_LCD_SetFont(&Font20);
    BSP_LCD_Clear(LCD_COLOR_WHITE);
	Parameter_Init();
	BSP_LCD_DisplayStringAt(0, 0, (uint8_t *)"Ready to measure", CENTER_MODE );
	HAL_Delay(1000);
	

//  HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);    //启动输入捕获
	HAL_TIM_Base_Start_IT(&htim5);
	HAL_TIM_Base_Start_IT(&htim3);
	HAL_TIM_Base_Start(&htim2);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */

  }
  /* USER CODE END 3 */

}

/** System Clock Configuration
*/
void SystemClock_Config(void)
{

  RCC_OscInitTypeDef RCC_OscInitStruct;
  RCC_ClkInitTypeDef RCC_ClkInitStruct;

    /**Configure the main internal regulator output voltage
    */
  __HAL_RCC_PWR_CLK_ENABLE();

  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

    /**Initializes the CPU, AHB and APB busses clocks
    */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 4;
  RCC_OscInitStruct.PLL.PLLN = 168;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 4;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

    /**Initializes the CPU, AHB and APB busses clocks
    */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
  {
    Error_Handler();
  }

    /**Configure the Systick interrupt time
    */
  HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);

    /**Configure the Systick
    */
  HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);

  /* SysTick_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}

/* USER CODE BEGIN 4 */


/**
  * @brief  Period elapsed callback in non blocking mode
  * @param  htim: TIM handle
  * @retval None
  */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	//规定1s测量时间到
		if(TIM5 == htim->Instance)
    {
			
			HAL_TIM_Base_Stop_IT(&htim5);
			HAL_TIM_Base_Stop_IT(&htim3);
			HAL_TIM_Base_Stop(&htim2);
			Nx+=TIM3->CNT;
			Nb=TIM2->CNT;
			fre=((double)Nx)/((double)Nb)*84000000.0;
			LCD_Display();			
	        Parameter_Init();
	  	__HAL_TIM_SetCounter(&htim2,0);  //注意!!!!!!!计数值不会因为定时器暂停而清零
		__HAL_TIM_SetCounter(&htim3,0);  //注意!!!!!!!计数值不会因为定时器暂停而清零

			
			HAL_TIM_Base_Start_IT(&htim5);
			HAL_TIM_Base_Start_IT(&htim3);
			HAL_TIM_Base_Start(&htim2);
    }
		else if(TIM3 == htim->Instance)
    {
			Nx+=0xFFFF;
    }

}
/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @param  None
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler */
  /* User can add his own implementation to report the HAL error return state */
  while(1) 
  {
  }
  /* USER CODE END Error_Handler */
}

#ifdef USE_FULL_ASSERT

/**
   * @brief Reports the name of the source file and the source line number
   * where the assert_param error has occurred.
   * @param file: pointer to the source file name
   * @param line: assert_param error line source number
   * @retval None
   */
void assert_failed(uint8_t* file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
    ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */

}

#endif

/**
  * @}
  */

/**
  * @}
*/

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

说明:

1.TIM3是16位的,高频计数Nx时可能不够值,所以在TIM3的溢出中断里及时更新Nx的值(虽然还是进中断了,但是次数变成了原来的1/65535,测试情况完全可以接受)。对于此,如果您使用TIM2,那么32位计数将完全够用。

2.开头带BSP的函数以及LCD_Display()仍然是LCD显示相关函数,可以不用care。

3.不要忘记开NVIC全局中断!!

测试效果还可以,不放图了,下面一起放。

/************************上述两种方法结合************************/

如上所述,高频部分测低频不准,低频部分测不了高频。既然做了以上工作,遂想方设法将其结合。

有一点小小说一下:咱们不可能说低频测试的时候效果OK,高频不行了,把输入信号手动换到高频的输入引脚去,之后测低频又换回来。这种比较麻烦,而且一般设计会避免这种问题。

所以,等精度测频时TIM3的输入捕获将为CHANNEL_1,与高频测量保持一致,这样信号输入口将保持为PA6,不用换。

说是结合,但是我们看到高低频测量TIM3的配置完全不一样,就不像稍微改一个Prescaler或者CNT的值那样简单。

作者偷懒(其实是不会自己一点点设置),遂将高频部分的TIM3初始化函数复制了过来。,放到tim.c中当作用户函数用。 

本来以为这样估计不行,但是居然可以。。接下来补上一些高低频率测量模式的转换逻辑就可以了。

转换逻辑很简单:给一个判断条件,抛弃当前测频结果,停止当前所有的计时器,重新配置TIM3引脚,重开计时器即可。

我设置的是10000Hz以上认为是高频,用高频方法,否则低频,用等精度测频法。

/**
  ******************************************************************************
  * File Name          : main.c
  * Description        : Main program body
  ******************************************************************************
  *
  * COPYRIGHT(c) 2022 STMicroelectronics
  *
  * Redistribution and use in source and binary forms, with or without modification,
  * are permitted provided that the following conditions are met:
  *   1. Redistributions of source code must retain the above copyright notice,
  *      this list of conditions and the following disclaimer.
  *   2. Redistributions in binary form must reproduce the above copyright notice,
  *      this list of conditions and the following disclaimer in the documentation
  *      and/or other materials provided with the distribution.
  *   3. Neither the name of STMicroelectronics nor the names of its contributors
  *      may be used to endorse or promote products derived from this software
  *      without specific prior written permission.
  *
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *
  ******************************************************************************
  */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "stm32f4xx_hal.h"
#include "tim.h"
#include "gpio.h"
#include "fsmc.h"

/* USER CODE BEGIN Includes */
#include "openx07z_c_lcd.h"
#include "Myfunction.h"
#include "Fre_function.h"
/* USER CODE END Includes */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
 

uint16_t GATE_OPEN,GATE_READY2CLOSE;
uint32_t Nx,Nb;
uint8_t Fre_State=LOW_FREQUENCY;
double fre;
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void Error_Handler(void);

/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/

/* USER CODE END PFP */

/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration----------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* Configure the system clock */
  SystemClock_Config();

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_FSMC_Init();
  MX_TIM2_Init();
  MX_TIM5_Init();
  MX_TIM3_Init();

  /* USER CODE BEGIN 2 */
    BSP_LCD_Init();
    BSP_LCD_SetBackColor(LCD_COLOR_WHITE);
    BSP_LCD_SetTextColor(LCD_COLOR_BLUE);
	BSP_LCD_SetFont(&Font20);
    BSP_LCD_Clear(LCD_COLOR_WHITE);
	Parameter_Init();
	BSP_LCD_DisplayStringAt(0, 0, (uint8_t *)"Ready to measure", CENTER_MODE );
	BSP_LCD_DisplayStringAt(0, 100, (uint8_t *)"INPUT PIN:PA6", CENTER_MODE );
	HAL_Delay(1000);
	
    HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);    //启动输入捕获
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */

  }
  /* USER CODE END 3 */

}

/** System Clock Configuration
*/
void SystemClock_Config(void)
{

  RCC_OscInitTypeDef RCC_OscInitStruct;
  RCC_ClkInitTypeDef RCC_ClkInitStruct;

    /**Configure the main internal regulator output voltage
    */
  __HAL_RCC_PWR_CLK_ENABLE();

  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

    /**Initializes the CPU, AHB and APB busses clocks
    */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 4;
  RCC_OscInitStruct.PLL.PLLN = 168;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 4;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

    /**Initializes the CPU, AHB and APB busses clocks
    */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
  {
    Error_Handler();
  }

    /**Configure the Systick interrupt time
    */
  HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);

    /**Configure the Systick
    */
  HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);

  /* SysTick_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}

/* USER CODE BEGIN 4 */
//输入中断触发的回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{

	if(!GATE_OPEN)
	{
		//启动闸门 TIM5 开始计数标准信号
		__HAL_TIM_CLEAR_IT(&htim5, TIM_IT_UPDATE);
		HAL_TIM_Base_Start_IT(&htim5);
		HAL_TIM_Base_Start(&htim2);
		GATE_OPEN=1;
	}
	else
	{
		//测频中,捕捉到一次信号 输入计数值加1
		Nx++;
		if(Nx>20000)  
		{
			HAL_TIM_Base_Stop(&htim2);
			HAL_TIM_IC_Stop_IT(&htim3, TIM_CHANNEL_1);
			HAL_TIM_Base_Stop_IT(&htim5);
			Turn2HIGH_FREQUENCY_MODE();
		}
	}
	
	if(GATE_OPEN&&GATE_READY2CLOSE)
	{
		//关闭闸门 停止捕捉 
		HAL_TIM_Base_Stop(&htim2);
		HAL_TIM_IC_Stop_IT(&htim3, TIM_CHANNEL_1);
		HAL_TIM_Base_Stop_IT(&htim5);
		//锁存Nx Nb数据 计算频率值
		Nb=__HAL_TIM_GetCounter(&htim2); 
		fre=((double)Nx)/((double)Nb)*84000000.0;
		//判断
		if(fre<10000.0)
		{
		  	    // LCD显示
				LCD_Display();
				//准备下一次测量
				Parameter_Init();
				__HAL_TIM_SetCounter(&htim2,0);  //注意!!!!!!!计数值不会因为定时器暂停而清零
				HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);    //启动输入捕获
		}
		else
		{
			Turn2HIGH_FREQUENCY_MODE();
		}
	}
	
}   

/**
  * @brief  Period elapsed callback in non blocking mode
  * @param  htim: TIM handle
  * @retval None
  */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	//闸门溢出 规定测量时间到
		if(TIM5 == htim->Instance&&Fre_State==LOW_FREQUENCY)
    {
			GATE_READY2CLOSE=1;
    }
		else if(TIM5 == htim->Instance&&Fre_State==HIGH_FREQUENCY)
    {
			HAL_TIM_Base_Stop_IT(&htim5);
			HAL_TIM_Base_Stop_IT(&htim3);
			HAL_TIM_Base_Stop(&htim2);
			Nx+=TIM3->CNT;
			Nb=TIM2->CNT;
	    	fre=((double)Nx)/((double)Nb)*84000000.0;
			if(Nx>=10000.0)
			{
			  LCD_Display();
	          Parameter_Init();
	  	      __HAL_TIM_SetCounter(&htim2,0);  
			  __HAL_TIM_SetCounter(&htim3,0); 

			  HAL_TIM_Base_Start_IT(&htim5);
			  HAL_TIM_Base_Start_IT(&htim3);
			  HAL_TIM_Base_Start(&htim2);
			}
			else
			{
				Turn2LOW_FREQUENCY_MODE();
			}
    }
		
		else if(TIM3 == htim->Instance&&Fre_State==HIGH_FREQUENCY)
    {
			Nx+=0xFFFF;
    }

}
/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @param  None
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler */
  /* User can add his own implementation to report the HAL error return state */
  while(1) 
  {
  }
  /* USER CODE END Error_Handler */
}

#ifdef USE_FULL_ASSERT

/**
   * @brief Reports the name of the source file and the source line number
   * where the assert_param error has occurred.
   * @param file: pointer to the source file name
   * @param line: assert_param error line source number
   * @retval None
   */
void assert_failed(uint8_t* file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
    ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */

}

#endif

/**
  * @}
  */

/**
  * @}
*/

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

说明:

1.设置了变量Fre_State作为当前高频模式和低频模式的标志。

2.低频模式下,如果输入频率过高(2M左右),闸门关不了,无法算出频率并重新改成高频模式。Debug的时候发现Nx蹭蹭蹭往上涨,遂只能在捕获语句的Nx++后进行一个判断以改变模式,不然直接卡死。这是为什么作者也不明白。。。= =

3.其他一些函数见下,也许能帮助您理解作者设计的转换流程,当然您也可以自己尝试。

#include "openx07z_c_lcd.h"
#include "Myfunction.h"
#include "tim.h"
#include "Fre_function.h"
extern unsigned int Forward_Posedge_Flag,Negedge_Flag;


extern double fre;
extern uint16_t GATE_OPEN,GATE_READY2CLOSE;
extern uint32_t Nx,Nb;
extern uint8_t Fre_State;
void Parameter_Init(void)
{

	GATE_OPEN=0;
	GATE_READY2CLOSE=0;
	Nx=0;
	Nb=0;
	fre=0.0;
}

void Turn2HIGH_FREQUENCY_MODE(void)
{
		    Fre_State=HIGH_FREQUENCY;
			MX_TIM3_Init_ETR();
			
			Parameter_Init();
	  	   __HAL_TIM_SetCounter(&htim2,0);  
			__HAL_TIM_SetCounter(&htim3,0);  

			HAL_TIM_Base_Start_IT(&htim5);
			HAL_TIM_Base_Start_IT(&htim3);
			HAL_TIM_Base_Start(&htim2);
}


void Turn2LOW_FREQUENCY_MODE(void)
{
		Fre_State=LOW_FREQUENCY;
		Parameter_Init();
	    __HAL_TIM_SetCounter(&htim2,0);  
		MX_TIM3_Init();
		HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);    //启动输入捕获
}

void LCD_Display(void)
{
  		BSP_LCD_SetFont(&Font20);
        BSP_LCD_Clear(LCD_COLOR_WHITE);
	    BSP_LCD_DisplayStringAt(0, 0, (uint8_t *)"Frequency:", LEFT_MODE );
	  if(fre<10000.0)
		{
	    BSP_LCD_Show_double(150,0,fre,4);
		BSP_LCD_DisplayStringAt(280, 0, (uint8_t *)"Hz", LEFT_MODE );
		BSP_LCD_DisplayStringAt(0, 200, (uint8_t *)"LOW FREQUENCY", CENTER_MODE );
		}
		else
		{
			 BSP_LCD_Show_double(150,0,fre/1000,4);
	    	 BSP_LCD_DisplayStringAt(260, 0, (uint8_t *)"kHz", LEFT_MODE );
			 BSP_LCD_DisplayStringAt(0, 200, (uint8_t *)"HIGH FREQUENCY", CENTER_MODE );
		}
	
		BSP_LCD_Show_int(200,60,(int)Nx);
	    BSP_LCD_Show_int(200,120,(int)Nb);
	
		BSP_LCD_SetFont(&Font16);
	    BSP_LCD_DisplayStringAt(0, 30, (uint8_t *)"Input Signal Counter Nx:", LEFT_MODE );
		BSP_LCD_DisplayStringAt(0, 90, (uint8_t *)"Internal Signal Counter Nb:", LEFT_MODE );
	

}

放一些测试结果。。

 

 

目前高频测试到10MHz,低频仍然测试到0.5Hz。

本文代码有点多,有些地方留有一大堆空行,有些甚至还没对齐,但作者懒得改了。。。

感谢您读到这里,祝您生活愉快!

物联沃分享整理
物联沃-IOTWORD物联网 » 使用STM32定时器进行精准高低频测频测试

发表评论