还不了解什么是中断的读者可以看下我的另一篇文章,他会帮你对中断有个基本的认识,可以帮你进行简单的应用。

https://blog.csdn.net/2301_80548884/article/details/148222251?spm=1001.2014.3001.5501

总的来说:中断是电脑里CPU停下正在做的事,先去处理突然来的任务(像按键盘、硬件报错),处理完再接着干原来的工作的机制。

产生中断的源头或设备被称为中断源

中断源可以是计算机系统内外部的各种事件或设备,例如:

外部中断源:键盘、鼠标、打印机等外部硬件设备,或外部传感器的信号变化。

内部中断源:程序运行中的异常(如除零错误)、系统调用、定时器超时等内部事件。

在上述的文章内讲解了外部中断,本文介绍的是STM32的外部定时器产生中断。

一、定时器是什么

1.定时器的简介

定时器是一种能够对时间进行测量、控制的设备或者功能模块,其用途是生成精准的时间基准,进而实现定时执行任务、频率测量以及 PWM(脉冲宽度调制)控制等功能。

这里我们主要会使用通用定时器。

2.基本原理

定时器的核心原理是对时钟信号的脉冲进行计数。它主要由以下几个部分构成:

时钟源:这是定时器的基础,为其提供稳定的脉冲信号。常见的时钟源有内部晶振、外部时钟等。

计数器(CNT):负责对时钟脉冲进行累加计数。当计数值达到预设的阈值时,就会触发相应的事件。

         

定时器的计数器(CNT)工作模式通常分为以下几种,不同 MCU 的定时器可能支持其中部分或全部模式:

1. 向上计数模式(Up Counting)

  • 工作方式:CNT 从 0 开始递增,达到自动重载值(ARR)时触发更新事件,然后重置为 0 重新计数。

  • 应用场景:最常见的定时模式,如生成固定频率的中断、PWM 输出。

2. 向下计数模式(Down Counting)

  • 工作方式:CNT 从 ARR 值开始递减,减到 0 时触发更新事件,然后重新加载 ARR 值继续计数。

  • 应用场景:某些需要倒计时的场合,如电机控制中的斜坡减速。

3. 中心对齐模式(Center-Aligned,也叫中央对齐或增减计数模式)

  • 工作方式:CNT 先从 0 递增到 ARR,再从 ARR 递减回 0,一个周期内触发两次更新事件(递增到 ARR 和递减到 0 时)。

  • 应用场景

    • PWM 控制:生成对称的 PWM 波形(如电机驱动中的互补 PWM),减少谐波干扰。

    • 精密测量:双倍频率的事件触发,提高测量分辨率。

4. 单向计数模式(One-Pulse Mode)

  • 工作方式:CNT 完成一次计数(从 0 到 ARR 或从 ARR 到 0)后停止,需软件重新触发才会再次计数。

  • 应用场景:生成单脉冲信号,如触发 ADC 采样或外部设备。

5. 编码器模式(Encoder Mode)

  • 工作方式:CNT 根据编码器输入的 A/B 相脉冲自动递增或递减,用于测量电机转速或位置。

  • 应用场景:电机闭环控制、机器人位置反馈。

自动重载寄存器(ARR):存储计数目标值。

预分频器(PSC):可以对时钟频率进行调整,从而扩大定时器的计时范围。

3.主要功能

定时功能:当计数器的值达到设定的目标值时,会触发中断或者输出信号,以此实现精确定时的目的。

PWM 输出:能够生成占空比可调节的方波,在电机控制、LED 调光等领域有广泛应用。

输入捕获:可以记录外部信号的边沿时间,用于测量频率、脉冲宽度等参数。

输出比较:将计数器的值与比较寄存器的值进行比较,进而控制输出引脚的电平变化。

4.小结

选择好时钟源后,我们可以通过时基单元:计数器、预分频器、自动重载寄存器等可以实现脉冲信号计数,当选用向上计数模式后,每来一次脉冲信号CNT就自增1,达到自动重载值后产生一次中断。

二、定时器代码配置实现

以下的代码目的是实现定时器使num自增,显示在OLED上。

Timer.c代码

void Timer_Init(void)
{
// 初始化 GPIO 结构体
GPIO_InitTypeDef GPIO_InitStructure;
// 配置 GPIOA 的第 0 引脚为上拉输入模式
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);

// 配置 TIM2 的外部时钟模式 2
// 使用外部时钟源(TIM2 时钟),不分频,触发极性为非反向(上升沿触发),外部触发滤波器为 0x09
TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x09);

// 初始化时基单元结构体
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
// 设置时钟分频因子为不分频
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
// 设置计数模式为向上计数模式(这里重复赋值了,应只保留一次)
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
// 设置自动重装载值为 9,定时周期 = (自动重装载值 + 1) / 计数频率
TIM_TimeBaseInitStructure.TIM_Period = 10 - 1;
// 设置预分频器值为 0,计数频率 = 时钟源频率 / (预分频器值 + 1)
TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;
// 设置重复计数器值为 0,用于 PWM 模式下的重复计数功能
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
// 初始化 TIM2 的时基单元
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);

// 清除 TIM2 的更新中断标志位,防止在启动定时器后立即触发中断
TIM_ClearFlag(TIM2, TIM_FLAG_Update);
// 使能 TIM2 的更新中断,允许在计数器溢出时触发中断
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);

// 配置 NVIC 的优先级分组为组 2,抢占优先级和子优先级各占 2 位
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
// 初始化 NVIC 结构体
NVIC_InitTypeDef NVIC_InitSturcture;
// 设置 TIM2 的中断通道
NVIC_InitSturcture.NVIC_IRQChannel = TIM2_IRQn;
// 使能 TIM2 的中断通道
NVIC_InitSturcture.NVIC_IRQChannelCmd = ENABLE;
// 设置 TIM2 中断的抢占优先级为 2
NVIC_InitSturcture.NVIC_IRQChannelPreemptionPriority = 2;
// 设置 TIM2 中断的子优先级为 1
NVIC_InitSturcture.NVIC_IRQChannelSubPriority = 1;
// 初始化 NVIC
NVIC_Init(&NVIC_InitSturcture);

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

uint16_t Timer_GetCounter(void)
{
//返回 STM32 定时器 2(TIM2)的计数器(CNT)当前值
	return TIM_GetCounter(TIM2);
}

主函数

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.H"
#include "Timer.h"
uint16_t Num; // 用于记录中断触发次数的变量

int main()
{
    // 初始化 OLED 显示屏
    OLED_Init();
    // 初始化定时器
    Timer_Init();
    
    // 在 OLED 上显示字符串 "Num: ",用于显示中断触发次数
    OLED_ShowString(1, 1, "Num: ");
    // 在 OLED 上显示字符串 "CNT: ",用于显示定时器当前计数值
    OLED_ShowString(2, 1, "CNT: ");
    
    while (1)
    {
        // 在 OLED 上显示 Num 的值,显示在第 1 行第 5 列,显示 5 位数字
        OLED_ShowNum(1, 5, Num, 5);
        // 在 OLED 上显示定时器当前计数值,显示在第 2 行第 5 列,显示 5 位数字
        OLED_ShowNum(2, 5, Timer_GetCounter(), 5);
    }
}

// TIM2 中断服务函数
void TIM2_IRQHandler(void)
{
    // 检查 TIM2 的更新中断标志位是否被设置
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
    {
        // 如果中断标志位被设置,Num 变量加 1,记录中断触发次数
        Num++;
        // 清除 TIM2 的更新中断标志位,防止再次触发中断
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    }
}

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐