STM32定时器深度解析:从定时中断到外部时钟应用实战
目录
前言
在嵌入式系统开发中,STM32定时器是最核心的外设之一,它不仅能实现精确的定时功能,还能处理外部时钟信号,为各种复杂应用场景提供支持。本文基于江协科技STM32入门教程系列,将深入探讨定时器的定时中断和外部时钟两大核心功能,帮助开发者全面掌握这一强大外设。
通过本文的学习,你将能够:
- 理解STM32定时器的工作原理和架构
- 掌握定时中断的配置和应用技巧
- 深入理解定时器外部时钟模式的工作机制
- 学会将两种模式结合应用到实际项目中
一、STM32定时器架构与基础原理
1.1 定时器系统架构
STM32定时器是基于ARM Cortex-M内核的复杂外设系统,其架构如下:
系统时钟 → AHB总线 → APB总线 → 定时器时钟 → 预分频器 → 计数器 → 比较/捕获寄存器
关键组成部分:
- 时钟源:可选择内部时钟或外部时钟
- 预分频器(PSC):对输入时钟进行分频
- 自动重装载寄存器(ARR):设定计数周期
- 计数器(CNT):实际计数值
- 控制寄存器:配置工作模式和中断
1.2 定时器分类与特性
STM32F103系列包含多种定时器:
- 基本定时器:TIM6、TIM7(仅定时中断功能)
- 通用定时器:TIM2-TIM5、*TIM9-TIM14(功能最丰富)
- 高级定时器:TIM1、TIM8(支持电机控制等高级功能)

STM32F103C8T6定时器资源:TIM1、TIM2、TIM3、TIM4(无基本定时器)
二、定时器定时中断模式深度解析
2.1 定时中断工作原理
定时中断模式是最基础的应用,其工作原理如下:
- 时钟配置:选择内部时钟源(通常为系统时钟)
- 预分频:通过PSC寄存器将高频时钟分频为低频
- 自动重载:ARR寄存器设定计数最大值
- 计数过程:CNT寄存器从0开始向上计数
- 中断触发:当CNT=ARR时,产生更新中断,CNT重置为0
定时时间计算公式:
定时时间 = (ARR + 1) × (PSC + 1) / 时钟频率
2.1.1 基本定时器

2.1.2 通用定时器

2.1.3 高级定时器

其中对于通用定时器和高级定时器,支持向上计数、向下计数、中央对齐三种模式;而基本定时器仅支持向上计数这一种模式。
2.2 定时中断基本结构

2.3 预分频器时序
计数器计数频率:CK_CNT = CK_PSC / (PSC + 1)

2.4 计数器时序
计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1) = CK_PSC / (PSC + 1) / (ARR + 1)

2.5 计数器无预装时序
2.5 计数器有预装时序

2.6 RCC时钟树

三、定时器定时中断配置代码实现
3.1 定时中断接线图

3.2 封装定时器模块
我们这里使用模块化编程的思路,先将STM32的内部时钟模块封装到System文件夹下
3.2.1 System\Timer.h
#ifndef __TIMER_H
#define __TIMER_H
void Timer_Init(void);
void TIM2_IRQHandler(void);
#endif
3.2.2 System\Timer.c
定时器的实现逻辑:开 TIM2 时钟(RCC_APB1PeriphClockCmd) → 设内部时钟(TIM_InternalClockConfig) → 配时基参数 PSC/ARR(TIM_TimeBaseInit) → 开更新中断(TIM_ITConfig) → 配 NVIC(NVIC_PriorityGroupConfig/NVIC_Init) → 启动 TIM2(TIM_Cmd) → 在中断函数里处理并清中断标志(TIM2_IRQHandler)
#include "stm32f10x.h" // Device header
extern uint16_t Num;
void Timer_Init(void)
{
// 1. 开启 TIM2 时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// 2. 选择内部时钟
TIM_InternalClockConfig(TIM2);
// 3. 配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
// 4. 清除更新标志位
TIM_ClearFlag(TIM2, TIM_FLAG_Update);
// 5. 开启更新中断
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
// 6. 配置 NVIC
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
// 7. 启动 TIM2
TIM_Cmd(TIM2, ENABLE);
}
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
Num ++;
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
需要注意的是,由于预分频器中缓冲寄存器的存在,会导致按下复位按钮后,更新事件和更新中断同时发生(即从1开始计数),所以为了避免刚初始化完成后就立即进中断,需要手动把更新中断标志位清除一下(TIM_ClearFlag)(即从0开始计数)
3.3 主程序实现
3.3.1 User\main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
uint16_t Num;
int main(void)
{
OLED_Init();
Timer_Init();
OLED_ShowString(1, 1, "Num:");
while (1)
{
OLED_ShowNum(1, 5, Num, 5);
OLED_ShowNum(2, 5, TIM_GetCounter(TIM2), 5);// CNT计数器的变化情况
}
}
/*
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
Num ++;
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
*/
这里对于main.c中的全局变量Num,在执行中断自增的操作时有两种处理方式,一种是在Timer.c文件中使用extern函数来声明外部变量Num,使其能够正常被中断函数调用;另一种是直接把中断函数void TIM2_IRQHandler(void)写在主函数中,这样就可以在主函数中直接调用Num了。

四、定时器外部时钟
4.1 外部时钟模式原理
外部时钟模式是本视频的核心内容,它允许定时器使用外部信号作为时钟源,实现对外部事件的精确计数和测量。
工作原理:
- 外部信号通过GPIO引脚输入
- 信号经过滤波和边沿检测
- 每检测到一个有效边沿,计数器加1
- 可配置上升沿、下降沿或双边沿触发
外部时钟模式应用场景:
- 旋转编码器测速
- 脉冲信号计数
- 频率测量
- 位置检测
五、定时器外部时钟配置代码实现
5.1 外部时钟接线图

5.2 封装外部时钟模块
5.2.1 System\Timer.h
#ifndef __TIMER_H
#define __TIMER_H
void Timer_Init(void);
void TIM2_IRQHandler(void);
uint16_t Timer_GetCounter(void);
#endif
这里我们将CNT监视器的函数模块也进行了封装,使代码更加规范
5.2.2 System\Timer.c
外部时钟配置流程与内部时钟基本相同,仅需将原来配置内部时钟的函数改为配置外部时钟的函数,并且把外部时钟外设对应的GPIO引脚配置好即可(这里使用对射式红外传感器作为外部时钟进行配置,由于传感器本身的性质,建议将滤波调整为0x05)
#include "stm32f10x.h" // Device header
extern uint16_t Num;
void Timer_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x05);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 10 - 1;
TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
TIM_ClearFlag(TIM2, TIM_FLAG_Update);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM2, ENABLE);
}
uint16_t Timer_GetCounter(void)
{
return TIM_GetCounter(TIM2);
}
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
Num ++;
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
5.3 主程序实现
5.3.1 User\main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
uint16_t Num;
int main(void)
{
OLED_Init();
Timer_Init();
OLED_ShowString(1, 1, "Num:");
OLED_ShowString(2, 1, "CNT:");
while (1)
{
OLED_ShowNum(1, 5, Num, 5);
OLED_ShowNum(2, 5, Timer_GetCounter(), 5);// CNT计数器的变化情况
}
}
/*
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
Num ++;
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
*/

六、总结展望与源码分享
通过本文的学习,我们深入掌握了STM32定时器的两大核心功能:定时中断模式和外部时钟模式。这两种模式各有特点,可以独立使用,也可以结合应用,为嵌入式系统开发提供强大的时间管理和外部信号处理能力。
6.1 核心收获
- 定时中断模式:适用于精确的时间控制、周期性任务调度、软件定时器实现
- 外部时钟模式:适用于脉冲信号计数、频率测量、编码器接口、外部事件触发
- 结合应用:通过两种模式的结合,可以实现复杂的实时控制系统,如电机控制、数据采集等
6.2 未来发展方向
- 深入学习定时器的PWM输出功能,实现电机精确控制
- 掌握输入捕获功能,实现高精度频率和占空比测量
- 学习定时器的主从模式,实现多定时器协同工作
- 结合DMA和定时器,实现高效的数据采集系统
6.3 实践建议
- 动手实验:按照本文代码,在开发板上实际运行并观察效果
- 项目应用:将定时器功能应用到实际项目中,如智能家居控制系统、工业监测设备等
- 性能优化:通过逻辑分析仪等工具,分析和优化定时器性能
- 深入学习:阅读STM32参考手册中定时器章节,理解底层硬件细节
6.4 源码分享
6-1 定时器定时中断
https://gitee.com/Li-Cheng-Ze/stm32/tree/master/6-1%20%E5%AE%9A%E6%97%B6%E5%99%A8%E5%AE%9A%E6%97%B6%E4%B8%AD%E6%96%AD6-2 定时器外部时钟
https://gitee.com/Li-Cheng-Ze/stm32/tree/master/6-2%20%E5%AE%9A%E6%97%B6%E5%99%A8%E5%A4%96%E9%83%A8%E6%97%B6%E9%92%9F定时器作为STM32最强大的外设之一,其应用场景远不止本文所述。希望本文能为你打开STM32定时器应用的大门,助你在嵌入式开发道路上更进一步!
作者:Auto_Dev_06
开发环境:Keil MDK-ARM + STM32F103C8T6
参考资料:江协科技STM32入门教程系列、STM32F10x参考手册
版权声明:本文为CSDN原创文章,转载请注明出处
发布时间:2026年4月12日
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)