MCU中的定时器

第0章 定时器的公式

公式中的时间表示的是定时器的时间周期,即定时器从计数开始到溢出的时间间隔。

预分频值设置的越大,表示计数越慢,溢出越慢。

预分频值设置的越小,表示计数越快,溢出越快。精度变高

0.1 stm32F407的定时器3产生2s钟的更新中断

#include "stm32f4xx.h"

void TIM3_Int_Init(uint16_t prescaler, uint16_t period)
{
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    // 1. 使能 TIM3 时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

    // 2. 配置 TIM3 基本参数
    TIM_TimeBaseStructure.TIM_Period = period - 1;                    // 自动重装载值
    TIM_TimeBaseStructure.TIM_Prescaler = prescaler - 1;              // 预分频值
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;           // 时钟分频
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;       // 计数模式:向上计数
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);

    // 3. 使能 TIM3 更新中断
    TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);

    // 4. 配置 NVIC(中断优先级)
    NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    // 5. 使能 TIM3
    TIM_Cmd(TIM3, ENABLE);
}

// TIM3 中断处理函数
void TIM3_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
    {
        TIM_ClearITPendingBit(TIM3, TIM_IT_Update); // 清除中断标志

        // 这里写 2s 触发的任务
        GPIO_ToggleBits(GPIOD, GPIO_Pin_12); // 切换 LED 状态
    }
}
// 初始化 TIM3,2s 触发
TIM3_Int_Init(8400, 20000);

第一章 定时器的应用场景

  1. 定时功能----------------延时功能
  2. 输出比较功能-----------输出PWM波
  3. 输入捕获功能-----------计算输入波形的周期

第二章 定时器的原理

2.1 定时器的计数原理

 

1. 定时器的本质是一个计数器;

2. 计数器是对输入的系统频率信号进行计数;

3. 每来一个周期的信号,计数器的cnt 加一。如果周期T表示为1s,来三个周期就表示3s时间到达;

如果系统时钟频率是120MHz,不进行预分频直接给计数器计数,那么周期为T = (1/120)us。计数器记录120次,表示t = 120 * T us = 1us时间到;计数器记录240次,表示t = 240 * T us = 2us时间到;

2.2 定时器的自动重装载器

一方面假设系统时钟频率是120MHz,不进行预分频直接给计数器计数,那么周期为T = (1/120)us。另一方面设置CAR寄存器的值为12000。每当计数器来一个脉冲(来一个周期T的信号),计数器的的值会自加1操作,并且将自加后的结果与CAR寄存器的值进行比较。

比较之后,如果发现没有到达设定的CAR寄存器值,则继续来脉冲累加。

如果计数器累加到12000次,达到了设定的CAR寄存器值,此时表示t = 12000 * T us = 100us时间到,会产生一个中断(进入中断函数)。

2.3 定时器的预分频器

假定预分频器的值设置为120,总线给定时器的时钟频率为120MHZ;

在定时器的预分频器(Prescaler)设置为 120 的情况下,输入时钟频率为 120 MHz 时,预分频器会对输入的时钟信号进行分频。具体分频过程如下:

  1. 预分频器的功能:它的作用是将高频的输入时钟信号降低到适合计数器工作的频率。
  2. 预分频器设置为 120:意味着预分频器需要 120 个输入脉冲 才会输出 1 个脉冲
  3. 输出到计数器的频率:输入时钟频率为 120 MHz,经过预分频器分频后,输出频率为: 

f = 120MHz / 120 = 1 MHz

因此,输入到预分频器的 120 个脉冲,会转换成输出的 1 个脉冲,然后这个脉冲才会送到定时器的计数器。

总结:

  • 预分频器 将输入的 120 MHz 时钟分频为 1 MHz。
  • 计数器 以 1 MHz 的频率进行计数。

那么输入120个脉冲到预分频器后,才产生1个脉冲给计数器

2.4 定时器的硬件结构

2.5 定时器的时钟树

2.6 通用定时器和高级定时器

  1. 最直观的区别是通道个数;

第三章 定时器输出PWM

3.1 应用举例

3.2 输出比较功能

3.2.1 GD32

  1.  如果T = 1us,周期为1us。每来一个脉冲计数器加一,并且将累计后的值CNT和输出比较值比较。如果小于输出比较值,输出高电平;如果大于输出比较值,输出低电平;
  2. 如果累加值累加到自动重装载值后,就会触发自动重载操作,将计数器中的累加值CNT置为为0,重新开始累加。
  • 定时器配置

3.2.2 stm32F4

注意:

  1. PF9口本来是一个普通的GPIO口,现在需要配置为定时器14的通道1,所以需要将IO口设置为复用功能;
  2. 配置定时器14输出100HZ的频率(周期为10ms),

3.3 输入捕获功能----测量信号的周期频率或脉冲宽度

3.3.1 输入捕获的原理一

  1. 预分配一直给计数器周期性的脉冲,并且假定周期为T = 1us;
  2. 计数器每来一个脉冲,计数器中的CNT累加1。
  3. 突然某一个时刻,GPIO口的电平发生跳变(假定是一个上升沿),此时会触发捕获事件以及和一个中断事件。
  4. 捕获事件将计数器中的值保存到输入捕获寄存器中保存着;
  5. 中断事件(中断函数中)将输入捕获寄存器中的值取出等待使用,假定为V1;
  6. 突然某一个时刻,GPIO口的电平又发生跳变(假定是一个上升沿),此时会触发捕获事件以及和一个中断事件。
  7. 捕获事件将计数器中的值保存到输入捕获寄存器中保存着;
  8. 中断事件(中断函数中)将输入捕获寄存器中的值取出,假定为V2;

中断中将V2 - V1得到一个差值V3;V3 * T = V3 * 1us的结果就是输入的周期频率

3.3.2 输入捕获的原理二

  1. 预分配一直给计数器周期性的脉冲,并且假定周期为T = 1us;
  2. 计数器每来一个脉冲,计数器中的CNT累加1。
  3. 突然某一个时刻,GPIO口的电平发生跳变(假定是一个上升沿),此时会触发捕获事件以及和一个中断事件。
  4. 捕获事件将计数器中的值保存到输入捕获寄存器中保存着;
  5. 中断事件(中断函数中)将计数器中的值第一次置位为0;
  6. 突然某一个时刻,GPIO口的电平又发生跳变(假定是一个上升沿),此时会触发捕获事件以及和一个中断事件。
  7. 捕获事件将计数器中的值保存到输入捕获寄存器中保存着;
  8. 中断事件(中断函数中)将计数器中的值读取出来为V1;

中断中将V1 * T = V1 * 1us的结果就是输入捕获的周期信号的频率。

3.3.3 代码示例;

project/

├── main.c // 主函数入口

├── tim_capture.c // 定时器输入捕获功能实现

├── tim_capture.h // 定时器输入捕获功能声明

 1. 头文件 (tim_capture.h)

#ifndef TIM_CAPTURE_H
#define TIM_CAPTURE_H

#include "stm32f10x.h"

// 捕获功能初始化函数
void TIM2_InputCapture_Init(void);

// 获取捕获的周期(单位: 微秒)
uint32_t TIM2_GetPeriod(void);

#endif // TIM_CAPTURE_H

2. 源文件 (tim_capture.c)

#include "tim_capture.h"

// 全局变量,存储捕获结果
static volatile uint16_t capture1 = 0, capture2 = 0; // 捕获值
static volatile uint32_t period = 0;                // 信号周期
static volatile uint8_t captureFlag = 0;            // 捕获完成标志

// 定时器输入捕获初始化
void TIM2_InputCapture_Init(void) {
    // 1. 启用时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // TIM2 时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // GPIOA 时钟

    // 2. 配置 GPIO
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;             // PA0: TIM2_CH1
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    // 3. 配置定时器
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
    TIM_TimeBaseStruct.TIM_Period = 0xFFFF; // 最大计数值
    TIM_TimeBaseStruct.TIM_Prescaler = 72 - 1; // 定时器分频为 72MHz / 72 = 1MHz
    TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStruct);

    // 4. 配置输入捕获
    TIM_ICInitTypeDef TIM_ICInitStruct;
    TIM_ICInitStruct.TIM_Channel = TIM_Channel_1;         // 捕获通道 1
    TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising; // 捕获上升沿
    TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI; // 直接输入
    TIM_ICInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1;    // 无分频
    TIM_ICInitStruct.TIM_ICFilter = 0x0;                 // 无滤波
    TIM_ICInit(TIM2, &TIM_ICInitStruct);

    // 5. 使能中断
    TIM_ITConfig(TIM2, TIM_IT_CC1, ENABLE); // 使能捕获比较中断
    NVIC_InitTypeDef NVIC_InitStruct;
    NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);

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

// 获取捕获周期
uint32_t TIM2_GetPeriod(void) {
    return period; // 返回测量的信号周期
}

// 定时器中断处理函数
void TIM2_IRQHandler(void) {
    if (TIM_GetITStatus(TIM2, TIM_IT_CC1) != RESET) {
        TIM_ClearITPendingBit(TIM2, TIM_IT_CC1); // 清除中断标志

        if (captureFlag == 0) {
            capture1 = TIM_GetCapture1(TIM2); // 第一次捕获
            captureFlag = 1;
        } else if (captureFlag == 1) {
            capture2 = TIM_GetCapture1(TIM2); // 第二次捕获
            if (capture2 > capture1) {
                period = capture2 - capture1; // 正常情况
            } else {
                period = (0xFFFF - capture1 + capture2 + 1); // 计数器溢出情况
            }
            captureFlag = 0; // 重置标志
        }
    }
}

3. 主函数 (main.c)

#include "stm32f10x.h"
#include "tim_capture.h"

int main(void) {
    SystemInit();                // 初始化系统时钟
    TIM2_InputCapture_Init();    // 初始化 TIM2 输入捕获功能

    while (1) {
        // 检查是否已经测量到信号周期
        uint32_t signalPeriod = TIM2_GetPeriod();
        if (signalPeriod != 0) {
            // 计算信号频率 (Hz)
            float frequency = 1000000.0f / signalPeriod; // 1 MHz 时钟基准
            // 用户可添加进一步处理,如显示频率或其他逻辑
        }
    }
}

触发中断的条件

根据代码和 STM32 的定时器捕获功能,以下条件会触发这个中断:

  1. TIM2 的输入捕获通道 1(CC1)检测到信号边沿跳变

    • 在定时器配置时,将 CC1 通道配置为输入捕获模式,并设置边沿触发类型(上升沿、下降沿或双边沿)。
    • 例如,信号从低到高(上升沿)或高到低(下降沿)跳变时,捕获触发。
  2. 中断使能

    • 必须启用 TIM2 的 CC1 通道中断:

      TIM_ITConfig(TIM2, TIM_IT_CC1, ENABLE);

  3. 中断标志位被置位

    • 捕获事件发生时,定时器的 CC1 中断标志位(TIM_IT_CC1)会被置位,随后触发 TIM2_IRQHandler

所以输入信号的周期为:

T = capture2 - capture1; // 周期的单位是微秒

所以输入信号的频率frequency (Hz)        f = (1 / T ) * 10^6 Hz

3.4 定时器作为延时函数

  1. 假定总线给定时器的时钟频率为84MHz时,并且设置预分频器的预分频值为84;那么产生的时钟频率f = 1MHz。
  2. T = 1us,如果设置自动重装载值为65535,并且设置定时器为向上计数。
  3. 计数器来一个周期为1us的脉冲,计时器加1;
#include "drv_timer1.h"

void InitDelayTime(void) {
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

    /* 定时器1时钟使能 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);

    /* 自动重装载寄存器周期的值 */
    TIM_TimeBaseStructure.TIM_Period = 0XFFFF - 1;  // 设置定时器溢出值(65535)

    /* 预分频器配置,将定时器时钟分频为1 MHz (每微秒1个周期) */
    TIM_TimeBaseStructure.TIM_Prescaler = ((SystemCoreClock / 2) / 1000000) -1;  // 

    /* 时钟分割 */
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;

    /* 向上计数 */
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;

    /* 初始化定时器 */
    TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);

    /* 先关闭定时器 */
    TIM_Cmd(TIM1, DISABLE);
}

/* 等待一段时间,单位us */
void hwDelayTimeus(DWORD dwDelay) {
    /* 确保延迟时间不超过16位计数器的范围 */
    DWORD dwTime = dwDelay & 0xFFFF;

    /* 使能定时器 */
    TIM_Cmd(TIM1, ENABLE);

    /* 清除定时器更新标志 */
    TIM_ClearFlag(TIM1, TIM_FLAG_Update);

    /* 设置定时器初始计数器值 */
    TIM_SetCounter(TIM1, 0);

    /* 等待定时器溢出(即计数器到达TIM_Period) */
    while (TIM_GetCounter(TIM1) < dwTime);

    /* 禁用定时器 */
    TIM_Cmd(TIM1, DISABLE);
}

/* 等待一段时间,单位ms */
void hwDelayTimems(DWORD dwDelay) {
    while (dwDelay--) {
        hwDelayTimeus(1000);  // 每次延时1毫秒(1000微秒)
    }
}

1. 定时器初始化时,只是配置了电气属性,并没有打开定时器,需要用的时候才打开。这样不耗电。

2. 需要用的时候设置定时器的开始计数值为0;

3. 获取计数器的值做比较,实现延时功能;

4. 时间到了就关闭定时器。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值