【江科大STM32学习笔记-06】TIM 定时器 - 6.1 定时器的基本定时功能

前言:第六章 TIM 定时器 包含以下四个部分内容:

  • 第一部分:介绍定时器的基本定时功能(本篇内容)
  • 第二部分:讲解定时器的输出比较功能(PWM)
  • 第三部分:探讨定时器的输入捕获功能
  • 第四部分:学习定时器的编码器接口

目录

[1 定时器简介](#1 定时器简介)

[2 STM32 定时器类型](#2 STM32 定时器类型)

[2.1 基本定时器(Basic Timer)](#2.1 基本定时器(Basic Timer))

[2.1.1 基本时基单元](#2.1.1 基本时基单元)

[2.1.2 工作原理与流程](#2.1.2 工作原理与流程)

[2.1.3 工程提示与注意点](#2.1.3 工程提示与注意点)

[2.2 通用定时器(General-Purpose Timer)](#2.2 通用定时器(General-Purpose Timer))

[2.2.1 主要功能概览](#2.2.1 主要功能概览)

[2.2.2 工作原理(分模块说明)](#2.2.2 工作原理(分模块说明))

[2.2.3 工程注意点](#2.2.3 工程注意点)

[2.3 高级定时器(Advanced-Control Timer)](#2.3 高级定时器(Advanced-Control Timer))

[2.3.1 主要功能概览](#2.3.1 主要功能概览)

[2.3.2 工作原理(按模块说明)](#2.3.2 工作原理(按模块说明))

[2.3.3 工程提示与注意点](#2.3.3 工程提示与注意点)

[3 定时器中断配置流程](#3 定时器中断配置流程)

[3.1 内部时钟触发定时器中断(使用 RCC 内部时钟)](#3.1 内部时钟触发定时器中断(使用 RCC 内部时钟))

[3.1.1 时钟开启](#3.1.1 时钟开启)

[3.1.2 时钟源配置](#3.1.2 时钟源配置)

[3.1.3 时基单元初始化](#3.1.3 时基单元初始化)

[3.1.4 中断输出使能](#3.1.4 中断输出使能)

[3.1.5 NVIC 配置](#3.1.5 NVIC 配置)

[3.1.6 运行控制](#3.1.6 运行控制)

[3.2 外部时钟触发定时器中断](#3.2 外部时钟触发定时器中断)

[3.2.1 时钟开启](#3.2.1 时钟开启)

[3.2.2 时钟源配置](#3.2.2 时钟源配置)

[3.2.3 时基单元初始化](#3.2.3 时基单元初始化)

[3.2.4 中断输出使能](#3.2.4 中断输出使能)

[3.2.5 NVIC 配置](#3.2.5 NVIC 配置)

[3.2.6 运行控制](#3.2.6 运行控制)

[4 相关元器件简介](#4 相关元器件简介)

[4.1 对射式红外传感器](#4.1 对射式红外传感器)

[4.1.1 传感器概述](#4.1.1 传感器概述)

[4.1.2 输出逻辑](#4.1.2 输出逻辑)

[5 本章节实验](#5 本章节实验)

[5.1 定时器定时中断](#5.1 定时器定时中断)

[5.1.1 实验目标](#5.1.1 实验目标)

[5.1.2 硬件设计](#5.1.2 硬件设计)

[5.1.3 软件设计](#5.1.3 软件设计)

[5.1.3.1 解析初始化后中断提前触发的"诡异"现象](#5.1.3.1 解析初始化后中断提前触发的“诡异”现象)

[5.1.4 实验现象](#5.1.4 实验现象)

[5.2 定时器外部时钟](#5.2 定时器外部时钟)

[5.2.1 实验目标](#5.2.1 实验目标)

[5.2.2 硬件设计](#5.2.2 硬件设计)

[5.2.3 软件设计](#5.2.3 软件设计)

[5.2.4 实验现象](#5.2.4 实验现象)



1 定时器简介

定时器(TIM)本质上是一个受控的计数器单元。它通过对基准时钟信号进行分频和累加,实现精确的时间管理功能。虽然名为"定时",但其底层逻辑其实是对时钟脉冲进行计数。

STM32 的定时器架构主要由时钟源、预分频器(PSC)和计数器(CNT)组成。其工作流程如下:

  1. 计数:在驱动时钟下,计数器(CNT)按步长递增;
  2. 匹配:当 CNT 达到自动重装载寄存器(ARR)设定的阈值时;
  3. 响应:硬件产生"更新事件"并触发中断。

这种基于硬件计数的定时方式,不仅精度极高,还能配合 DMA 或其他外设实现复杂的自动化控制逻辑。


2 STM32 定时器类型

STM32 的定时器功能强大、种类丰富。按功能复杂度和应用场景可以分为三大类:高级定时器(Advanced)通用定时器(General-purpose)基本定时器(Basic)。不同类型的定时器挂载在不同的总线上,这决定了它们的时钟源和最高运行频率,从而影响实际计时能力与外设联动能力。

定时器类型 典型编号 挂载总线 功能概述
高级定时器 TIM1、TIM8 APB2 拥有通用定时器全部功能,额外支持重复计数器、死区生成、互补输出、刹车输入等面向电机和电源控制的高级特性。
通用定时器 TIM2、TIM3、TIM4、TIM5 APB1 拥有基本定时器全部功能,额外具备内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等,应用最灵活。
基本定时器 TIM6、TIM7 APB1 结构最简单,仅提供基本定时中断和触发 DAC 的主模式输出,通常不暴露外部 I/O 引脚(用于产生稳定的时间基准)。

STM32F103C8T6 为例,该芯片包含以下计时源:

  • 高级定时器 (1个):TIM1
  • 通用定时器 (3个):TIM2、TIM3、TIM4
  • 基本定时器 (0个)(该型号不含TIM6/TIM7)
  • 其他 (3个):2个看门狗定时器(IWDG, WWDG)和1个系统滴答定时器(SysTick)。

2.1 基本定时器(Basic Timer)

基本定时器是 STM32 定时器家族中功能最精简的一类,设计目标是提供稳定、低开销的时间基准或作为其它外设的周期触发源。它不暴露比较/捕获通道,硬件实现简单,适用于周期性中断、定时唤醒和驱动 DAC。

2.1.1 基本时基单元

从基本定时器的框图可以看出,其仅包含最核心的时基单元部分。

  • PSC(Prescaler,预分频器) :16 位,用于对定时器输入时钟 CK_PSC 进行分频,从而改变计数速度。
  • CNT(Counter,计数器):16 位,用于记录当前的计数值。计数到 ARR 后触发更新事件,并重新从 0 开始计数。
  • ARR(Auto-Reload Register,自动重装载寄存器):16 位,用于定义计数器 CNT 计数的"终点值"。

2.1.2 工作原理与流程

基本定时器通过以下四个步骤循环工作,产生精准的时间基准:

(1)时钟输入与分频

定时器从 RCC 获得基准时钟 CK_PSC。经过预分频器后,生成计数时钟 CK_CNT。计算关系如下:

(2)计数驱动

CK_CNT 推动 CNT 按配置方向(通常向上)递增,直至等于 ARR。

(3)触发更新事件 (UEV)

当 CNT 的值累加至与 ARR 设定值相等时,硬件自动产生一个更新事件 (Update Event, UEV)

  • 中断/DMA 触发:若开启了相关功能,UEV 将触发更新中断或 DMA 请求。
  • 影子寄存器刷新:UEV 到来时,预装载的 ARR 或 PSC 值会被正式同步到活跃寄存器中。
  • TRGO 输出:UEV 可通过主模式控制器输出 TRGO 信号,直接触发 ADC 或 DAC。

(4)自动重置与循环

产生 UEV 后,CNT 会自动清零(0)并重新开始下一轮计数,从而实现连续的周期性定时。

:STM32F103C8T6 不含基本定时器(TIM6/TIM7),但在配置通用定时器的定时中断时,其核心逻辑与上述流程完全一致。

2.1.3 工程提示与注意点

  • PSC 实际分频系数为 PSC + 1;写 PSC=0 表示 1 分频(即不分频)。
  • ARR/PSC 通常采用影子寄存器机制,写入后在下一次 UEV 才会生效。
  • 基本定时器不直接驱动外设引脚;若需 PWM 或捕获功能,请使用通用或高级定时器。

2.2 通用定时器(General-Purpose Timer)

通用定时器(如 TIM2~TIM5)是在基本定时功能基础上的增强型,支持多通道信号处理与外设联动,是 STM32 中使用频率最高的一类定时器。

通用定时器框图如图所示:

2.2.1 主要功能概览

通用定时器通常具备以下核心能力:

  • 最多 4 个独立通道:每个通道可配置为输出比较、PWM 输出或输入捕获。
  • 多种计数模式:支持向上计数、向下计数、中心对齐模式。
  • 外部时钟与触发:支持 ETR / TIx 外部时钟输入与内部触发。
  • 主从同步机制:可与其他定时器级联或同步启动。

2.2.2 工作原理(分模块说明)

(1)计数核心与更新机制

通用定时器的底层时基结构与基本定时器一致。它从 RCC / APB 总线获得输入时钟(CK_APB),经预分频器 PSC 分频后形成计数节拍 CK_CNT。PSC 的实际分频系数为 PSC + 1,因此当 PSC = 0 时表示不分频。

CK_CNT 持续驱动计数器 CNT 按配置模式运行。CNT 可以向上计数、向下计数,或采用中心对齐(上下计数)模式运行。当 CNT 达到 ARR 所设定的自动重载值,或完成一次上下计数循环时,硬件产生更新事件 UEV。

UEV 是周期边界的重要同步点。它不仅标志一个计数周期结束,同时还承担寄存器更新功能------若启用了预装载机制,ARR 和 CCRx 的新值会在 UEV 时刻统一生效。此外,UEV 还可以触发更新中断、DMA 请求,或输出 TRGO 信号作为其他外设或定时器的触发源。

(2)比较单元与 PWM 输出

在计数运行过程中,硬件会实时比较 CNT 当前值与各通道的 CCRx 值。当 CNT 等于某个 CCRx 时产生比较匹配事件(Compare Match)。

这一事件可以直接控制对应的 OCx 输出引脚状态,例如置高、置低或翻转;也可以仅作为内部事件触发中断或 DMA。比较机制与自动重载寄存器 ARR 结合,就形成了 PWM 输出结构。

ARR 决定整个计数周期长度,因此决定 PWM 频率;CCRx 决定比较发生的时刻,因此决定占空比。只要修改 CCRx 的值,就可以改变输出脉冲的高电平持续时间,而不影响周期本身。

在边缘对齐模式下,CNT 单向计数,每个周期只发生一次比较匹配,结构简单、应用最广。中心对齐模式下,CNT 在上升和下降阶段各触发一次比较事件,输出波形围绕周期中心对称,谐波特性更优,常用于电机驱动等对波形质量要求较高的场景。

为了避免运行过程中直接写 ARR 或 CCRx 导致波形瞬态跳变,通用定时器通常启用影子寄存器机制。写入的新值首先进入缓冲区,只有在下一次 UEV 到来时才同步到工作寄存器,从而保证参数在周期边界更新,维持输出连续性。

(3)输入捕获与信号测量

当某个通道被配置为输入捕获模式时,其外部引脚的指定边沿到来时,会把当前 CNT 的数值锁存到对应的 CCRx 中。由于 CNT 本质上是时间计数器,因此锁存的值可以直接用于计算脉冲宽度、周期或频率。

输入路径通常带有数字滤波器和采样分频功能,用于抑制毛刺或抖动信号,提升测量稳定性。捕获事件发生后,可以产生中断通知 CPU,也可以通过 DMA 自动搬运数据,实现高频测量而不占用大量处理器资源。

(4)外部时钟与主从同步机制

通用定时器不仅可以使用内部 CK_APB 作为时基,还可以选择外部信号(如 ETR 或 TIx)作为计数时钟,从而实现对外部脉冲的计数功能。

此外,它支持主从同步模式。一个定时器可以通过 TRGO 输出触发信号,另一个定时器作为从设备接收该触发信号并同步启动或复位。通过这种方式,可以构建多个定时器组成的同步系统,例如多相 PWM 输出、同步 ADC 采样或级联计数结构。

这种硬件级联机制避免了软件触发带来的延迟和抖动,提高了系统整体时序一致性。

2.2.3 工程注意点

  • 高速 PWM 不宜完全依赖中断,应结合 DMA 以减轻 CPU 负担。
  • 中心对齐模式下,一个周期内比较事件会发生两次,需注意逻辑控制。
  • APB 分频改变时,定时器时钟可能存在倍频规则(如 APB1 预分频不为 1 时,定时器时钟会自动×2)。

2.3 高级定时器(Advanced-Control Timer)

高级定时器(典型:TIM1 / TIM8)在通用定时器基础上引入面向功率与电机控制的硬件特性,除了支持输出比较、输入捕获、PWM 和编码器接口外,还提供互补输出、可编程死区、重复计数器和刹车保护等功能,适用于三相逆变、电机驱动和高可靠性电源管理场景。

高级控制定时器框图如图所示:

2.3.1 主要功能概览

  • 具备通用定时器的全部功能。
  • 互补输出 (OCx 与 OCxN)及硬件死区时间(Dead-time)插入。
  • 重复计数器(Repetition Counter),用于控制寄存器更新的频率。
  • 刹车(Break)输入,用于硬件故障快速关断保护。

2.3.2 工作原理(按模块说明)

(1)计数核心与更新

时钟(CK_APB)经 PSC 分频生成 CK_CNT,驱动 CNT 按配置的对齐模式计数(向上/向下/中心对齐)。当 CNT 达到 ARR 或完成计数周期时产生 UEV,用于刷新影子寄存器并可输出 TRGO。

(2)比较与互补输出

每个通道的比较匹配(CNT = CCRx)不仅控制主输出 OCx 的翻转,也可同时或条件性地控制互补输出 OCx̄。互补输出在切换时钟入会按用户设定自动插入死区时间,确保主/互补在开关瞬间不会同时导通,防止桥臂直通故障。

(3)重复计数与寄存器更新

重复计数器允许把"寄存器更新/输出更新"的操作按 N 次 UEV 下采样,例如每 N 次 UEV 才真正将影子寄存器内容转入工作寄存器或触发外部事件,从而实现更细粒度的输出时序控制。

(4)刹车(Break)与故障保护

当检测到外部故障(如过流)或收到刹车信号时,硬件能立即以最高优先级将所有输出置于安全状态(强制关断或设为预定义电平),同时生成中断以通知软件处理。刹车路径是硬件级别的快速保护机制,响应时间短且可靠性高。

2.3.3 工程提示与注意点

  • 死区时间需根据驱动器与功率器件特性精确配置,过短会导致直通,过长会增加开关损耗/畸变。
  • 重复计数器与影子寄存器配合使用可避免输出更新时的瞬态错误,但会增加输出更新延迟,控制策略需兼顾。
  • 刹车输入通常与外部监测电路(过流/过压)联动,应制定明确的保护与恢复策略(例如硬件解除、手动复位或软件确认)。
  • 高级定时器通常挂载在较高速的 APB2 总线上(具体以芯片手册为准),因此在高频 PWM 场景下能提供更高的分辨率与实时性。
  • 在功率控制场景中,建议把关键保护逻辑尽可能放在硬件路径(刹车、死区)上,软件作为次级处理与状态记录。

3 定时器中断配置流程

无论时钟源来自哪里,配置定时器中断的通用标准流程如下:

  1. 时钟开启:使能 TIM 外设及相关外设的时钟。
  2. 时钟源配置:选择内部时钟或外部触发源(ETR/ITR/TIx)。
  3. 配置时基单元:设置预分频器(PSC)、自动重装载寄存器(ARR)、计数模式。
  4. 使能更新中断:开启定时器到 NVIC 的中断信号输出,使能定时器更新中断输出位。
  5. 配置 NVIC 中断通道:设定中断向量优先级分组,配置 TIM 中断通道并使能。
  6. 启动定时器:使能定时器计数器(CEN),定时器开始工作。

3.1 内部时钟触发定时器中断(使用 RCC 内部时钟)

在本章的第一个实验《定时器定时中断》中,定时器使用RCC内部时钟(72MHz)作为时钟源,实现精确的定时功能。

配置的流程图如图所示:

其配置流程大致包括以下关键步骤:

3.1.1 时钟开启

首先需要开启定时器的外设时钟。STM32 的通用定时器 TIM2 挂载在 APB1 总线上。开启时钟后,TIM2 的基准时钟和外设时钟才会工作。

代码示例如下:

cpp 复制代码
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // 开启TIM2的时钟

3.1.2 时钟源配置

调用相关得时钟源选择函数确定计数器的驱动基准。对于定时中断,通常选择内部时钟模式。

代码示例如下:

cpp 复制代码
TIM_InternalClockConfig(TIM2); // 选择TIM2为内部时钟(默认即为此配置)

3.1.3 时基单元初始化

此步骤直接决定了中断触发的频率。实验的目标是实现 1 秒(1Hz) 的定时中断,让变量 Num 每秒自增并在 OLED 上显示。

定时器的更新频率(即进入中断的频率)由以下公式决定:

其中各参数含义如下:

  • f_CLK :定时器的输入时钟频率(STM32F103 中 TIM2 默认由内部 RCC 提供,频率为 72MHz)。
  • PSC (Prescaler):预分频器数值。
  • ARR (Auto-Reload Register):自动重装载寄存器数值。
  • f:最终触发中断的频率(单位:Hz)。

若要实现 1 秒定时,即目标更新频率 f = 1Hz。我们将已知项代入公式:

通过移项可知,需要让 (PSC + 1) * (ARR + 1) = 72,000,000。为了计算方便且不超出 16 位寄存器的范围(最大 65535),通常进行如下组合:

  • 设定 PSC = 7200 - 1:这样预分频后的时钟频率为 72\\text{MHz} / 7200 = 10,000\\text{Hz}(即每秒计 10000 个数)。
  • 设定 ARR = 10000 - 1:计数器从 0 计到 9999,刚好经历 10000 个计数值。

**为什么在代码中要写 "- 1" ?**可能大家会由此疑问。底层逻辑只有一句话:STM32 的硬件计数是从 0 开始的。实际写入寄存器的值 = 你的目标计算结果 - 1

  • 预分频器 (PSC) :如果想实现 7200 分频,硬件会从 0 数到 7199 。如果填入 0 ,则代表 1 分频(不分频)。

  • 自动重装载 (ARR) :如果想计 10000 个数,硬件会从 0 累加到 9999 。当从 9999 回到 0 的那一瞬间,触发中断。

最终结果:

频率 1Hz 对应的周期 T = 1 / f = 1秒。

代码示例如下:

cpp 复制代码
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     // 时钟分频,用于滤波
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数模式
TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;               // ARR
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;             // PSC
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            // 高级定时器专用
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);

3.1.4 中断输出使能

开启定时器的更新中断(Update Interrupt)信号通道。在开启中断前,需手动清除更新标志位。

代码示例如下:

cpp 复制代码
TIM_ClearFlag(TIM2, TIM_FLAG_Update);        // 清除由于初始化产生的更新标志位
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);   // 开启定时器更新中断

注意:TIM_TimeBaseInit 在底层执行时会手动产生一次更新事件以装载预分频值,这会导致 UIF 标志位提前置 1。若不调用 TIM_ClearFlag,程序初始化后会立刻进入一次中断。

3.1.5 NVIC 配置

在内核中断控制器(NVIC)中设置定时器中断的优先级(抢占/响应优先级),并使能对应中断通道,确保 CPU 能够响应中断信号。

代码示例如下:

cpp 复制代码
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 设置优先级分组
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; // 定时器2中断通道
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; // 抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;        // 响应优先级
NVIC_Init(&NVIC_InitStructure);

3.1.6 运行控制

最后,使能定时器计数器,定时器正式开始计时,并在达到设定值(ARR)时触发中断服务函数。

代码示例如下:

cpp 复制代码
TIM_Cmd(TIM2, ENABLE); // 启动定时器

3.2 外部时钟触发定时器中断

在本章的第二个实验《定时器外部时钟》中,定时器TIM2使用来自 ETR 引脚(对应 PA0 引脚)输入的外部脉冲信号作为时钟源。

外部时钟触发模式允许用外部脉冲信号驱动定时器计数,从而在外部事件达到设定次数时产生中断。在该模式下,配置流程在上节基础上增加了 GPIO 和时钟源设置,配置的流程图如图所示:

其配置流程大致包括以下关键步骤:

3.2.1 时钟开启

除定时器时钟外,还需开启对应输入引脚的 GPIO 时钟。

代码示例如下:

cpp 复制代码
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

3.2.2 时钟源配置

配置 GPIO 引脚为输入模式,并将 TIM2 设置为外部时钟模式2。参数说明:

  • TIM_ExtTRGPSC_OFF 表示不使用外部预分频器,
  • TIM_ExtTRGPolarity_NonInverted 表示高电平或上升沿有效,
  • 滤波参数 0x0F 根据需要设置消抖。

这样 TIM2 的时钟源就切换为外部输入(ETR),每次外部脉冲到来时计数器增加 1。

代码示例如下:

cpp 复制代码
// GPIO 初始化
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, 0x0F); 
// 0x0F 为滤波器参数,用于滤除外部信号抖动

提示:STM32 参考手册中推荐把 TIM2 的 ETR(PA0)引脚配置为浮空输入,因为这能最真实地反映外部逻辑。如果引脚悬空或信号不稳定,浮空输入极易受电磁干扰产生"毛刺",导致计数器误触发。因此,为了增强抗干扰能力,通常推荐配置为上拉输入 (IPU)下拉输入

只有在外部信号源阻抗很高或信号驱动力很弱(例如某些传感器或微弱模拟信号)时,内部上拉电阻才可能会参与分压,干扰原始信号,无法达到 MCU 识别的逻辑阈值。此时,必须配置为浮空输入,以确保不影响外部信号的原始电平。

3.2.3 时基单元初始化

本章第二个实验《定时器外部时钟》中,TIM2 的计数时钟来自外部触发输入 ETR(对应引脚 PA0),外部脉冲由对射式红外传感器的 DO 引脚输出------我们通过遮挡/移开挡光片产生高低电平变化,模拟方波脉冲以驱动定时器计数。因此在时基单元的配置上应以"每个外部脉冲为一次计数"的思路来设置 PSC 与 ARR。

首先明确几个概念:

  • 定时器的计数器 CNT 在每个计数脉冲到来时递增一次;
  • ARR(Auto-Reload Register)定义CNT计数周期的上限,计数器从 0 计数到 ARR,然后产生一次更新事件(UEV);
  • PSC(Prescaler)是预分频器,实际分频系数为 PSC + 1。

对于外部时钟模式,计数脉冲源是来自 ETR 的外部信号,内部的 PSC 仍可用于进一步分频,但外部还有专门的外部触发预分频/滤波器设置(由 TIM_ETRClockMode2Config 的参数控制)。在本实验中,因外部脉冲来源为手动遮挡产生,频率较低且不可太快,因此选择不对外部脉冲再做分频,直接以每个外部脉冲计一次数。

基于上述考虑,示例中将 PSC 设置为 1 - 1(即 PSC = 0,表示不分频),ARR 设置为 10 - 1(即 ARR = 9),这意味着:计数器从 0 计数到 9(共 10 个外部脉冲)时产生一次更新事件并触发中断,进而在中断服务函数中将 Num 自增一次。

代码示例如下:

cpp 复制代码
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 10 - 1;   // 计满10个脉冲触发中断
TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1; // 外部时钟不分频
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);

3.2.4 中断输出使能

和内部时钟模式一样,清除标志位并使能更新中断:

cpp 复制代码
TIM_ClearFlag(TIM2, TIM_FLAG_Update);         // 清除标志,避免初始触发
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);    // 使能更新中断

3.2.5 NVIC 配置

同内部时钟模式,对 TIM2_IRQn 设置优先级并使能

cpp 复制代码
	/*NVIC中断分组*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//配置NVIC为分组2
																//即抢占优先级范围:0~3,响应优先级范围:0~3
																//此分组配置在整个工程中仅需调用一次
																//若有多个中断,可以把此代码放在main函数内,while循环之前
																//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
	
	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;				//选择配置NVIC的TIM2线
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;	//指定NVIC线路的抢占优先级为2
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1
	NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设
	

3.2.6 运行控制

使能定时器。此后,TIM2 将响应外部输入脉冲,当计数到 ARR 时触发中断。

cpp 复制代码
TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行

4 相关元器件简介

4.1 对射式红外传感器

4.1.1 传感器概述

提示:对射式红外传感器在上一篇【江科大STM32学习笔记-05】EXTI外部中断中有详细说明,这里只简单介绍一下。

对射式红外传感器是一种常见的光电开关型传感器,通常由红外发射管 和 红外接收管 组成,两者相对安装,中间形成一个固定宽度的"光槽"。当光路未被遮挡时,接收端可以正常接收到红外光;当有物体进入槽中遮挡光路时,接收状态发生变化,从而在输出端产生明确的电平跳变信号。

| 引脚名 | 功能说明 |
| VCC | 电源正极(3.3V~5V) |
| GND | 电源负极 |
| DO | 数字输出(TTL 电平) |

AO 模拟输出(本模块未接出/不起作用)

4.1.2 输出逻辑

在本实验所使用的对射式红外传感器模块中,其输出逻辑关系如下表所示:

光路状态 接收管状态 DO 输出电平 指示灯状态
无遮挡 导通 低电平
有遮挡 截止 高电平

即:

  • 当物体进入光路、产生遮挡时,DO 引脚电平由低电平跳变为高电平,产生一个上升沿;
  • 当物体离开光路、遮挡消失时,DO 引脚电平由高电平跳变为低电平,产生一个下降沿;

由此可知:遮挡时产生上升沿,移开遮挡时产生下降沿。它可以很好地作为定时器的外部脉冲源。


5 本章节实验

本章通过两个实验来讲解 STM32 定时器的基本定时功能与中断使用:第一个实验用内部时钟产生周期性定时中断并在 OLED 上显示计数;第二个实验把定时器作为计数器,使用外部时钟(PA0/ETR)驱动计数,并在外部事件达到设定次数时产生中断。两个实验代码结构相似,差别主要在时钟源与 GPIO 配置上,便于初学者理解"定时器 = 时钟来源 + 时基配置 + 中断/触发"的思想。

5.1 定时器定时中断

5.1.1 实验目标

掌握使用内部时钟配置 STM32 定时器以产生周期性更新中断;理解并实践以下要点:

  • 如何打开定时器外设时钟(RCC);
  • 如何设置时基单元(PSC、ARR)以得到所需周期;
  • 如何使能并配置 TIM 更新中断(TIM_IT_Update);
  • 如何在 NVIC 中设置中断通道与优先级;
  • 中断服务例程(ISR)中如何判断、处理并清除中断标志;
  • 在中断中更新状态并在主程序(OLED 显示)中读取显示。

完成后,能用定时中断驱动一个变量 Num 周期性自增,并在 OLED 上实时显示该数值,验证中断触发与计时精度。

5.1.2 硬件设计

5.1.3 软件设计

本实验软件分为两大阶段:一次性初始化和循环显示(主循环),中断处理放在独立 ISR 中。

(1)初始化阶段(只执行一次)

  1. 打开定时器时钟:调用 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); 打开 TIM2 的时钟,使能外设寄存器可写、定时器可工作。
  2. 选择时钟源:显式调用 TIM_InternalClockConfig(TIM2);(可选,因为默认也是内部时钟),明确使用内部时钟作为计时源。
  3. 配置时基单元:使用 TIM_TimeBaseInitTypeDef 设置 TIM_Prescaler(PSC)和 TIM_Period(ARR)。
  4. 清除更新标志并使能更新中断:先 TIM_ClearFlag(TIM2, TIM_FLAG_Update); 防止初始化时立刻触发中断,然后 TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); 打开更新中断。
  5. 配置 NVIC:调用 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 设置分组(只需全工程调用一次),然后通过 NVIC_InitTypeDef 设置 NVIC_IRQChannel = TIM2_IRQn、抢占优先级与子优先级,最后 NVIC_Init(&NVIC_InitStructure); 使能。
  6. 启动定时器:TIM_Cmd(TIM2, ENABLE); 使能 TIM2 开始计数。

(2)主循环阶段(无限循环)

  • 主程序不做计时工作,仅负责显示。示例中 main() 先初始化 OLED 与定时器,然后在 while(1) 中不断调用 OLED_ShowNum(1,5,Num,5); 将中断中自增的 Num 显示出来。这样可以把定时/计数逻辑与显示逻辑解耦。

(3)中断处理(TIM2_IRQHandler)

  • 在中断函数中先调用 TIM_GetITStatus(TIM2, TIM_IT_Update) 判断是否为更新中断,然后执行用户逻辑(示例为 Num++),最后调用 TIM_ClearITPendingBit(TIM2, TIM_IT_Update) 清除挂起位。清除中断标志是必须的,否则中断会被反复触发导致主程序无法运行。
5.1.3.1 解析初始化后中断提前触发的"诡异"现象

(1) 现象描述

在初始化流程中,若未在调用 TIM_TimeBaseInit() 函数后、开启中断前手动执行 TIM_ClearFlag() 操作,会出现一个异常现象:每次按下复位键(Reset)后,OLED 显示屏上的 Num 数值并非从 0 开始计数,而是直接显示为 1。

(2)原因分析

这表明初始化函数在内部生成了一次"更新事件(UEV)",并把更新中断标志置位了,因此在真正开启中断使能后会马上进入一次 ISR。

追踪库函数源码,在 TIM_TimeBaseInit 的末尾发现这样一行代码和注释:

复制代码
/* 向事件产生寄存器(EGR)写入 1,手动产生一个更新事件 */
TIMx->EGR = TIM_PSCReloadMode_Immediate; 

它的目的是:立即产生一个更新事件(UEV)来重新装载预分频器(PSC)和自动重装载寄存器(ARR)的值。

这是由定时器的硬件架构决定的。预分频器(PSC)具有**影子寄存器(Shadow Register)**机制。当我们写入新的分频值时,它并不会立刻生效,只有在产生"更新事件"时,值才会被真正加载到硬件逻辑中。

库函数为了保证初始化完成后,定时器能立刻按照你设定的频率运行,所以贴心地在最后"手动"触发了一次更新事件,但它同时也带来了一个副作用:产生更新事件(UEV)的同时,硬件会自动将更新中断标志位(UIF)置为 1。

这意味着,当你随后执行 TIM_ITConfig 开启中断使能时,中断系统发现 UIF 已经是 1 了,于是 CPU 认为中断条件已达成,初始化一结束就立刻飞奔进中断函数执行了 Num++

(3)解决方案

要解决这个问题,只需在初始化时基单元之后、开启中断使能之前,手动调用一次清除标志位的函数即可:

复制代码
// 1. 时基单元初始化(底层会手动产生一次更新事件,导致 UIF = 1)
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);

// 2. 关键:手动清除掉初始化产生的更新中断标志位,避免误进中断
TIM_ClearFlag(TIM2, TIM_FLAG_Update); 

// 3. 此时再开启中断输出使能,逻辑就是干净的了
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); 

总结: 这一行 TIM_ClearFlag 相当于给中断系统做了一次"归零"校准,确保实验中的 Num 严格从 0 开始递增。

具体代码如下:

main.c文件:

cpp 复制代码
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"

uint16_t Num;			//定义在定时器中断里自增的变量

int main(void)
{
	/*模块初始化*/
	OLED_Init();		//OLED初始化
	Timer_Init();		//定时中断初始化
	
	/*显示静态字符串*/
	OLED_ShowString(2, 1, "Num:");			//2行1列显示字符串Num:
	OLED_ShowString(3,1,"CNT:");            //3行1列显示字符串Num:
	while (1)
	{
		OLED_ShowNum(2, 5, Num, 5);			    //不断刷新显示Num变量
		OLED_ShowNum(3,5,Timer_GetCounter(),5); //不断刷新显示CNT的值
	}
}

/**
  * 函    数:TIM2中断函数
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
  *           函数名为预留的指定名称,可以从启动文件复制
  *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
  */
void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)		//判断是否是TIM2的更新事件触发的中断
	{
		Num ++;												//Num变量自增,用于测试定时中断
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);			//清除TIM2更新事件的中断标志位
															//中断标志位必须清除
															//否则中断将连续不断地触发,导致主程序卡死
	}
}

Timer.c文件:

cpp 复制代码
#include "stm32f10x.h"                  // Device header

/**
  * 函    数:定时中断初始化
  * 参    数:无
  * 返 回 值:无
  */
void Timer_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟
	
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM2);		//选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
	
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;	//计数器模式,选择向上计数
	TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;				//计数周期,即ARR的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;				//预分频器,即PSC的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;			//重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);				//将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元	
	
	/*中断输出配置*/
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);						//清除定时器更新标志位
																//TIM_TimeBaseInit函数末尾,手动产生了更新事件
																//若不清除此标志位,则开启中断后,会立刻进入一次中断
																//如果不介意此问题,则不清除此标志位也可
	
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);					//开启TIM2的更新中断
	
	/*NVIC中断分组*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//配置NVIC为分组2
																//即抢占优先级范围:0~3,响应优先级范围:0~3
																//此分组配置在整个工程中仅需调用一次
																//若有多个中断,可以把此代码放在main函数内,while循环之前
																//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
	
	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;				//选择配置NVIC的TIM2线
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;	//指定NVIC线路的抢占优先级为2
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1
	NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设
	
	/*TIM使能*/
	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}

uint16_t Timer_GetCounter(void)
{
	return TIM_GetCounter(TIM2);
}

/* 定时器中断函数,可以复制到使用它的地方
void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
	{
		
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}
*/

5.1.4 实验现象

运行程序后,OLED 屏上会显示静态字符串 "Num:" 以及不断增长的数值 Num。以示例 PSC/ARR 配置为目标,Num 将以约固定周期(示例为 1 秒)递增一次。主循环不断刷新 OLED,读者可通过观察数值变化验证定时器中断触发、ISR 正常执行以及标志位清除是否正确。

5.2 定时器外部时钟

5.2.1 实验目标

理解并掌握将 STM32 定时器配置为外部时钟输入模式的方法;学会配置外部引脚作为 ETR(外部触发),并观察:当外部脉冲达到预设次数(ARR)时,定时器产生更新中断。通过该实验学会:

  • 配置 GPIO 为外部时钟输入(上拉/下拉选择);
  • 使用 TIM_ETRClockMode2Config 将 TIM 切换到外部时钟模式(ETR);
  • 结合 PSC/ARR 设置在 N 个外部脉冲后产生中断;
  • 在主程序中读取并显示当前 CNT 值以验证外部计数行为。

5.2.2 硬件设计

5.2.3 软件设计

外部时钟模式的软件流程与内部时钟模式类似,但在初始化阶段需增加 GPIO 配置与外部时钟模式设置,主要步骤如下:

  1. 打开时钟与 GPIO :启用 TIM2 时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); 并启用 PA0 所在 GPIO 的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
  2. 配置 PA0 为输入 :通过 GPIO_InitTypeDef 将 PA0 设置为上拉输入(GPIO_Mode_IPU),保证无外部信号时的确定电平。
  3. 设置外部时钟模式(ETR) :调用 TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x0F); 将 TIM2 切换到外部时钟模式 2,使定时器以 ETR 管脚输入的脉冲作为计数时钟。参数中 TIM_ExtTRGPSC_OFF 表示不使用外部预分频,TIM_ExtTRGPolarity_NonInverted 表示非反向极性(上升沿/高电平有效,具体以芯片手册为准),0x0F 为滤波器寄存器用于消除毛刺(示例使用较大滤波值以提高鲁棒性)。
  4. 配置时基单元(PSC/ARR) :与内部计数相同,使用 TIM_TimeBaseInit 配置计数模式、PSC 与 ARR。示例中为了让定时器在每 10 个外部脉冲后产生一次更新中断,设 PSC = 1-1(无分频)、ARR = 10-1。实际应用中可根据脉冲频率与期望响应频率调整。
  5. 清除标志并使能中断 :调用 TIM_ClearFlag(TIM2, TIM_FLAG_Update); 清初始标志,然后 TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); 使能更新中断。
  6. 设置 NVIC 并启动定时器 :按内部时钟实验一样配置 NVIC_PriorityGroupConfigNVIC_Init,最后 TIM_Cmd(TIM2, ENABLE); 启动计数。
  7. ISR 与主循环 :中断服务函数 TIM2_IRQHandler 检查 TIM_GetITStatus(TIM2, TIM_IT_Update),在触发时对 Num 自增并 TIM_ClearITPendingBit 清标志。主循环可以通过 Timer_GetCounter()(示例提供了该函数)读取当前 CNT 值并显示在 OLED 上,便于实时观察外部脉冲计数与中断触发。

具体代码如下:

main.c文件:

cpp 复制代码
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"

uint16_t Num;			//定义在定时器中断里自增的变量

int main(void)
{
	/*模块初始化*/
	OLED_Init();		//OLED初始化
	Timer_Init();		//定时中断初始化
	
	/*显示静态字符串*/
	OLED_ShowString(2, 1, "Num:");			//1行1列显示字符串Num:
	OLED_ShowString(3, 1, "CNT:");			//2行1列显示字符串CNT:
	
	while (1)
	{
		OLED_ShowNum(2, 5, Num, 5);			//不断刷新显示Num变量
		OLED_ShowNum(3, 5, Timer_GetCounter(), 5);		//不断刷新显示CNT的值
	}
}

/**
  * 函    数:TIM2中断函数
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
  *           函数名为预留的指定名称,可以从启动文件复制
  *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
  */
void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)		//判断是否是TIM2的更新事件触发的中断
	{
		Num ++;												//Num变量自增,用于测试定时中断
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);			//清除TIM2更新事件的中断标志位
															//中断标志位必须清除
															//否则中断将连续不断地触发,导致主程序卡死
	}
}

Timer.c文件:

cpp 复制代码
#include "stm32f10x.h"                  // Device header

/**
  * 函    数:定时中断初始化
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数配置为外部时钟,定时器相当于计数器
  */
void Timer_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟
	
	/*GPIO初始化*/
	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);						//将PA0引脚初始化为上拉输入
	
	/*外部时钟配置*/
	TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x0F);
																//选择外部时钟模式2,时钟从TIM_ETR引脚输入
																//注意TIM2的ETR引脚固定为PA0,无法随意更改
																//最后一个滤波器参数加到最大0x0F,可滤除时钟信号抖动
	
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;	//计数器模式,选择向上计数
	TIM_TimeBaseInitStructure.TIM_Period = 10 - 1;					//计数周期,即ARR的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;				//预分频器,即PSC的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;			//重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);				//将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元	
	
	/*中断输出配置*/
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);						//清除定时器更新标志位
																//TIM_TimeBaseInit函数末尾,手动产生了更新事件
																//若不清除此标志位,则开启中断后,会立刻进入一次中断
																//如果不介意此问题,则不清除此标志位也可
																
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);					//开启TIM2的更新中断
	
	/*NVIC中断分组*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//配置NVIC为分组2
																//即抢占优先级范围:0~3,响应优先级范围:0~3
																//此分组配置在整个工程中仅需调用一次
																//若有多个中断,可以把此代码放在main函数内,while循环之前
																//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
	
	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;				//选择配置NVIC的TIM2线
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;	//指定NVIC线路的抢占优先级为2
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1
	NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设
	
	/*TIM使能*/
	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}

/**
  * 函    数:返回定时器CNT的值
  * 参    数:无
  * 返 回 值:定时器CNT的值,范围:0~65535
  */
uint16_t Timer_GetCounter(void)
{
	return TIM_GetCounter(TIM2);	//返回定时器TIM2的CNT
}

/* 定时器中断函数,可以复制到使用它的地方
void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
	{
		
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}
*/

5.2.4 实验现象

程序运行后,OLED 第2行显示 Num:,第三行显示 CNT:

当不断用挡光片来回遮挡对射式红外传感器的光耦时, DO端口输出脉冲信号,可以观察到:

  • CNT 数值随着遮挡的次数不断递增;
  • CNT 计数达到设定值9之后自动清零,同时 Num 数值加 1;

若一直不断的用挡光片来回遮挡,则:

  • CNT 周期性递增并回零;
  • CNT 每回零一次,Num 递增一次。

实验现象表明,定时器能够对外部输入脉冲进行计数,并在达到设定值后产生一次中断更新。

相关推荐
四谎真好看2 小时前
SSM学习笔记(Spring篇Day03)
笔记·学习·学习笔记·ssm
YangYang9YangYan2 小时前
2026大专大数据与会计专业学习数据分析的价值分析
大数据·学习·数据分析
weixin_448119942 小时前
Datawhale Easy-Vibe 202602 第2次笔记
笔记
2501_901147832 小时前
打家劫舍Ⅱ 延伸学习笔记
笔记·学习
weixin_448119942 小时前
Datawhale 硅基生物进化论 202602第2次笔记
笔记
Peter·Pan爱编程3 小时前
NVIDIA DKMS 驱动构建失败修复笔记
笔记·cuda
Дерек的学习记录10 小时前
C++:入门基础(下)
开发语言·数据结构·c++·学习·算法·visualstudio
亿道电子Emdoor10 小时前
【Arm】Keil MDK 的Symbols窗口
stm32·单片机·嵌入式硬件
半壶清水10 小时前
[软考网规考点笔记]-OSI参考模型与TCP/IP体系结构
网络·笔记·tcp/ip