【星云 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); // 中断回调函数
关键点解析:
-
定时器参数计算
Tout = (ARR + 1) * (PSC + 1) / TIM_CLK
- 当APB1时钟为84MHz时,设置PSC=999得到分频后时钟84MHz/1000=84kHz
- ARR设置为41999,则定时周期为(41999+1)/84kHz = 0.5s
-
HAL库工作流程
-
中断响应流程
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 定时器配置流程
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. 测试验证
- 编译工程生成HEX文件
- 使用ST-Link下载到开发板
- 观察PA5引脚LED以1Hz频率闪烁
- 示波器测量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状态
}
}
-
graph TD; A[定时器溢出] --> B[触发TIM6中断]; B --> C[进入TIMx_IRQHandler]; C --> D[调用HAL_TIM_IRQHandler]; D --> E[自动执行HAL_TIM_PeriodElapsedCallback];