手把手教你开发stm32——定时器(上)(基于hal库)

1.stm32定时器介绍

1.1.stm32f103定时器介绍

  • 定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断。
  • 16位计数器、预分频器、自动重装载寄存器的时基单元。
  • 不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能。
  • 根据复杂度和引用场景分为了高级定时器、通用定时器、基本定时器三种类型。
    在这里插入图片描述
    以上是各类定时器的主要功能

1.2.定时器计数模式

在这里插入图片描述

  1. 向上计数模式
    计数器从0计数到自动加载值(TIMx_ARR),然后重新从0开始技术并且产生一个计数器溢出事件。
  2. 向下计数模式
    计数器从自动装入的值(TIMx_ARR)开始向下计数到0,然后从自动装入的值重新开始,并产生一个计数器向下溢出事件。
  3. 中央对齐模式(向上/向下计数)
    计数器从0开始计数到自动装入的值-1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器溢出四件;然后再从0开始重新计数。

1.3.定时器的时钟

在这里插入图片描述
上图是stm32的系统结构,我们可以从上图中看到,TIM1和TIM8是挂载到APB2总线上的,而TIM2-TIM7是挂载到APB1总线上的。
在stm32f103系列的MCU中,虽然定时器挂载的总线是不同的,但是每个定时器的时钟频率都是相同的。而其他系列的MCU,比如说stm32f407系列的MCU,如果挂载到不同的总线上,那么定时器的时钟频率可能是不同的。从下图配置的cubemx图中我们可以看到每个定时器上具体的时钟。
在这里插入图片描述
我们从上面这个图中可以看到,挂载到APB1和APB2总线上的定时器的时钟都是72MHZ。

2.stm32时钟的工作方式

我们这个地方主要就是讲一下定时器基本的定时计数的功能,定时器基本的定时计数功能主要是通过以下的框图来进行的。
在这里插入图片描述

  • 时钟源:定时器时钟TIMxCLK,即内部时钟CK_INT,经过APB预分频器分频后提供,就是从APB总线上引出的时钟来源。
  • 计数器时钟:计数器时钟是经过②PSC预分频器后产生的,就是由于不需要APB总线上这么高频率的时钟,所以在时钟输入以后,还需要进行预分频以后,才能产生真正驱动CNT计数器的时钟,每个定时器的时钟的预分频器PSC都是最高16位的,所以不能超过预分频器的范围。
  • 计数器CNT:计数器CNT是进行计数的单元,当接收到CK_CNT来的时钟信号以后,每经过一个时钟频率,CNT就会进行向上计数或者向下计数,当向上计数到自动重装载寄存器的值(ARR)或者从ARR减到0以后,就会产生中断或者事件更新。计数器CNT是一个16位/32位的计数器,每个定时器都是不同的,所以我们需要根据实际的情况来进行设置。
  • 自动重装载寄存器(ARR):这里面装着计数器能计数的最大数值,当计数到这个值的时候,如果使能了中断的话,定时器就会产生溢出中断。
  • 计时中断时间:1/(TIMxCLK/(PSC+1)*(ARR+1))

3.定时器中断具体实现

我们需要实现的目标是利用基本定时器实现定时1s中断,并在中断处理函数中控制LED灯的亮灭。

3.1.cubemx的具体配置

首先配置TIM2的相关参数
在这里插入图片描述
其次配置TIM2的相关中断
在这里插入图片描述

3.2.具体代码的实现

在这里插入图片描述
我们设置了定时器中断,同样需要在这个中断C文件中去寻找相关的TIM2的中断,我们找到这个函数以后追进去。
在这里插入图片描述
我们找到定时器更新事件,不出意外需要重写回调函数。
在这里插入图片描述
进行回调函数的重写,写的规则是if(htim->Instance == TIM2) {HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_1);},这样就可以在每一秒钟,使得PA1口的LED灯进行亮灭的操作。
其中htim->Instance 是指htim是一个结构体指针,然后指向他的成员变量intance(这个是寄存器基地址),如果这个寄存器的基地址等于TIM2的基地址,则证明这两者相同,也就是说是TIM2产生的事件更新中断,然后再进行下一步的操作。

写到这个地方以后,我们还需要进行定时器中断时基单元的开启,这样,定时器才能开始计数。
在这里插入图片描述
我们在main函数中开启TIM2的时基单元,这样就能实现最终的效果了。

4.通用定时器功能分析

通用定时器除了最基本的定时功能以外,还有输入捕获和输出比较的功能,具体的功能我们可以通过下面的结构框图来进行了解。
在这里插入图片描述
我们一块一块来分析

  1. 第1块是整个定时器的时钟源,用来进行时钟源的选择,时钟源有以下几个选择:

    • 内部时钟(CK_INT):内部时钟源就是通过stm32的内部时钟来进行时钟的输入的,通过内部时钟总来作为定时器的时钟来源
    • 外部时钟模式1:外部输入引脚Tlx(x=1,2,3,4),采用定时器的外部通道引脚的输入信号(TI1,TI2)作为定时器的时钟源。TI1(TI2)通过滤波和边缘检测,得到信号TI1FP2(TI2FP2)作为计数器的触发信号。值得注意的是,只有TI1FP2和TI2FP2可以作为时钟源,也就是说想要使用外部时钟模式1,时钟信号只能通过通道1或者通道2输入。
    • 外部时钟模式2:外部触发输入ETR,采用定时器的ETR引脚作为外部时钟信号源,通过边沿检测和滤波来作为定时器的时钟源。
    • 外部触发输入(ITRx):使用一个定时器作为另一定时器的预分频器,将主定时器的TRGO信号作为从定时器的ITRx输入作为时钟源。
      一般而言,我们都是使用内部时钟作为定时器的时钟来源
  2. 第2块是通用定时器的控制器,主要包括触发控制器、从模式控制器以及编码器接口。触发控制器用来针对片内外设输出触发信号,比如为其他定时器提供时钟和触发DAC/ADC转换。编码器接口专门针对编码器计数来使用;从模式控制器可以控制计数器复位、启动、递增/递减、计数等功能。控制器其实说白了就是时钟系统的配置单元,一个外设中需要使能相应的功能那么就需要进行控制器的配置,比如说我们需要配置定时器的中断、定时器主从级联、定时器发送信号等功能就需要进行控制器的配置,由控制器来控制整个定时器的功能。

  3. 第3块是时基单元,定时器产生的定时计数就是从这个地方产生的。通用定时器的时基单元主要由预分频器PSC、计数器CNT、自动冲转载寄存器ARR组成的。时钟单元我们在第二章stm32时钟工作方式讲过,这个地方就不再赘述。

  4. 第4块是输入捕获的部分,输入捕获部分我们主要就是用来捕获外部输入信号的频率或者脉宽,我们肯定是需要去将外部的信号去连接我们的定时器通道。

    • 当一个信号输入进来以后,首先我们需要对输入进来的信号进行一个滤波,因为输入进来的信号可能不是很稳定的信号,所以我们首先需要进行滤波的操作;然后我们还需要进行一个边沿的检测,当我们需要去检测一个外部信号的脉宽或者频率的时候,我们首先要想的就是如何去检测这个信号,我们肯定是要用边沿来检测这样一个信号,如果要去检测脉宽,那么就需要测量一个信号的上升沿/下降沿和下降沿/上升沿,如果需要去检测一个信号的频率,那么我们就需要去检测一个信号的两次边沿,这样就能测量出来这个信号的频率,这就是我们需要使用一个输入滤波和边沿检测器的原因了。
    • 采集到信号以后,我们需要进行预分频器的设置,我们可以去设置这个预分频器,也可以不去设置这个预分频器的值,如果设置这个预分频器,比如说我们将预分频器的值设置为2,那么这个意思就是,我们在采集信号频率的时候,并不是采集到两次上升沿或者两次下降沿就去报告我们采集到这个信号的频率,我们需要去采集四次上升沿和下降沿再去汇报这个信号的频率,这样做的目的就是我们可以进行信号的一次采集滤波,更好地去测试信号的频率。
  5. 第5块是捕获/比较寄存器,输入捕获和输出比较的时候都需要用到这个寄存器,在我们使用输入捕获的功能的时候,我们可以用这个寄存器来存放我们输入捕获到的值,这就是我们在输入捕获过程中需要使用这个寄存器的方法;而输出比较的时候,我们需要通过这个寄存器来设置CCR的值,通过这个CCR的值和ARR的值的比较,我们就可以输出一个PWM的波形,这样就可以用作电机PWM调速或者产生呼吸灯的效果,具体的输出比较功能我们放在第6块来讲解。

  6. 输出比较的功能就是用来产生PWM波形,下面这个图就是显示PWM的原理。
    我们可以看到在0-t1的过程中,CCR的值小于ARR的值,这样就可以产生一个低电平;在t1-t2的过程中,ARR的值大于CCR,这样就可以产生一个高电平;我们知道,PWM的全称是脉冲宽度调制,当高低电平在一个周期中有不同的时候,这样就可以产生不同的电压。比如说我们知道高电平是3.3V,当低电平时间在一个周期里面占40%,那么高电平时间占60%的时间,那么这样在一个周期里面的平均电平就是1.98V,这样就是PWM的原理,通过输出比较的功能,我们就可以看到这样就可以产生一个PWM的信号用来驱动电机或者产生呼吸灯的效果。
    在这里插入图片描述

5.高级定时器功能分析

在这里插入图片描述
高级定时器和通用定时器的主要区别就是在第3块和第5块的地方
第3块主要的区别就是,高级定时器比通用定时器多了一个重复次数计数器,重复次数计数器的作用就是,当CNT计数器产生溢出的时候,并不会直接产生中断,而是会将溢出传递到重复次数计数器,重复次数计数器也会存一个数值,每次CNT计数器产生溢出以后,重复次数计数器数值就会减一,当重复次数计数器减到0以后,才会产生溢出。
第5块主要区别就是高级定时器增加了可编程的死区互补输出功能,主要应用在工业电机控制方面。但是目前来讲,我没有用到过这个功能,因此我也就不在这里班门弄斧了。

6.输入捕获实验

我们上面介绍过,定时器有一个非常好用的功能就是输入捕获,很多的示波器的原理就是利用时钟的输入捕获的功能,本章节就来具体讲一个输入捕获的实验。
实验目标:利用定时器4的输入捕获功能测量按键按下后低电平持续的时间,并用LED灯亮的时间来显示捕获的时间。

6.1.理论知识

输入捕获功能框图如下图所示
在这里插入图片描述
我们可以从每个定时器的通道中来进行输入捕获,具体的结构分析我们在上面的理论知识中已经讲过了,这里就不再赘述了。

6.2.cubemx配置

在这里插入图片描述
在这里插入图片描述
其他配置都和原来的配置相同,将调试端口、时钟配置、文件名配置完成后就可以生成对应的文件了。

6.3.具体代码实现

实验目标:利用定时器4的输入捕获功能测量按下按键后低电平持续的时间,并通过PA3口LED灯亮的时间来显示。

  1. 首先输入捕获模式,可以用来测量频率或者电平保持的时间,如下图所示:
    在这里插入图片描述
    我们如果需要捕获低电平的时间,就需要从value2处触发一次捕获中断,然后再从value3处触发一次捕获中断,通过这两次捕获中断的差值就可以来计算出低电平持续的时间了。但是我们一开始设置的是下降沿捕获,而第二次捕获需要进行上升沿的捕获,所以中间就会涉及到一次边沿捕获方式的转变。
  2. 然后我们需要考虑捕获的时间,由于我们低电平持续的时间可能会大于定时器ARR的值,导致中途溢出,如下图所示:
    在这里插入图片描述
    所以我们还需要考虑每次中途溢出的时间,我们可以用一个标志位来记录每次按键按下以后,低电平持续时间中,定时器中断溢出了几次,这样我就可以使用标志位*ARR的值来计算溢出的时间,然后通过最后捕获的时间减去初始时间就能得到最后精确的值了。

以下是我的代码的具体实现方式:

  1. 我们首先找到TIM4的中断函数
    在这里插入图片描述
  2. 追进去以后需要找到两个回调函数,第一个就是定时器中断溢出的回调函数,第二个就是输入捕获的回调函数,我们找到这两个回调函数以后,需要对这两个回调函数进行重写,来实现我们的功能。

在这里插入图片描述
在这里插入图片描述
3. 两个回调函数的重写
我的两个回调函数是在time.c中重写并实现具体的功能的,我的回调函数都有写注释,然后具体可以看我写的注释。

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	if(htim->Instance==TIM4)
	{
		HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_3);  //当按键按下以后,每过1s钟,PA3上的LED灯就翻转一次
		tim_value+=10000;  //按键按下后,当到达溢出以后,tim_value的值就加上ARR设置的值,ARR设置的值为10000
	}
}


void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)  //输入捕获中断的回调函数
{
	if(htim->Instance==TIM4)  //检查是否为TIM4触发的中断
	{
		if(toggle_flag==0) //检查标志位
		{
			HAL_Delay(20); //消抖
			if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_6)==GPIO_PIN_RESET)
			{
				toggle_flag=1;  //进入中断后首先翻转标志位
				HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_SET); //PA2的LED灯亮200ms,显示进入输入捕获函数
				HAL_Delay(200);
				HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_RESET);
				HAL_TIM_Base_Start_IT(htim); //开启溢出中断,开始进行计时
				
				__HAL_TIM_DISABLE(htim);  //关闭TIM4
				
				__HAL_TIM_SET_COUNTER(htim,0); //设置时钟计数值为0
				TIM_RESET_CAPTUREPOLARITY(htim,TIM_CHANNEL_1);  //清除TIM4通道1的原始设置
				TIM_SET_CAPTUREPOLARITY(htim,TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_RISING);//将触发输入捕获中断源设置为上升沿捕获
				
				__HAL_TIM_ENABLE(htim);//使能TIM4
			} 
		}
		else if(toggle_flag==1)//检查标志位
		{
			HAL_Delay(20);//消抖
			if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_6)==GPIO_PIN_SET)//检查是否为上升沿
			{
				toggle_flag=0; 
				HAL_TIM_Base_Stop_IT(htim);//关闭溢出中断
				HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_SET);//若是上升沿触发输入捕获中断,LED灯亮200ms
				HAL_Delay(200);
				HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_RESET);
				
				tim_value=(tim_value+HAL_TIM_ReadCapturedValue(&htim4,TIM_CHANNEL_1 ))/10+20;//计算捕获的总时间
				
				HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_SET); //计算出总时间后,使得PA2口的LED灯亮的时间和总时间相同,以显示输入捕获效果
				HAL_Delay(tim_value);
				HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_RESET);
				
				__HAL_TIM_DISABLE(htim);
				
				TIM_RESET_CAPTUREPOLARITY(htim,TIM_CHANNEL_1);
				TIM_SET_CAPTUREPOLARITY(htim,TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_FALLING);
				
				__HAL_TIM_ENABLE(htim);
			}
		}
	}
}
  1. 其他工作
    我们用到了定时器的相关内容,就需要使能这些内容。
  • 首先我们需要开启定时器的输入捕获功能。
    在这里插入图片描述
  • 当然我们还需要开启定时器溢出中断的功能,但是这部分功能我在重写输入捕获的回调函数的时候才进行操作,具体可以看我上面的代码
    在这里插入图片描述
  • 定义相关变量
    我们在重写定时器溢出回调函数和定时器输入捕获回调函数的时候用到了一些变量,我们需要在time.c中定义这些变量
    在这里插入图片描述

下面是我的time.c和main.c中的具体代码
main.c

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2023 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "tim.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

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

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
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();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

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

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM4_Init();
  /* USER CODE BEGIN 2 */
	
	HAL_TIM_IC_Start_IT(&htim4,TIM_CHANNEL_1);
//	extern uint32_t tim_value;
//	extern uint8_t send_flag;
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
//	  if(send_flag==1)
//	  {
//		  tim_value/=1000;
//		  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET);
//		  HAL_Delay(tim_value);
//		  tim_value=0;
//	  }
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses 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_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

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

/* USER CODE BEGIN 4 */



/* USER CODE END 4 */

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

#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 /* USE_FULL_ASSERT */

time.c

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    tim.c
  * @brief   This file provides code for the configuration
  *          of the TIM instances.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2023 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "tim.h"

/* USER CODE BEGIN 0 */
uint32_t tim_value=0;
uint8_t toggle_flag=0;
//uint8_t send_flag=0;
/* USER CODE END 0 */

TIM_HandleTypeDef htim4;

/* TIM4 init function */
void MX_TIM4_Init(void)
{

  /* USER CODE BEGIN TIM4_Init 0 */

  /* USER CODE END TIM4_Init 0 */

  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_IC_InitTypeDef sConfigIC = {0};

  /* USER CODE BEGIN TIM4_Init 1 */

  /* USER CODE END TIM4_Init 1 */
  htim4.Instance = TIM4;
  htim4.Init.Prescaler = 7200;
  htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim4.Init.Period = 10000;
  htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  if (HAL_TIM_Base_Init(&htim4) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_IC_Init(&htim4) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING;
  sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
  sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
  sConfigIC.ICFilter = 0;
  if (HAL_TIM_IC_ConfigChannel(&htim4, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM4_Init 2 */

  /* USER CODE END TIM4_Init 2 */

}

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(tim_baseHandle->Instance==TIM4)
  {
  /* USER CODE BEGIN TIM4_MspInit 0 */

  /* USER CODE END TIM4_MspInit 0 */
    /* TIM4 clock enable */
    __HAL_RCC_TIM4_CLK_ENABLE();

    __HAL_RCC_GPIOB_CLK_ENABLE();
    /**TIM4 GPIO Configuration
    PB6     ------> TIM4_CH1
    */
    GPIO_InitStruct.Pin = GPIO_PIN_6;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    /* TIM4 interrupt Init */
    HAL_NVIC_SetPriority(TIM4_IRQn, 1, 1);
    HAL_NVIC_EnableIRQ(TIM4_IRQn);
  /* USER CODE BEGIN TIM4_MspInit 1 */

  /* USER CODE END TIM4_MspInit 1 */
  }
}

void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* tim_baseHandle)
{

  if(tim_baseHandle->Instance==TIM4)
  {
  /* USER CODE BEGIN TIM4_MspDeInit 0 */

  /* USER CODE END TIM4_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_TIM4_CLK_DISABLE();

    /**TIM4 GPIO Configuration
    PB6     ------> TIM4_CH1
    */
    HAL_GPIO_DeInit(GPIOB, GPIO_PIN_6);

    /* TIM4 interrupt Deinit */
    HAL_NVIC_DisableIRQ(TIM4_IRQn);
  /* USER CODE BEGIN TIM4_MspDeInit 1 */

  /* USER CODE END TIM4_MspDeInit 1 */
  }
}

/* USER CODE BEGIN 1 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	if(htim->Instance==TIM4)
	{
		HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_3);  //当按键按下以后,每过1s钟,PA3上的LED灯就翻转一次
		tim_value+=10000;  //按键按下后,当到达溢出以后,tim_value的值就加上ARR设置的值,ARR设置的值为10000
	}
}


void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)  //输入捕获中断的回调函数
{
	if(htim->Instance==TIM4)  //检查是否为TIM4触发的中断
	{
		if(toggle_flag==0) //检查标志位
		{
			HAL_Delay(20); //消抖
			if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_6)==GPIO_PIN_RESET)
			{
				toggle_flag=1;  //进入中断后首先翻转标志位
				HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_SET); //PA2的LED灯亮200ms,显示进入输入捕获函数
				HAL_Delay(200);
				HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_RESET);
				HAL_TIM_Base_Start_IT(htim); //开启溢出中断,开始进行计时
				
				__HAL_TIM_DISABLE(htim);  //关闭TIM4
				
				__HAL_TIM_SET_COUNTER(htim,0); //设置时钟计数值为0
				TIM_RESET_CAPTUREPOLARITY(htim,TIM_CHANNEL_1);  //清除TIM4通道1的原始设置
				TIM_SET_CAPTUREPOLARITY(htim,TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_RISING);//将触发输入捕获中断源设置为上升沿捕获
				
				__HAL_TIM_ENABLE(htim);//使能TIM4
			} 
		}
		else if(toggle_flag==1)//检查标志位
		{
			HAL_Delay(20);//消抖
			if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_6)==GPIO_PIN_SET)//检查是否为上升沿
			{
				toggle_flag=0; 
				HAL_TIM_Base_Stop_IT(htim);//关闭溢出中断
				HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_SET);//若是上升沿触发输入捕获中断,LED灯亮200ms
				HAL_Delay(200);
				HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_RESET);
				
				tim_value=(tim_value+HAL_TIM_ReadCapturedValue(&htim4,TIM_CHANNEL_1 ))/10+20;//计算捕获的总时间
				
				HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_SET); //计算出总时间后,使得PA2口的LED灯亮的时间和总时间相同,以显示输入捕获效果
				HAL_Delay(tim_value);
				HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_RESET);
				
				__HAL_TIM_DISABLE(htim);
				
				TIM_RESET_CAPTUREPOLARITY(htim,TIM_CHANNEL_1);
				TIM_SET_CAPTUREPOLARITY(htim,TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_FALLING);
				
				__HAL_TIM_ENABLE(htim);
			}
		}
	}
}


/* USER CODE END 1 */

以上就是定时器(上)的相关内容,我们了解了定时器的相关结构、定时器的工作原理、定时器中断实验和输入捕获实验,欢迎各位大佬进行批评指正!

  • 8
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 可以使用下面的代码实现: TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; /* 配置定时器TIM2 */ /* 时钟频率 = 72MHz */ /* 计数频率 = 72MHz/7200 = 10KHz(0.1ms) */ /* 计数器自动重装载值 = 7200-1 = 7199 */ /* 计数时间 = 0.1ms * 7199 = 719.9ms = 0.72s */ TIM_TimeBaseStructure.TIM_Period = 7199; TIM_TimeBaseStructure.TIM_Prescaler = 7200 - 1; TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); /* 启动TIM2 */ TIM_Cmd(TIM2, ENABLE); ### 回答2: 下面是一个简单的示例代码,使用STM32定时器实现定时60秒的功能。 ``` #include "stm32xxxx.h" // 根据具体的芯片型号引入对应的头文件 void Delay(uint32_t milliseconds) { uint32_t start = HAL_GetTick(); // 获取当前的系统Tick数 while ((HAL_GetTick() - start) < milliseconds); // 等待指定的毫秒数 } int main(void) { // 初始化定时器 TIM_HandleTypeDef htim; TIM_TypeDef *timer = TIMx; // 根据具体的定时器选择对应的TIMx htim.Instance = timer; htim.Init.Prescaler = 0; // 预分频器,如果需要更长的定时时间,可以设置合适的值 htim.Init.CounterMode = TIM_COUNTERMODE_UP; htim.Init.Period = 60000 - 1; // 计数器重载值,每秒计数60000次 HAL_TIM_Base_Init(&htim); // 启动定时器 HAL_TIM_Base_Start(&htim); // 等待60秒钟 Delay(60000); // 关闭定时器 HAL_TIM_Base_Stop(&htim); while (1) { // 循环等待 } } ``` 这个代码示例使用HAL库来配置和操作STM32定时器。首先,我们初始化定时器,设置预分频器为0和计数器模式为向上计数。然后,设置计数器的重载值为60000,这样每秒计数60000次,即60秒钟计数一次。接着,启动定时器并调用`Delay`函数等待60秒钟。最后,关闭定时器,并进入一个无限循环等待的状态。 请注意,这个代码示例中的`TIMx`应该被替换为实际使用的定时器的指针,例如`TIM2`或`TIM3`,具体根据芯片型号和芯片硬件资源而定。还需要根据实际情况调整预分频器的值来适配不同的系统时钟频率和所需的定时时间。 ### 回答3: STM32定时器定时60秒钟的代码可以使用定时器的自动重装载特性来实现。 首先,需要初始化定时器的时钟源和模式。可以选择使用定时器的内部时钟源和向上计数模式。例如,选择TIM2作为定时器,使用内部时钟源,向上计数模式,带预分频器的自动重装载。 然后,根据系统时钟频率和预分频器值来设置定时器的预分频器和自动重装载寄存器的值。假设系统时钟频率为72MHz,预分频器值为7200,那么计算得到自动重装载寄存器的值为10000。这样,定时器每计数到10000时,就会触发一次定时。 接下来,使能定时器的自动重装载功能,并启动定时器计数。在计数器溢出时,产生定时中断。 最后,可以在定时中断的回调函数中处理需要执行的任务。在这个例子中,可以简单的在定时中断中通过GPIO口输出一个脉冲信号,来表示定时器每经过60秒就会触发一次。 以下是示例代码: ```c #include "stm32f10x.h" void TIM2_IRQHandler(void) { // 处理定时中断事件 // 在这个例子中,向GPIO口输出一个脉冲信号 GPIO_WriteBit(GPIOx, GPIO_Pin_x, Bit_SET); GPIO_WriteBit(GPIOx, GPIO_Pin_x, Bit_RESET); // 清除定时器中断标志位 TIM_ClearITPendingBit(TIMx, TIM_IT_Update); } int main(void) { // 初始化定时器 TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct; TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInitStruct.TIM_Period = 10000; // 自动重装载值 TIM_TimeBaseInitStruct.TIM_Prescaler = 7200; // 预分频器值 TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStruct); // 使能定时器的自动重装载功能 TIM_ARRPreloadConfig(TIM2, ENABLE); // 使能定时器的更新中断 TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); // 启动定时器计数 TIM_Cmd(TIM2, ENABLE); // 初始化GPIO口 GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Pin = GPIO_Pin_x; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOx, &GPIO_InitStruct); // 初始化中断向量表 NVIC_InitTypeDef NVIC_InitStruct; NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); while (1) { // 主循环 } } ``` 以上就是一个简单的使用STM32定时器定时60秒的示例代码。具体的GPIO口和中断优先级等相关参数,请根据具体的系统配置进行设置。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嵌入式进阶之路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值