第25章 基本定时器定时实验

第二十五章 基本定时器定时实验

1. 硬件设计

本实验利用基本定时器 TIM6/7 定时 1s,1s 时间到 LED 翻转一次。基本定时器是单片机内部的资源, 没有外部 IO,不需要接外部电路,现只需要一个 LED 即可。

2. 软件设计

2.1 编程目标

  1. 开定时器时钟 TIMx_CLK, x[6,7];

  2. 初始化时基初始化结构体;

  3. 使能 TIMx, x[6,7] update 中断;

  4. 打开定时器;

  5. 编写中断服务程序

通用定时器和高级定时器的定时编程要点跟基本定时器差不多,只是还要再选择下计数器的计数模式, 是向上还是向下。因为基本定时器只能向上计数,且没有配置计数模式的寄存器,默认是向上。

2.2 代码分析

  • 基本定时器宏定义
#define BASIC_TIM6 // 如果使用TIM7,注释掉这个宏即可

#ifdef  BASIC_TIM6 // 使用基本定时器TIM6
#define            BASIC_TIM                   TIM6                   // 选择使用的定时器--TIM6
#define            BASIC_TIM_APBxClock_FUN     RCC_APB1PeriphClockCmd // 使能定时器时钟--APB1
#define            BASIC_TIM_CLK               RCC_APB1Periph_TIM6    // 定时器时钟--TIM6
#define            BASIC_TIM_Period            1000-1                 // 定时器周期--1000-1
#define            BASIC_TIM_Prescaler         71                     // 定时器分频器--71
#define            BASIC_TIM_IRQ               TIM6_IRQn              // 定时器中断向量号--TIM6_IRQn
#define            BASIC_TIM_IRQHandler        TIM6_IRQHandler        // 定时器中断服务程序--TIM6_IRQHandler

#else  // 使用基本定时器TIM7
#define            BASIC_TIM                   TIM7
#define            BASIC_TIM_APBxClock_FUN     RCC_APB1PeriphClockCmd
#define            BASIC_TIM_CLK               RCC_APB1Periph_TIM7
#define            BASIC_TIM_Period            1000-1
#define            BASIC_TIM_Prescaler         71
#define            BASIC_TIM_IRQ               TIM7_IRQn
#define            BASIC_TIM_IRQHandler        TIM7_IRQHandler

#endif

基本定时器有 TIM6 和 TIM7,我们可以有选择的使用,为了提高代码的可移植性,我们把当需要修改定时器时需要修改的代码定义成宏, 默认使用的是定时器 6,如果想修改成定时器 7,只需要把宏 BASIC_TIM6 注释掉即可。

  • 基本定时器配置
// 基本定时器模式配置
static void BASIC_TIM_Mode_Config(void)
{
    // 1.定义结构体
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;

    // 2.开启定时器时钟,即内部时钟CK_INT=72M
    BASIC_TIM_APBxClock_FUN(BASIC_TIM_CLK, ENABLE);

    // 3.自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
    TIM_TimeBaseStructure.TIM_Period = BASIC_TIM_Period;    

    // 4.时钟预分频数为
    TIM_TimeBaseStructure.TIM_Prescaler= BASIC_TIM_Prescaler;

    // 时钟分频因子 ,基本定时器没有,不用管
    //TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;

    // 计数器计数模式,基本定时器只能向上计数,没有计数模式的设置
    //TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; 

    // 重复计数器的值,基本定时器没有,不用管
    //TIM_TimeBaseStructure.TIM_RepetitionCounter=0;

    // 5.初始化定时器
    TIM_TimeBaseInit(BASIC_TIM, &TIM_TimeBaseStructure);

    // 6.清除计数器中断标志位
    TIM_ClearFlag(BASIC_TIM, TIM_FLAG_Update);

    // 7.开启计数器中断
    TIM_ITConfig(BASIC_TIM, TIM_IT_Update, ENABLE);

    // 8.使能计数器
    TIM_Cmd(BASIC_TIM, ENABLE);    
}

我们把定时器设置自动重装载寄存器 ARR 的值为 1000,设置时钟预分频器为 71, 则驱动计数器的时钟:CK_CNT = CK_INT / (71+1)=1M, 则计数器计数一次的时间等于:1/CK_CNT=1us,当计数器计数到 ARR 的值 1000时, 产生一次中断,则中断一次的时间为:1/CK_CNT*ARR=1ms。


这里出现了几个新的库函数,我们来解释一下:

1. TIM_TimeBaseInit

原型:

void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_InitStruct);

功能:

  • 初始化指定定时器的时间基准(计数周期和预分频器)。
  • TIMx 是要配置的定时器外设,例如 TIM2、TIM3 等。
  • TIM_InitStruct 是一个 TIM_TimeBaseInitTypeDef 结构体,包含了计数器的各种参数,如计数周期(TIM_Period)、预分频器(TIM_Prescaler)、时钟分频(TIM_ClockDivision)和计数模式(TIM_CounterMode

结构体定义:

typedef struct
{
    uint16_t TIM_Period;            // 自动重载寄存器的值
    uint16_t TIM_Prescaler;          // 预分频器的值
    uint16_t TIM_ClockDivision;      // 时钟分频
    uint16_t TIM_CounterMode;        // 计数模式
} TIM_TimeBaseInitTypeDef;

2. TIM_ClearFlag

原型:

void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);

功能:

  • 清除指定定时器的标志位。TIM_FLAG 是标志位参数,例如 TIM_FLAG_Update
  • 该函数用于重置定时器的状态标志,以便在下一次事件发生时能够重新捕捉。

标志位定义:

#define TIM_FLAG_Update ((uint16_t)0x0001)  // 更新中断标志位

3. TIM_ITConfig

原型:

void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);

功能:

  • 配置定时器的中断。TIM_IT 是中断源,例如 TIM_IT_Update
  • NewState 可以是 ENABLE 或 DISABLE,用于启用或禁用中断。

参数说明:

  • TIMx:要配置的定时器外设。
  • TIM_IT:中断源。
  • NewState:设置中断状态,ENABLE 启用,DISABLE 禁用。

4. TIM_Cmd

原型:

void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);

功能:

  • 启动或停止定时器。NewState 可以是 ENABLE 或 DISABLE
  • 启用定时器使其开始计数,或禁用定时器停止计数。

参数说明:

  • TIMx:要配置的定时器外设。
  • NewState:设置定时器状态,ENABLE 启用,DISABLE 禁用。

在初始化定时器的时候,我们定义了一个结构体:TIM_TimeBaseInitTypeDef, TIM_TimeBaseInitTypeDef 结构体里面有 5 个成员, TIM6 和 TIM7 的寄存器里面只有TIM_Prescaler 和 TIM_Period,另外三个成员基本定时器是没有的, 所以使用TIM6 和TIM7 的时候只需初始化这两个成员即可, 另外三个成员是通用定时器和高级定时器才有,具体说明如下:

typedef struct {
    TIM_Prescaler            // 都有
    TIM_CounterMode          // TIMx,x[6,7]没有,其他都有
    TIM_Period               // 都有
    TIM_ClockDivision        // TIMx,x[6,7]没有,其他都有
    TIM_RepetitionCounter    // TIMx,x[1,8,15,16,17]才有
} TIM_TimeBaseInitTypeDef;

其中 TIM15/16/17 只存在与互联型产品中,在 F1 大/中/小容量型号中没有。

  • 定时器中断优先级配置
// 中断优先级配置
void BASIC_TIM_NVIC_Config(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;
    // 设置中断组为0
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
    // 设置中断来源
    NVIC_InitStructure.NVIC_IRQChannel = BASIC_TIM_IRQ ;
    // 设置主优先级为 0
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    // 设置抢占优先级为3
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

我们设置中断分组为 0,主优先级为 0,抢占优先级为 3。

  • 定时器中断服务程序
void  BASIC_TIM_IRQHandler (void)
{
    if(TIM_GetITStatus(BASIC_TIM, TIM_IT_Update) != RESET ) {
        time++;
        TIM_ClearITPendingBit(BASIC_TIM , TIM_FLAG_Update);
    }
}

定时器中断一次的时间是 1ms,我们定义一个全局变量 time,每当进一次中断的时候,让time 来记录进入中断的次数。 如果我们想实现一个 1s 的定时,我们只需要判断 time 是否等于 1000 即可,1000 个 1ms 就是 1s。 然后把 time 清0,重新计数,以此循环往复。在中断服务程序的最后,要把相应的中断标志位清除掉,切记。


解释一下中断函数中出现的新库函数:

1. TIM_GetITStatus

原型:

ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);

功能:

  • 检查指定定时器的特定中断标志位是否被置位。
  • TIMx 是指定的定时器外设(例如 TIM2TIM3 等)。
  • TIM_IT 是要检查的中断源,例如 TIM_IT_Update,表示更新中断。

返回值:

  • 如果指定的中断标志位被置位,返回 SET
  • 否则,返回 RESET

2. TIM_ClearITPendingBit

原型:

void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);

功能:

  • 清除指定定时器的特定中断挂起标志位。
  • TIMx 是指定的定时器外设。
  • TIM_IT 是要清除的中断源,例如 TIM_IT_Update,表示更新中断。

作用:

  • 在中断服务程序中调用该函数可以将中断挂起标志位清除,从而避免重复触发同一中断。

  • 主函数
// 基本定时器TIMx,x[6,7]定时应用
#include "stm32f10x.h"
#include "bsp_led.h"
#include "bsp_TiMbase.h"

volatile uint32_t time = 0; // ms 计时变量 

int main(void)
{
    LED_GPIO_Config();    
    BASIC_TIM_Init();

  while(1)
  {
    if(time == 1000) // 1000 * 1 ms = 1s 时间到 
    {
      time = 0;
      LED1_TOGGLE; 
    }        
  }
}

主函数做一些必须的初始化,然后在一个死循环中不断的判断 time 的值,time的值在定时器中断改变, 每加一次表示定时器过了 1ms,当 time 等于 1000 时,1s时间到,LED1翻转一次,并把 time 清 0。

3. 小结

TIM基本定时器还算简单,合适的配置就行,下面我们来回顾一下基本流程:

1. 配置基本定时器

1.1. 使能定时器时钟

在开始配置定时器之前,你需要确保定时器的时钟已经被启用。这通常通过配置系统时钟(RCC)来完成。

// 使能定时器时钟,例如使能 TIM2
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

1.2. 配置定时器基础参数

你需要设置定时器的预分频器、计数周期等参数。预分频器用于分频定时器时钟,计数周期用于设定定时器溢出的时间间隔。

TIM_TimeBaseInitTypeDef TIM_InitStructure;

// 配置定时器基础参数
TIM_InitStructure.TIM_Period = 9999; // 计数周期值(计数器溢出值)
TIM_InitStructure.TIM_Prescaler = 71; // 定时器时钟预分频值
TIM_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 时钟分割
TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up; // 计数器向上计数

// 初始化定时器
TIM_TimeBaseInit(TIM2, &TIM_InitStructure);

2. 配置中断

2.1. 使能定时器更新中断

启用定时器的更新中断,使得定时器溢出时可以触发中断请求

// 使能定时器更新中断
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);

2.2. 配置中断优先级和使能中断

在 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
NVIC_Init(&NVIC_InitStructure);

3. 启动定时器

最后,你需要启动定时器,使其开始计数。

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

4. 编写中断服务函数

在中断服务程序中处理定时器溢出的事件。你需要确保在中断服务程序中清除中断挂起标志位。

void TIM2_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
        // 执行定时器溢出后的任务
        // 例如:改变 LED 状态、更新计时器等
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    }
}

2024.9.10 第一次修订,后期不再维护

posted @ 2024-09-10 20:32  hazy1k  阅读(9)  评论(0编辑  收藏  举报