STM32:深入理解定时器与使用定时中断实现精准延时

一、什么是定时器?

定时器,顾名思义就是用来定时的内部外设。不同的芯片型号上搭载了不同的定时器,定时器的类型也分为高级定时器,中级定时器,基本定时器。

高级定时器 (TIM1, TIM8等)

高级定时器功能最为强大,通常包含以下特性:

  • 多通道 PWM 输出: 可以产生多个独立的 PWM 信号,用于电机控制、逆变器等应用。
  • 互补输出: 支持互补的 PWM 输出,常用于控制三相电机。
  • 死区时间插入: 在互补输出之间插入可编程的死区时间,防止功率器件同时导通。
  • 编码器接口: 可以直接连接增量式编码器,用于测量旋转速度和方向。
  • 触发输入和输出: 可以与其他定时器或外设进行同步触发。

高级定时器通常用于需要精确控制和复杂波形生成的场合。

中级定时器 (TIM2, TIM3, TIM4, TIM5等)

中级定时器也称为通用定时器,功能相对平衡,包含以下特性:

  • 基本计数功能: 提供向上、向下或中央对齐的计数模式。
  • 可编程预分频器: 可以对时钟源进行分频,以获得不同的计数频率。
  • 自动重装载功能: 计数器溢出后可以自动重装载预设值。
  • 输出比较功能: 可以产生单脉冲或用于 PWM 生成。
  • 输入捕获功能: 可以捕获外部信号的上升沿、下降沿或双边沿。
  • 触发输入和输出: 可以与其他定时器或外设进行同步触发。

中级定时器应用广泛,可以用于测量时间间隔、生成 PWM 信号、实现简单的定时中断等,例如您提供的链接中的实验就是使用了通用定时器 TIM3 来实现中断。

基本定时器 (TIM6, TIM7等)

基本定时器功能最为简单,主要用于产生基本的时间基准,包含以下特性:

  • 简单的向上计数功能: 只能进行向上计数。
  • 可编程预分频器: 可以对时钟源进行分频。
  • 自动重装载功能: 计数器溢出后可以自动重装载预设值。

基本定时器通常用于驱动 DAC 转换器或者作为简单的延时功能。

网上有一个说法,定时器本质上就是计数器,何以见得,我觉得通过下面关于时基单元的介绍,也许能解释这个说法。

二、时基单元是什么,有什么用?

首先,我们引入一个东西,晶振,

晶振

晶体振荡器(Crystal Oscillator)是一种利用石英晶体的压电效应制成的谐振器件。当石英晶体受到外加电场的作用时会发生形变,反之,当石英晶体发生形变时也会产生电场。如果在石英晶体的两个电极之间施加一个交变电压,当电压的频率接近石英晶片的固有频率时,就会发生谐振现象,产生一个非常稳定的振荡信号。

在嵌入式系统中,晶振通常作为系统时钟的来源,为 CPU、外设等提供稳定的时序基准。晶振的频率非常精确且稳定,受温度和电压变化的影响较小,因此是实现精确定时的关键。常见的晶振频率有 8MHz、12MHz、25MHz 等。

总而言之,晶振会以一个一定的频率发出一个信号,我们使用一个计数器接受到信号,然后计数器得到的数,就反映了时间。时基单元的配置,就是配置信号的频率,以及我们需要定时的时间,这个定时的时间,其实也是通过对比计数器得到的数来体现的。

时基单元

时基单元(Time Base Unit)是定时器的核心组成部分,它负责产生定时器的时间基准。一个典型的时基单元包含以下几个关键组件:

  1. 时钟源 (Clock Source): 定时器的计数时钟来源,通常是来自系统时钟(如 APB 总线时钟)经过选择和可能的预分频后的时钟信号。在 STM32 中,可以通过 RCC 寄存器配置时钟源和分频系数。
  2. 预分频器 (Prescaler, PSC): 预分频器用于对输入的时钟源进行分频。通过设置预分频器的值,可以将时钟频率降低到适合计数器计数的范围。预分频器的作用是延长计数器的计数周期,从而实现更长时间的定时。例如,如果预分频器的值为 N,则计数器的时钟频率为输入时钟频率的 1/(N+1)。
  3. 计数器 (Counter, CNT): 计数器是一个寄存器,它在每个时钟脉冲到来时进行递增(或递减,取决于计数模式)。计数器的位数决定了其最大计数值,例如一个 16 位的计数器可以计数到 65535。
  4. 自动重装载寄存器 (Auto-Reload Register, ARR): 自动重装载寄存器存储着计数器需要达到的目标值。当计数器计数到 ARR 的值时,会发生以下动作之一(取决于配置):
    • 溢出 (Overflow): 如果是向上计数模式,计数器会从 0 重新开始计数,并可能产生一个更新事件(Update Event),该事件可以触发中断。
    • 比较匹配 (Compare Match): 如果配置了输出比较功能,当计数器值等于某个比较寄存器的值时,也会产生相应的事件。

通过配置预分频器和自动重装载寄存器的值,可以精确地控制定时器的计数频率和计数周期,从而实现所需的定时时间。例如,假设 APB1 时钟频率为 36MHz,我们希望实现 1ms 的定时周期。可以将预分频器设置为 35,这样计数器的时钟频率就为 1MHz。然后将自动重装载寄存器设置为 999,这样计数器每计数 1000 次(即 1ms)就会溢出一次,产生更新事件。

时基单元原理图

时基单元的原理可以简单描述为:

一个时钟源信号输入到预分频器 (PSC),预分频器根据配置的分频系数对时钟信号进行分频,得到计数器 (CNT) 的时钟信号。计数器在该时钟信号的驱动下进行计数,其计数值不断与自动重装载寄存器 (ARR) 中的值进行比较。当计数器的值达到或超过 ARR 的值时,会产生一个更新事件(Update Event)。这个更新事件可以触发中断标志位,从而引发定时中断。

逻辑连接示意:

Clock Source ---> Prescaler (PSC) ---> Counter (CNT) ---> Comparator (vs. Auto-Reload Register ARR) ---> Update Event Flag

三、定时中断是什么

理解了时基单元,自然就知道定时中断是什么了。当定时器的计数器计数到自动重装载寄存器设定的值,并产生更新事件时,如果使能了相应的中断,那么就会触发一个中断请求信号,CPU 响应这个请求后,会跳转到预先定义好的中断服务函数中执行相应的代码。在 STM32 中,需要配置定时器的中断使能位以及 NVIC(嵌套向量中断控制器),才能使能定时中断。您提供的链接中的实验就展示了如何配置 TIM3 的中断,并在中断服务函数中翻转 LED 的电平。关于中断,可以看这篇文章中断:嵌入式系统的高效事件处理机制_嵌入式单片机中断-CSDN博客文章浏览阅读315次,点赞3次,收藏5次。中断是嵌入式系统中一种重要的事件处理机制。它允许单片机在执行主程序的过程中,当有特定的外部事件发生时,能够暂时中断当前的任务,转而去执行一段专门处理该事件的程序,处理完毕后再回到原来的位置继续执行主程序。这就好比你在专注地写代码,突然手机铃声响起,你暂停编码,接听电话处理重要事务,通话结束后再回到代码编辑状态,继续未完成的工作。总的来说,中断是一种节约资源的手段,也是提升程序灵活度的工具,与日常生活也很贴近,很好理解。_嵌入式单片机中断https://blog.csdn.net/m0_62710548/article/details/146465808?spm=1001.2014.3001.5501

四、用定时中断写一个 delay()

补充一个常用定时中断做的 delay()

复制代码
#include "stm32f10x.h" // 根据你的STM32型号选择头文件

// 定义一个全局变量作为延时标志位
volatile uint8_t delay_flag = 0;

// 配置 TIM2 定时器
void TIM2_Configuration(uint16_t period_ms) {
    // 1. 使能 TIM2 时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

    // 2. 配置 TIM2 的时基单元
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    // 假设系统时钟为 72MHz,APB1 时钟为 36MHz
    // 设置预分频系数,将时钟频率降低到 1MHz (36MHz / 36)
    TIM_TimeBaseStructure.TIM_Prescaler = 35;
    // 设置自动重装载值,以实现 period_ms 毫秒的定时
    // 例如,period_ms = 1 时,计数 1000 次溢出 (1MHz / 1000 = 1kHz, 周期 1ms)
    TIM_TimeBaseStructure.TIM_Period = period_ms * 1000 - 1;
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

    // 3. 使能 TIM2 中断
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);

    // 4. 配置 NVIC 中断控制器
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 抢占优先级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;      // 子优先级
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    // 5. 使能 TIM2 定时器
    TIM_Cmd(TIM2, ENABLE);
}

// TIM2 中断服务函数
void TIM2_IRQHandler(void) {
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
        // 清除中断标志位
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
        // 设置延时标志位
        delay_flag = 1;
    }
}

// 延时函数 (毫秒级)
void delay_ms(uint16_t ms) {
    delay_flag = 0;
    TIM2_Configuration(ms); // 重新配置定时器以实现所需的延时

    // 等待延时标志位被设置
    while (delay_flag == 0);

    // 停止定时器 (可选,如果只需要单次延时)
    TIM_Cmd(TIM2, DISABLE);
    TIM_ITConfig(TIM2, TIM_IT_Update, DISABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, DISABLE);
}

// 示例:主函数 (包含 GPIO 配置作为演示)
void GPIO_Configuration(void) {
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能 GPIOA 时钟

    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;          // 选择 GPIOA 的引脚 0
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;    // 设置为推挽输出模式
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  // 设置传输速度
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    GPIO_SetBits(GPIOA, GPIO_Pin_0);                   // 初始状态设置为高电平
}

int main(void) {
    GPIO_Configuration(); // 配置 GPIO

    while (1) {
        GPIO_ResetBits(GPIOA, GPIO_Pin_0); // 拉低电平,例如点亮 LED
        delay_ms(500);
        GPIO_SetBits(GPIOA, GPIO_Pin_0);   // 拉高电平,例如熄灭 LED
        delay_ms(1000);
    }
}

代码补充说明:

  • GPIO 配置:main() 函数中,我添加了一个 GPIO_Configuration() 函数作为演示,配置了 GPIOA 的引脚 0 为推挽输出,可以用于连接 LED 等外设,更贴合您提供的链接中实验的做法。
  • 主函数示例: main() 函数中展示了如何使用 delay_ms() 函数来控制 GPIO 引脚的输出状态,实现简单的闪烁效果。
  • 总的来说,当使用 delay_ms() 时,程序会配置一个硬件定时器来计时,并在计时结束后通过中断的方式通知程序,程序会一直等待这个通知的到来,从而实现延时的效果。
相关推荐
坏柠3 小时前
深入浅出SPI通信协议与STM32实战应用(W25Q128驱动)(实战部分)
stm32·单片机·嵌入式硬件
硬件进化论5 小时前
硬件工程师面试问题(四):车载MCU面试问题与详解
单片机·嵌入式硬件·数码相机·电视盒子·智能音箱·智能手表
nuannuan2311a8 小时前
CR03AM-12-ASEMI智能家居专用CR03AM-12
单片机
蓝桥_吹雪9 小时前
【备赛】蓝桥杯实现多个LED联合控制
笔记·stm32·单片机·蓝桥杯
sewinger10 小时前
STM32外部中断EXTI:原理、结构与应用
stm32·单片机·嵌入式硬件·iot
触角0101000110 小时前
STM32看门狗原理与应用详解:独立看门狗 vs 窗口看门狗(上) | 零基础入门STM32第九十四步
驱动开发·stm32·单片机·嵌入式硬件·物联网
LaoZhangGong12310 小时前
char表示有符号,还是无符号
经验分享·stm32·单片机·嵌入式硬件
多多*10 小时前
2024第十五届蓝桥杯大赛软件赛省赛Java大学B组 报数游戏 类斐波那契循环数 分布式队列 食堂 最优分组 星际旅行 LITS游戏 拼十字
java·linux·stm32·单片机·嵌入式硬件·spring·eclipse
菜鸟江多多11 小时前
32x32热成像高斯滤波图像处理
图像处理·单片机·算法