当前位置: 首页 > article >正文

【星云 Orbit•STM32F4】13. 探索定时器:基本定时器

【星云 Orbit•STM32F4】13. 探索定时器:基本定时器

七律 · 定时器
芯片之心精巧藏,
定时精准度量长。
初学莫畏千般难,
动手方知妙用强。

为读者提供完整代码,但不提供代码文件,也别做“三键”工程师。唯有自己动手,技术这活,动手才行。
摘要:本文详细讲解STM32F407基本定时器原理与HAL库开发流程,涵盖硬件设计、寄存器配置、HAL库函数解析及模块化代码实现。
关键词:STM32F407, TIM6, HAL库, 定时器中断, 裸机开发


1. 引言

基本定时器(TIM6/TIM7)是STM32中结构最简单的定时单元,适用于时基生成和DAC触发场景。本教程以TIM6为例,实现500ms周期定时中断控制LED翻转。


2. 基础知识

2.1 定时器分类

  • 基本定时器:16位递增计数器,无输入捕获/输出比较
  • 通用定时器:TIM2-TIM5,支持输入捕获/PWM输出
  • 高级定时器:TIM1/TIM8,带死区控制等复杂功能

2.2 TIM6关键寄存器

寄存器功能描述
CR1控制寄存器(使能、计数模式)
SR状态寄存器(中断标志位)
CNT当前计数值
PSC预分频器
ARR自动重装载值

3. 硬件电路连接

  • LED:PA5(推挽输出,串联220Ω限流电阻)
  • TIM6:内部时钟源(无外部引脚)

4. 软件配置

4.1 时钟树配置

// system_stm32f4xx.c中配置主频为168MHz  
#define PLL_M      8  
#define PLL_N      336  
#define PLL_P      2       // APB1时钟=84MHz

4.2 HAL库关键函数

HAL_TIM_Base_Init(TIM_HandleTypeDef *htim); // 定时器初始化  
HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim); // 启动定时器中断  
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim); // 中断回调函数

关键点解析:
  1. 定时器参数计算

    Tout = (ARR + 1) * (PSC + 1) / TIM_CLK
    
    • 当APB1时钟为84MHz时,设置PSC=999得到分频后时钟84MHz/1000=84kHz
    • ARR设置为41999,则定时周期为(41999+1)/84kHz = 0.5s
  2. HAL库工作流程

    HAL_TIM_Base_Init
    初始化定时器
    调用HAL_TIM_Base_MspInit
    配置NVIC和时钟
    HAL_TIM_Base_Start_IT
    开启定时器中断
  3. 中断响应流程

    定时器溢出
    触发TIM6中断
    进入TIMx_IRQHandler
    调用HAL_TIM_IRQHandler
    自动执行HAL_TIM_PeriodElapsedCallback

5. 代码实现

5.1 工程目录结构

STM32F40x_Project/
├── Drivers/
│   ├── BSP/
|       ├── LED/
│   │       ├── bsp_led.c    # LED硬件抽象层
│   │       └── bsp_led.h    
│   └── Module/TIMER/
│       ├── TIMER/
            ├── drv_tim.c    # 定时器驱动模块
│           └── drv_tim.h    
└── Users 
    ├── main.c           # 主程序
    └── stm32f4xx_it.c   # 中断服务程序

5.2 定时器配置流程

启动系统时钟
配置GPIO
初始化TIM6
设置预分频PSC
设置ARR值
使能更新中断
启动定时器

5.3 计算定时周期公式

Tout = (ARR+1)*(PSC+1)/TIMx_CLK  
例:APB1=84MHz, ARR=41999, PSC=999  
Tout = (41999+1)*(999+1)/84e6 = 0.5s

5.4 核心代码

drv_btim.c

#include "drv_btim.h"

TIM_HandleTypeDef htim6;

void TIM6_Init(uint16_t arr, uint16_t psc) {
  htim6.Instance = TIM6;
  htim6.Init.Prescaler = psc;
  htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim6.Init.Period = arr;
  htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  
  HAL_TIM_Base_Init(&htim6);
  HAL_TIM_Base_Start_IT(&htim6);
}

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle) {
  if(tim_baseHandle->Instance == TIM6) {
    __HAL_RCC_TIM6_CLK_ENABLE();
    HAL_NVIC_SetPriority(TIM6_DAC_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn);
  }
}

stm32f4xx_it.c

void TIM6_DAC_IRQHandler(void) {
  HAL_TIM_IRQHandler(&htim6);
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
  if(htim->Instance == TIM6) {
    LED_Toggle(); // LED状态翻转
  }
}

6. 测试验证

  1. 编译工程生成HEX文件
  2. 使用ST-Link下载到开发板
  3. 观察PA5引脚LED以1Hz频率闪烁
  4. 示波器测量PA5波形:高/低电平各500ms

附录: 完整工程代码

1. BSP层代码

2.1 bsp_led.h
#ifndef __BSP_LED_H
#define __BSP_LED_H

#include "stm32f4xx_hal.h"

/* 硬件定义 */
#define LED_GPIO_PORT    GPIOA
#define LED_GPIO_PIN     GPIO_PIN_5
#define LED_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()

/* 函数声明 */
void LED_Init(void);               // LED初始化
void LED_Toggle(void);             // LED状态翻转

#endif /* __BSP_LED_H */
2.2 bsp_led.c
#include "bsp_led.h"

/**
  * @brief  LED GPIO初始化
  * @note   配置PA5为推挽输出模式
  */
void LED_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    LED_GPIO_CLK_ENABLE();  // 使能GPIO时钟
    
    GPIO_InitStruct.Pin = LED_GPIO_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;      // 推挽输出
    GPIO_InitStruct.Pull = GPIO_NOPULL;              // 无上下拉
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;     // 低速模式
    HAL_GPIO_Init(LED_GPIO_PORT, &GPIO_InitStruct);
    
    HAL_GPIO_WritePin(LED_GPIO_PORT, LED_GPIO_PIN, GPIO_PIN_RESET); // 初始状态熄灭
}

/**
  * @brief  翻转LED状态
  */
void LED_Toggle(void)
{
    HAL_GPIO_TogglePin(LED_GPIO_PORT, LED_GPIO_PIN);
}

2. 定时器模块

2.1 drv_btim.h
#ifndef __DRV_BTIM_H
#define __DRV_BTIM_H

#include "stm32f4xx_hal.h"

/* 定时器定义 */
#define TIMx                           TIM6
#define TIMx_CLK_ENABLE()              __HAL_RCC_TIM6_CLK_ENABLE()
#define TIMx_IRQn                      TIM6_DAC_IRQn
#define TIMx_IRQHandler                TIM6_DAC_IRQHandler

/* 函数声明 */
void TIM6_Init(uint16_t arr, uint16_t psc);  // 定时器初始化

#endif /* __DRV_BTIM_H */
2.2 drv_tim.c
#include "drv_btim.h"

TIM_HandleTypeDef htim6;  // 定时器句柄

/**
  * @brief  TIM6初始化
  * @param  arr : 自动重装载值
  * @param  psc : 预分频系数
  * @note   定时周期公式: Tout = (arr+1)*(psc+1)/TIMx_CLK
  */
void TIM6_Init(uint16_t arr, uint16_t psc)
{
    htim6.Instance = TIMx;
    htim6.Init.Prescaler = psc;                  // 预分频值
    htim6.Init.CounterMode = TIM_COUNTERMODE_UP; // 向上计数
    htim6.Init.Period = arr;                     // 自动重装载值
    htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; // 不缓冲ARR
    
    // 初始化定时器
    if (HAL_TIM_Base_Init(&htim6) != HAL_OK)
    {
        Error_Handler();
    }
    
    // 启动定时器中断
    HAL_TIM_Base_Start_IT(&htim6);
}

/**
  * @brief  HAL库MSP初始化回调函数
  * @param  htim : 定时器句柄指针
  * @note   此函数由HAL_TIM_Base_Init()自动调用
  */
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim)
{
    if(htim->Instance == TIMx)
    {
        TIMx_CLK_ENABLE();                       // 使能TIM6时钟
        
        // 配置NVIC
        HAL_NVIC_SetPriority(TIMx_IRQn, 1, 0);   // 中断优先级设置
        HAL_NVIC_EnableIRQ(TIMx_IRQn);           // 使能中断
    }
}

3. 用户层代码

3.1 main.c
#include "main.h"
#include "./LED/bsp_led.h"
#include "./TIMER/bsp_tim.h"

/**
  * @brief  系统时钟配置
  * @note   使用内部HSI时钟,配置系统时钟为168MHz
  */
void SystemClock_Config(void)
{
    // ... 此处需根据实际板载时钟源配置 ...
}

int main(void)
{
    HAL_Init();                     // 初始化HAL库
    SystemClock_Config();           // 配置系统时钟
    LED_Init();                     // 初始化LED
    
    /* 计算定时器参数:
       - APB1时钟频率 = 84MHz
       - 目标定时周期 = 500ms
       - 预分频值PSC = 999 (实际分频系数=999+1=1000)
       - 自动重装载值ARR = 41999 (实际计数次数=41999+1=42000)
       Tout = (42000 * 1000) / 84,000,000 = 0.5s
    */
    TIM6_Init(41999, 999);          // 初始化TIM6
    
    while (1)
    {
        /* 主循环不执行任何操作,由定时器中断处理任务 */
    }
}

// 错误处理函数(示例)
void Error_Handler(void)
{
    while(1);
}
3.2 stm32f4xx_it.c
#include "stm32f4xx_it.h"
#include "./TIMER/drv_btim.h"
#include "./LED/bsp_led.h"

/**
  * @brief  TIM6中断服务函数
  */
void TIMx_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&htim6);  // 调用HAL库中断处理函数
}

/**
  * @brief  定时器周期到达回调函数
  * @param  htim : 定时器句柄指针
  */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIMx)
    {
        LED_Toggle();  // 每次中断翻转LED状态
    }
}

  1. graph TD;
        A[定时器溢出] --> B[触发TIM6中断];
        B --> C[进入TIMx_IRQHandler];
        C --> D[调用HAL_TIM_IRQHandler];
        D --> E[自动执行HAL_TIM_PeriodElapsedCallback];
    
    

4. 状态转换图

CNT < ARR
CNT == ARR
IDLE
CNT < ARR
产生中断
复位CNT

5. 流程图思维导图:

触发
硬件初始化
定时器配置
中断使能
主循环空转
定时中断触发
执行回调函数
LED翻转

http://www.kler.cn/a/578432.html

相关文章:

  • 生命周期总结(uni-app、vue2、vue3生命周期讲解)
  • 蓝桥杯 - 简单 - 工作协调
  • 如何在 Conda 环境中使用 PySide6 将 .ui 文件转换为 .py 文件
  • 【技海登峰】Kafka漫谈系列(六)Java客户端之消费者Consumer核心概念与客户端配置详解
  • 【文心索引】搜索引擎测试报告
  • Synchronized 锁升级过程
  • Python asyncIO 面试题及参考答案 草
  • linux---天气爬虫
  • 从0开始完成基于异步服务器的boost搜索引擎
  • Qt的QGraphics View的使用
  • 万字深度剖析——JS数据结构(上)
  • 【Linux学习笔记】Linux基本指令分析和权限的概念
  • Ubuntu通过局域网共享文件夹实现文件夹的连接
  • 13.C语言指针的易错点
  • Jmeter的脚本录制
  • DeepSpeek服务器繁忙?这几种替代方案帮你流畅使用!(附本地部署教程)
  • 【语料数据爬虫】Python实现将Json语料数据转换成Word文档
  • 从零开始用react + tailwindcss + express + mongodb实现一个聊天程序(十二) socketio 消息处理
  • Ansible安装
  • 2025-3-9 树和森林的遍历