Timer实验1:定时器基本用法(CuBeMX版)+启动函数解析

1、实验目的

自制一个毫秒级的延时函数:定义一个全局变量,配置基本定时器为1毫秒的中断,在中断中对此全局变量累加,即可实现毫秒级计时。同时为了验证延时效果,配置串口输出。

2、配置CuBeMX

1)选择MCU

image-20250630210732420

image-20250630210853586

2)配置时钟和Debug

image-20250630211149140

image-20250630211223545

image-20250630211813672

3)配置定时器

本实验使用基本定时器TIM6,挂载在APB1总线上,配置为84MHz

image-20250630212843381

由上配置可以得到定时器周期1ms

有关定时器的详细介绍,请见:【STM32学习笔记】Timer总结:从模式控制器、时基单元、输入捕获、输出比较

4)开启中断

image-20250630213105691

5)配置串口

image-20250630215102615

有关串口更详细的介绍,请见:【STM32学习笔记】串口总结:串口轮询模式、串口中断模式、串口DMA模式以及串口接收不定长数据

6)配置路径生成代码

image-20250630215619146

3、定时器相关语句

定时器启动函数相关

HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim)
	/*
    1、参数htim:外设定时器句柄的指针
    2、返回值:(成功还是失败)
            typedef enum 
            {
              HAL_OK       = 0x00U,  	//成功
              HAL_ERROR    = 0x01U,		//失败
              HAL_BUSY     = 0x02U,		//串口忙
              HAL_TIMEOUT  = 0x03U		//接收超时
            } HAL_StatusTypeDef;
            只会返回前两个值
    */

在HAL_TIM_Base_Start_IT函数中,会使能定时器更新中断和使能定时器

image-20250701215652134

启动定时器函数__HAL_TIM_ENABLE的具体形式为以下宏定义,它的作用是将定时器的控制寄存器 1 (CR1) 中的计数器使能位 (CEN, Counter ENable) 置 1。

/**
  * @brief  Enable the TIM peripheral.
  * @param  __HANDLE__ TIM handle
  * @retval None
  */
#define __HAL_TIM_ENABLE(__HANDLE__)                 ((__HANDLE__)->Instance->CR1|=(TIM_CR1_CEN))
  1. 寄存器级操作: 这是最底层的、直接操作硬件寄存器的指令。效率极高。
  2. 仅设置 CEN 位: 它只负责设置 CEN 位来启动计数。不会自动配置定时器的其他参数(如时钟源、预分频器 PSC、重装载值 ARR、比较 值 CCRx、中断/DMA 使能、输出模式等)。这些配置通常在调用 HAL_TIM_Base_Init(), HAL_TIM_Base_Start_IT(), HAL_TIM_PWM_Start() 等函数时已经完成。__HAL_TIM_ENABLE 是这些更高级启动函数内部最终执行的核心操作。
  3. 参数 HANDLE: 必须是一个有效且已初始化(通常通过 HAL_TIM_Base_Init 或类似函数初始化过)的 TIM_HandleTypeDef 结构体指针。这个结构体包含了指向目标定时器寄存器块 (Instance) 的指针以及其他配置信息。
  4. TIM_CR1_CEN 常量: 这个值由 ST 的 HAL 库提供,确保了代码在不同 STM32 系列之间的可移植性(即使不同系列定时器寄存器地址不同,宏的使用方式不变)。

下面介绍TIM_CR1_CEN 常量,在代码中找到其定义如下:

/******************************************************************************/
/*                                                                            */
/*                                    TIM                                     */
/*                                                                            */
/******************************************************************************/
/*******************  Bit definition for TIM_CR1 register  ********************/
#define TIM_CR1_CEN_Pos           (0U)                                         
#define TIM_CR1_CEN_Msk           (0x1UL << TIM_CR1_CEN_Pos)                    /*!< 0x00000001 */
#define TIM_CR1_CEN               TIM_CR1_CEN_Msk                              /*!<Counter enable        */

1.#define TIM_CR1_CEN_Pos (0U)

  • 作用:定义 CEN 位在 CR1 寄存器中的位位置 (Position)

  • 解释

    0U 表示该位位于寄存器的 第 0 位 (最低有效位,LSB)。

    U 后缀表示这是一个无符号整型 (unsigned integer) 常量。

  • 意义:这告诉你 CEN 标志在 CR1 寄存器中的物理位置是 bit 0。

2.#define TIM_CR1_CEN_Msk (0x1UL << TIM_CR1_CEN_Pos)

  • 作用:定义 CEN 位的位掩码 (Bit Mask)

  • 分解

    0x1UL:十六进制值 1,UL 后缀表示 无符号长整型 (unsigned long)。这确保数值在32位系统上有正确的宽度。

    << TIM_CR1_CEN_Pos:将 0x1UL 左移 TIM_CR1_CEN_Pos 位 (即左移 0 位)。

    结果:0x1UL << 0 = 0x00000001 (32位掩码)。

  • 意义:创建一个掩码,其中只有 CEN 位 (bit 0) 为 1,其他所有位 (bit 1 到 31) 为 0。这是用于操作该位的核心掩码。

3.#define TIM_CR1_CEN TIM_CR1_CEN_Msk

  • 作用:为 CEN 位定义一个易记的标识符

  • 解释

    TIM_CR1_CEN 直接定义为 TIM_CR1_CEN_Msk 的值 (0x00000001)。

  • 意义:提供简洁明了的名称 (TIM_CR1_CEN) 来代表 CEN 位的掩码,提高代码可读性。

使能定时器中断函数__HAL_TIM_ENABLE_IT(HANDLE, INTERRUPT)定义如下:

/** @brief  Enable the specified TIM interrupt.
  * @param  __HANDLE__ specifies the TIM Handle.
  * @param  __INTERRUPT__ specifies the TIM interrupt source to enable.
  *          This parameter can be one of the following values:
  *            @arg TIM_IT_UPDATE: Update interrupt
  *            @arg TIM_IT_CC1:   Capture/Compare 1 interrupt
  *            @arg TIM_IT_CC2:  Capture/Compare 2 interrupt
  *            @arg TIM_IT_CC3:  Capture/Compare 3 interrupt
  *            @arg TIM_IT_CC4:  Capture/Compare 4 interrupt
  *            @arg TIM_IT_COM:   Commutation interrupt
  *            @arg TIM_IT_TRIGGER: Trigger interrupt
  *            @arg TIM_IT_BREAK: Break interrupt
  * @retval None
  */
#define __HAL_TIM_ENABLE_IT(__HANDLE__, __INTERRUPT__)    ((__HANDLE__)->Instance->DIER |= (__INTERRUPT__))

具体中断形式如以上代码中简介,具体到寄存器就不再展开,大家可以顺藤摸瓜,参照源码和手册进行学习。

操作相关寄存器函数

__HAL_TIM_SET_COUNTER
__HAL_TIM_GET_COUNTER
__HAL_TIM_SET_AUTORELOAD
__HAL_TIM_GET_AUTORELOAD
__HAL_TIM_SET_PRESCALER
/**
  * @brief  Set the TIM Counter Register value on runtime.
  * @param  __HANDLE__ TIM handle.
  * @param  __COUNTER__ specifies the Counter register new value.
  * @retval None
  */
#define __HAL_TIM_SET_COUNTER(__HANDLE__, __COUNTER__)  ((__HANDLE__)->Instance->CNT = (__COUNTER__))

用于在运行时设置定时器(TIM)的计数器寄存器(Counter Register)的值

HANDLE:指向定时器句柄的指针(如 &htim1)。该句柄包含定时器的配置信息和寄存器基地址(通过 Instance 成员访问)。
COUNTER:要设置的计数器新值(16位或32位整数,取决于定时器位数)。

/**
  * @brief  Get the TIM Counter Register value on runtime.
  * @param  __HANDLE__ TIM handle.
  * @retval 16-bit or 32-bit value of the timer counter register (TIMx_CNT)
  */
#define __HAL_TIM_GET_COUNTER(__HANDLE__)  ((__HANDLE__)->Instance->CNT)

和上面获取类似,不展开

/**
  * @brief  Set the TIM Autoreload Register value on runtime without calling another time any Init function.
  * @param  __HANDLE__ TIM handle.
  * @param  __AUTORELOAD__ specifies the Counter register new value.
  * @retval None
  */
#define __HAL_TIM_SET_AUTORELOAD(__HANDLE__, __AUTORELOAD__) \
  do{                                                    \
    (__HANDLE__)->Instance->ARR = (__AUTORELOAD__);  \
    (__HANDLE__)->Init.Period = (__AUTORELOAD__);    \
  } while(0)

这个宏 __HAL_TIM_SET_AUTORELOAD 是 STM32 HAL 库中用于动态设置定时器自动重载值的核心宏。

  1. 硬件操作
    (__HANDLE__)->Instance->ARR = (__AUTORELOAD__)
    直接修改定时器的 ARR 寄存器(AutoReload Register),立即改变定时器的计数周期

  2. 软件同步
    (__HANDLE__)->Init.Period = (__AUTORELOAD__)
    更新定时器句柄中的软件配置,保持软件状态与实际硬件一致

  3. 为什么使用软硬件同步:

    - 保持软件配置与实际硬件一致:在后续的操作中,HAL库可能会使用句柄中的Init.Period值来进行某些计算或重新初始化。如果不更新这个值,软件中保存的配置就会与实际硬件寄存器中的值不一致,导致错误。

    - 避免重新初始化:这个宏的名称已经说明,它可以在不重新调用初始化函数(如HAL_TIM_Init)的情况下改变ARR值。通常,重新初始化定时器会涉及更多的寄存器配置,并且可能会打断定时器的当前操作。而通过直接修改ARR并同步更新软件配置,就可以避免重新初始化。

    - 方便用户查询当前配置:如果用户需要知道当前的ARR值,可以直接从句柄的Init.Period字段读取,而不必去读取硬件寄 存器(虽然读取硬件寄存器也是可行的,但软件读取更简单且不会增加总线访问)。

影子寄存器机制

  • TIMx_CR1.ARPE=1(预装载使能),新ARR值在下个更新事件生效
  • ARPE=0,新ARR值立即生效
/**
  * @brief  Get the TIM Autoreload Register value on runtime.
  * @param  __HANDLE__ TIM handle.
  * @retval 16-bit or 32-bit value of the timer auto-reload register(TIMx_ARR)
  */
#define __HAL_TIM_GET_AUTORELOAD(__HANDLE__)  ((__HANDLE__)->Instance->ARR)

和上面设置类似,不展开

/**
  * @brief  Set the TIM Prescaler on runtime.
  * @param  __HANDLE__ TIM handle.
  * @param  __PRESC__ specifies the Prescaler new value.
  * @retval None
  */
#define __HAL_TIM_SET_PRESCALER(__HANDLE__, __PRESC__)       ((__HANDLE__)->Instance->PSC = (__PRESC__))

这个宏 __HAL_TIM_SET_PRESCALER 是 STM32 HAL 库中用于动态设置定时器预分频值的核心宏。

对比其他宏

特性 __HAL_TIM_SET_PRESCALER __HAL_TIM_SET_AUTORELOAD
目标寄存器 PSC ARR
影响 改变计数频率 改变计数周期
软件同步 ❌ 不更新句柄 ✅ 更新Init.Period
使用频率 低频修改(时钟变化时) 高频修改(动态调频)

4、定时器中断相关代码

定时器中断函数:

/**
  * @brief This function handles TIM7 global interrupt.
  */
void TIM7_IRQHandler(void)
{
  /* USER CODE BEGIN TIM7_IRQn 0 */

  /* USER CODE END TIM7_IRQn 0 */
  HAL_TIM_IRQHandler(&htim7);
  /* USER CODE BEGIN TIM7_IRQn 1 */

  /* USER CODE END TIM7_IRQn 1 */
}

进入HAL_TIM_IRQHandler(&htim7)函数,我们会找到有关更新中断的回调函数:

  /* TIM Update event */
  if (__HAL_TIM_GET_FLAG(htim, TIM_FLAG_UPDATE) != RESET)
  {
    if (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_UPDATE) != RESET)
    {
      __HAL_TIM_CLEAR_IT(htim, TIM_IT_UPDATE);
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
      htim->PeriodElapsedCallback(htim);
#else
      HAL_TIM_PeriodElapsedCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
    }
  }

5、整体代码

参数定义

/* USER CODE BEGIN PV */
static volatile uint32_t currentMS = 0;
char *str = "test successfully\r\n";


/* USER CODE END PV */

延时函数、获取当前时间毫秒数函数的声明与定义

/* USER CODE BEGIN PFP */
static void MyDelay(uint32_t Delay);
static uint32_t GetCurrentTime(void);

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
static void MyDelay(uint32_t Delay){
	uint32_t time = GetCurrentTime() + Delay;
	while(GetCurrentTime() < time);
	
}

static uint32_t GetCurrentTime(void){
	return currentMS;
}

/* 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();

  /* 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_DMA_Init();
  MX_USART1_UART_Init();
  MX_TIM7_Init();
  /* USER CODE BEGIN 2 */
	HAL_TIM_Base_Start_IT(&htim7);  //ÖжϷ½Ê½¿ªÆô¶¨Ê±Æ÷

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		HAL_UART_Transmit(&huart1, (uint8_t *)str, strlen(str), HAL_MAX_DELAY);
		MyDelay(1000);
//		HAL_Delay(990);
    /* USER CODE END WHILE */

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

定时器中断回调函数:

/* USER CODE BEGIN 4 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
	if(htim == &htim7){
		currentMS++;
	}
}

/* USER CODE END 4 */

6、实验结果

主函数中的MyDelay(1000)中的参数是1000,即1000ms,从串口助手可以看出刚好1s发一次。

image-20250630231413841

7、结语

学习记录,欢迎批评指正

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐