STM32标准库-TIM定时器

发布于:2025-06-05 ⋅ 阅读:(26) ⋅ 点赞:(0)

一、TIM定时器

1.1定时器

  • TIM(Timer)定时器
  • 定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断
  • 16位计数器、预分频器、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时
  • 不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
  • 根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型

1.2定时器类型

在这里插入图片描述
== STM32F103C8T6定时器资源:TIM1、TIM2、TIM3、TIM4==

1.1.1 高级定时器

在这里插入图片描述

1.1.2通用定时器

在这里插入图片描述

1.1.3基本定时器

在这里插入图片描述

二、定时中断基本结构

在这里插入图片描述

预分频器时器

在这里插入图片描述

预分频器作用:通过分频降低定时器时钟频率,公式为 CK_CNT = CK_PSC / (PSC + 1)(PSC 是预分频器寄存器值)

  • 时序关键节点:

CK_PSC:输入时钟(高频);
CNT_EN:计数器使能信号;
CK_CNT:分频后的计数器时钟(低频,驱动CNT计数);
更新事件(UEV):预分频器参数变更后,触发定时器更新,使新分频值生效。
流程:修改TIMx_PSC寄存器值→预分频缓冲器暂存→更新事件触发时,新值加载到实际计数器,改变CK_CNT频率。

计时器时序

在这里插入图片描述

计数器无预装时序

在这里插入图片描述

计数器有预装时序

在这里插入图片描述

RCC时钟树

在这里插入图片描述

三、定时器定时中断

3.1 接线图

在这里插入图片描述

3.2代码

main.c

#include "stm32f10x.h"                  // 包含STM32F10x系列设备头文件,访问寄存器和外设
#include "Delay.h"                       // 延时功能(未使用,可忽略)
#include "OLED.h"                        // OLED显示驱动,实现屏幕初始化和显示
#include "CountSensor.h"                 // 计数传感器(未使用,可忽略)
#include "Timer.h"                       // TIM2定时器初始化(内部时钟,1秒中断一次)

uint16_t Num = 0;                        // 记录定时器中断次数(每秒+1)

int main(void)
{		
    OLED_Init();                          // 初始化OLED(配置通信,如I2C/SPI)
    Timer_Init();                         // 初始化TIM2(内部时钟,周期1秒)
    
    OLED_ShowString(1, 1, "Num:");        // 第1行显示"Num:",标识计数
    
    while(1) // 主循环,持续更新显示
    { 
        OLED_ShowNum(1, 5, Num, 5);       // 显示中断次数(右对齐,5位)
        OLED_ShowNum(2, 5, TIM_GetCounter(TIM2), 5); // 显示TIM2当前计数值(0~9999循环)
    }
}

// TIM2更新中断服务函数(每秒触发一次)
void TIM2_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM2, TIM_IT_Update)) { // 检查中断标志
        Num++;                                // 计数+1(每秒更新)
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 清除标志
    }
}

Timer.

#include "stm32f10x.h"                  // 包含STM32F10x系列设备头文件,访问寄存器和外设功能

extern uint16_t Num;  // 外部声明计数变量(主程序中定义)

// 定时器TIM2初始化(内部时钟,1秒中断一次)
void Timer_Init(void)
{
    // 1. 使能TIM2时钟(APB1总线)
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

    // 2. 配置内部时钟源
    TIM_InternalClockConfig(TIM2);

    // 3. 时基单元配置(72MHz系统时钟下,1秒周期)
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
    TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;          // 时钟分频(无分频)
    TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;      // 向上计数
    TIM_TimeBaseInitStruct.TIM_Period = 9999;                         // 自动重装值(0~9999,共10000次计数)
    TIM_TimeBaseInitStruct.TIM_Prescaler = 7199;                      // 预分频(7200分频,72MHz/7200=10kHz)
    TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;                 // 重复计数(通用定时器不用)
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStruct);                  // 初始化时基

    // 4. 清除初始更新标志(避免误触发)
    TIM_ClearFlag(TIM2, TIM_FLAG_Update);

    // 5. 使能更新中断
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);

    // 6. NVIC配置(中断优先级)
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);                   // 分组:2位抢占,2位子优先级
    NVIC_InitTypeDef NVIC_InitStruct;
    NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;                      // TIM2中断通道
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;                      // 使能中断
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;            // 抢占优先级2
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;                   // 子优先级1
    NVIC_Init(&NVIC_InitStruct);                                      // 初始化NVIC

    // 7. 启动定时器
    TIM_Cmd(TIM2, ENABLE);
}

/*
// TIM2中断服务函数(需取消注释以更新Num)
void TIM2_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) {                // 检查更新中断标志
        Num++;                                                       // 计数变量递增(每秒+1)
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);                   // 清除中断标志
    }
}
*/

3.3效果:

Num 变化:每秒递增 1(如:0 → 1 → 2 → …)
CNT 变化:每秒内从 0 递增到 9999,然后归零,Num+1。

四、定时器外部中断

4.1接线图

在这里插入图片描述

4.2代码

Timer.c

#include "stm32f10x.h"                  // 包含STM32F10x系列外设寄存器定义

extern uint16_t Num;  // 外部声明的计数变量(需在主程序中定义)

/**
 * @brief  定时器TIM2初始化(ETR外部时钟模式)
 * @note   存在3处关键问题:
 *         1. PA0被配置为上拉输入,与ETR复用功能冲突;
 *         2. 中断服务函数被注释,无法响应溢出中断;
 *         3. 时基参数过于激进(周期9、预分频0),计数过快。
 */
void Timer_Init(void)
{
    // 1. 使能时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);  // 使能TIM2时钟(APB1总线)
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能GPIOA时钟(APB2总线)

    // 2. 配置PA0(冲突点:ETR模式下应自动复用,手动配置输入模式会冲突)
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;         // 上拉输入(与ETR功能冲突,建议移除)
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;     // 输入模式下速度设置无效,仅语法要求
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;             // 配置PA0引脚
    GPIO_Init(GPIOA, &GPIO_InitStructure);                // 初始化GPIOA

    // 3. 配置ETR外部时钟模式2
    //    参数:不分频、上升沿触发(NonInverted对应上升沿)、无滤波
    TIM_ETRClockMode2Config(TIM2, 
                            TIM_ExtTRGPSC_OFF,            // ETR预分频器关闭
                            TIM_ExtTRGPolarity_NonInverted, // 极性:非反相(上升沿触发)
                            0x00);                        // 滤波系数0(无滤波)

    // 4. 配置时基单元(参数问题:周期9 + 预分频0 → 每10个脉冲触发一次中断)
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
    TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; // 时钟分频(不影响计数频率)
    TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数
    TIM_TimeBaseInitStruct.TIM_Period = 10 - 1;             // 自动重装值:计数到9时溢出
    TIM_TimeBaseInitStruct.TIM_Prescaler = 1 - 1;           // 预分频值:0(不分频)
    TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;       // 重复计数器(通用定时器无效)
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStruct);        // 初始化时基

    // 5. 清除更新标志(避免初始化时误触发中断)
    TIM_ClearFlag(TIM2, TIM_FLAG_Update);

    // 6. 使能更新中断
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);              // 允许TIM2的更新中断

    // 7. 配置NVIC中断优先级
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);         // 分组:2位抢占优先级 + 2位子优先级
    NVIC_InitTypeDef NVIC_InitStruct;
    NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;            // 中断通道:TIM2全局中断
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;            // 使能中断通道
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;  // 抢占优先级:2
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;         // 子优先级:1
    NVIC_Init(&NVIC_InitStruct);                            // 初始化NVIC

    // 8. 启动定时器
    TIM_Cmd(TIM2, ENABLE);                                  // 启动TIM2计数器
}

/**
 * @brief  获取TIM2当前计数值
 * @retval TIM2的计数器值(0 ~ TIM_Period)
 */
uint16_t Timer_GetCounter(void)
{
    return TIM_GetCounter(TIM2);  // 读取TIM2的CNT寄存器值
}

/*
// 【关键缺失】TIM2中断服务函数(当前被注释,导致Num无法递增)
void TIM2_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) { // 检查更新中断标志
        Num++;                                        // 计数变量递增
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);   // 清除中断标志(必须操作)
    }
}
*/

main.c

#include "stm32f10x.h"                  // 包含STM32F10x系列设备的寄存器定义和外设库函数

#include "Delay.h"                      
#include "OLED.h"                       
#include "CountSensor.h"               
#include "Timer.h"                       // 定时器驱动头文件(包含TIM2初始化逻辑)

uint16_t Num = 0;                        // 全局计数变量,记录定时器中断次数

int main(void)
{		
    OLED_Init();                          // 初始化OLED显示屏(配置通信接口,如I2C/SPI)
    Timer_Init();                         // 初始化TIM2定时器(ETR外部时钟模式,需PA0输入外部信号)
    
  
    OLED_ShowString(1, 1, "Num:");  // 在OLED第1行第1列显示字符串"Num:",标识计数结果
  
    OLED_ShowString(2, 1, "CNT:");  // 在OLED第2行第1列显示字符串"CNT:",标识定时器当前计数值

    while(1) // 主循环,持续更新显示内容
    { 
   
        OLED_ShowNum(1, 5, Num, 5);     // 在OLED第1行第5列显示Num(右对齐,占5位)
        OLED_ShowNum(2, 5, Timer_GetCounter(), 5);   // 在OLED第2行第5列显示TIM2当前计数值(右对齐,占5位)
    }
}


void TIM2_IRQHandler(void)// TIM2定时器中断服务函数(处理更新中断,每TIM_Period+1个脉冲触发一次)
{

    if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)    // 检查TIM2的更新中断标志是否置位
    {
        Num++;                 // 计数变量递增(记录中断次数)
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 清除中断标志,避免重复触发
    }
}
  • 定时器配置(Timer.c):

使能 TIM2 和 GPIOA 时钟(PA0 为 ETR 引脚)。
配置 ETR 外部时钟模式(上升沿触发,不分频)。
时基参数:TIM_Period(自动重载值)和TIM_Prescaler(预分频值,需根据外部信号频率调整)。
使能更新中断,配置 NVIC 优先级。

  • 中断处理:

每次 TIM2 计数溢出(达到TIM_Period)时,Num加 1,用于统计中断次数。
OLED 实时显示Num(总计数)和TIM2->CNT(当前计数值,0~TIM_Period循环)。

4.3效果:

  • 当 PA0 接入外部脉冲信号(如方波)时,TIM2 计数器CNT随脉冲上升沿递增。
  • 每计数到 9(即收到 10 个脉冲)时,触发更新中断,Num加 1。
  • 计数器每触发一次CNT+1,加到9时,Num+1.