目录

一、STM32的定时器资源

1、STM3 的通用 TIMx (TIM2、TIM3、TIM4 和 TIM5)定时器功能

2、定时器计数模式

3、计数器时钟选择

二、使用STM32CubeMX创建工程

1、设置RCC

2、时钟树配置

3、配置TIM3

4、设置工程文件等等

三、程序设计

四、HAL库中定时器相关的函数与其用法


一、STM32的定时器资源

STM32 的定时器功能十分强大,有 TIME1 和 TIME8 等高级定时器,也有 TIME2~TIME5 等通用定时器,还有 TIME6 和
TIME7 等基本定时器。

本文将介绍STM32的通用定时器TIME2~TIME5。

1、STM3 的通用 TIMx (TIM2、TIM3、TIM4 和 TIM5)定时器功能

1)16 位向上、向下、向上/向下自动装载计数器(TIMx_CNT)。

2)16 位可编程(可以实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数为 1~65535 之间的任意数值。

3)4 个独立通道(TIMx_CH1~4),这些通道可以用来作为:

A.输入捕获

B.输出比较

C.PWM 生成(边缘或中间对齐模式)

D.单脉冲模式输出

4)可使用外部信号(TIMx_ETR)控制定时器和定时器互连(可以用 1 个定时器控制另外一个定时器)的同步电路。

5)如下事件发生时产生中断/DMA:

A.更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)

B.触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)

C.输入捕获

D.输出比较

E.支持针对定位的增量(正交)编码器和霍尔传感器电路

F.触发输入作为外部时钟或者按周期的电流管理

2、定时器计数模式

STM32的通用定时器有三种计数模式。

向上计数:计数器从0计数到自动加载值(TIMx_ARR计数器的内容),然后重新从0开始计数并且产生一个计数器溢出事件。

向下计数:计数器从自动装入的值(TIMx_ARR计数器的值)开始向下计数到0,然后从自动装入的值重新开始并且产生一个计数器向下溢出事件。

中央对齐模式( 向上/向下计数):计数器从0开始计数到自动加载的值(TIMx_ARR寄存器)−1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器下溢事件;然后再从0开始重新计数。

用一个表格来说明三种计数模式溢出值与重装值的关系

计数模式计数器溢出值计数器重装值
向上计数CNT = ARRCNT = 0
向下计数CNT = 0CNT = ARR
中心对齐计数CNT = ARR - 1CNT = ARR
CNT = 1CNT = 0

在《STM32参考手册》中有图形来形象的描述这三种计数方式。

3、计数器时钟选择

计数器时钟可由下列时钟源提供:

内部时钟(CK_INT)

外部时钟模式1:外部输入脚(TIx)

外部时钟模式2:外部触发输入(ETR)

内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器,如可以配置一个定时器Timer1而作为另一个定时器Timer2的预分频器。

二、使用STM32CubeMX创建工程

接下来以STM32L475VET6开发板为例,使用STM32CubeMX软件创建一个定时器3的工程,控制LED闪烁。

1、设置RCC

因为这里我们只使用到 HSE,所以我们设置选项 High Speed Clock(HSE)的值为Crystal/Ceramic Resonator(使用晶振/陶瓷振荡器)即可。

2、时钟树配置

根据参考手册上面的时钟树,我们可以知道通用定时器是挂载在APB1总线上面的。

在本次实验中,我想让系统全速运行,而且我的外部晶振为8MHz,设置HCLK为最大频率为80MHz,APB1总线频率也为80MHz。

3、配置TIM3

在TIM3的配置界面,由于在本次实验中仅仅使用到定时器功能,所以只需要设置计数器时钟源为内部时钟即可。

接下来进入详细配置。

定时器的溢出时间为:

T_{out}=\frac{(PSC+1)\ast (ARR+1)}{T_{clk}}

在这里,我需要定时器定时1ms,所以可以设置PSC=79,ARR=999,设置为向上计数模式

这里我们只需要设置以上选项即可。

当然不要忘记开启中断,否则定时器是不会进入中断的。

4、设置工程文件等等

至此,CubeMX的配置完成,点击生成代码。

三、程序设计

在CubeMX生成的代码中,外设初始化已经给写好了

TIM_HandleTypeDef htim3;

/* TIM3 init function */
void MX_TIM3_Init(void)
{
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  htim3.Instance = TIM3;
  htim3.Init.Prescaler = 79;
  htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim3.Init.Period = 999;
  htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }

}

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{

  if(tim_baseHandle->Instance==TIM3)
  {
  /* USER CODE BEGIN TIM3_MspInit 0 */

  /* USER CODE END TIM3_MspInit 0 */
    /* TIM3 clock enable */
    __HAL_RCC_TIM3_CLK_ENABLE();

    /* TIM3 interrupt Init */
    HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(TIM3_IRQn);
  /* USER CODE BEGIN TIM3_MspInit 1 */

  /* USER CODE END TIM3_MspInit 1 */
  }
}

以及初始化函数的调用

和中断函数都已经写好了

关于STM32的HAL库的中断系统,稍后会详细讲解。

我们只需要编写定时器的中断回调函数,该函数在stm32l4xx_hal_tim.c文件中也有定义。

/**
  * @brief  Output Compare callback in non-blocking mode
  * @param  htim TIM OC handle
  * @retval None
  */
__weak void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(htim);

  /* NOTE : This function should not be modified, when the callback is needed,
            the HAL_TIM_OC_DelayElapsedCallback could be implemented in the user file
   */
}

注意,在stm32l4xx_hal_tim.c文件中定义的这个回调函数,前面有一个__weak修饰符,关于这个关键字的用法如下

weak 顾名思义是“弱”的意思,所以如果函数名称前面加上__weak 修饰符,我们一般称这个函数为“弱函数”。加上了__weak 修饰符的函数,用户可以在用户文件中重新定义一个同名函数,最终编译器编译的时候,会选择用户定义的函数,如果用户没有重新定义这个函数,那么编译器就会执行__weak 声明的函数,并且编译器不会报错。

__weak 在回调函数的时候经常用到。这样的好处是,系统默认定义了一个空的回调函数,保证编译器不会报错。同时,如果用户自己要定义用户回调函数,那么只需要重新定义即可,不需要考虑函数重复定义的问题,使用非常方便,在 HAL 库中__weak 关键字被广泛使用。

所以我们重新定义一个中断回调函数,然后在回调函数中判断是哪个定时器发生了溢出中断,执行相应的操作即可。

注意:自己添加的代码请在用户代码段之间,防止使用CubeMX软件更新配置的时候将用户自定义代码擦除覆盖。

/* USER CODE BEGIN 1 */
// 请在这里添加代码,防止被擦除覆盖
/* USER CODE END 1 */

自定义定时器中断回调函数,这里我放在了tim.c文件中的用户代码段里面

// 定时器更新中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	static uint16_t TIM3_Cnt1 = 0;
	
	// 判断是定时器3发生的中断
	if(htim->Instance == TIM3)
	{
		// 定时器3每中断一次,计数器自加1
		TIM3_Cnt1 ++;
		// 计数1000次,定时1s
		if(TIM3_Cnt1 >= 1000)
		{
			// 清除计数器
			TIM3_Cnt1 = 0;
                        // 翻转LED_R的电平状态
			HAL_GPIO_TogglePin(LED_R_GPIO_Port, LED_R_Pin);
		}
	}
}

然后在main.c中,在定时器初始化命令之后加入以下代码

// 清除定时器初始化过程中的更新中断标志,避免定时器一启动就中断
__HAL_TIM_CLEAR_IT(&htim3, TIM_IT_UPDATE);		
// 使能定时器3更新中断并启动定时器
HAL_TIM_Base_Start_IT(&htim3);

最终结果如下所示

然后编译,并烧录到单片机中,观察发现红灯每个1s亮灭一次,实验成功。

四、HAL库中定时器相关的函数与其用法

 

未完待续......

 

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐