江科大STM32(3):定时器(1)定时器的基本定时功能

发布时间 2023-11-27 21:06:39作者: xsgcumt

1.TIM简介

  • TIM(Timer)定时器
  • 定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断
  • 16位计数器(59.65s的计时,1/72M/65536/65536)、预分频器(对计数器时钟进行分频,让计数更灵活)、自动重装寄存器(计数的目标值,记N个时钟后申请中断)的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时
  • 不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
  • 根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型

2. 定时器类型

  • STM32F103C8T6定时器资源:TIM1、TIM2、TIM3、TIM4

 2.1 基本定时器

 

  • 下面三个叫做时基单元,预分频器之前,连接的就是基准计数时钟的输入(内部时钟CK_INT,来源自RCC的TIMxCLK,频率值一般都是系统的主频72MHz),预分频器可以对72MHz进行分频(1就是2分频),计数器可以对预分频后的计数时钟进行计数(每来一个上升沿,计数器就加一,0~65535),计数器的值会在计时过程中会不断地自增运行,当自增运行到目标值时,产生中断,那就完成了定时的任务,自动重装定时器存的就是写入的计数目标,在运行的过程中,计数值不断自增,自动重装值是固定的目标,当计数值等于自动重装值时,也就是计时时间到了,就会产生一个中断信号,并清零计数器,计数器自动开始下一次的计数计时
  • 采用向上计数模式。UI:更新中断 U:更新事件

2.2 通用定时器

  • 预分频器对时钟进行预分频,计数器自增计数,当计数值到自动重装值时,计数值清零同时产生更新终端和更新事件
  • 通用定时器和高级定时器还支持向下计数模式和中央对齐模式。

  向下计数模式就是从重装值开始,向下自减,减到0之后,回到重装值同时申请中断,然后继续下一轮,依次循环;

  中央对齐的计数模式,就是从0开始,先向上自增,计到重装值,申请中断,然后再向下自减,减到0,再申请中断,然后继续下一轮,依次循环。

  • 上面一部分就是内外时钟源选择和主从触发模式的结构

  内外时钟源选择:不止系统频率72MHZ,还可以选择外部时钟,

  TIMx_ETR引脚上的外部时钟(PA0),可以接一个外部方波时钟,配置一下内部的极性选择、边沿检测和预分频器电路,再配置输入滤波电路,最后,滤波后的信号,兵分两路,上面一路ETRF进入触发控制器,紧跟着就可以选择作为时基单元的时钟【外部时钟模式2】; 下面这里还有一路可以提供时钟,就是TRGI(Trigger In),主要是用作触发输入来使用的【外部时钟模式1】。

  外部时钟模式1:

  第一个,就是ETR引脚的信号;

  第二个,就是ITR信号,这一部分的时钟信号是来自其他定时器的,从右边可以看出,这个主模式的输出TRGO可以通向其他定时器,那通向其他定时器的时候,就接到了其他定时器的ITR引脚上来了,ITRO到ITR3分别来自其他4个定时器的TRGO输出

【比如可以先初始化TIM3,然后使用主模式把它的更新事件映射到TRGO上,接着再初始化TIM2,这里选择ITR2,对应的就是TIM3的TRGO,然后后面再选择时钟为外部时钟模式1,这样TIM3的更新事件就可以驱动TM2的时基单元,也就实现了定时器的级联】;

  第三个,TI1F_ED,连接的是这里输入捕获单元的CH1引脚,也就是从CH1引脚获得时钟,这里后缀加一个ED(Edge)就是边沿的意思(也就是通过这一路输入的时钟,上升沿和下降沿均有效);

  最后,这个时钟还能通过TI1FP1和TI2FP2获得,其中TI1FP1是连接到了CH1引脚的时钟,TI1FP2是连接到了CH2引脚的时钟。

总结一下就是,外部时钟模式1的输入可以是ETR引脚、其他定时器、CH1引脚的边沿、CH1引脚和CH2引脚

  • 下面这一部分主要包含了两块电路,

  左边这一块是输入捕获电路,总共有四个通道,分别对应CH1到CH4的引脚,可以用于测输入方波的频率等

  右边这一块是输出比较电路,总共有四个通道,分别对应CH1到CH4的引脚,可以用于输出PWM波形,驱动电机

  中间这个寄存器是捕获/比较寄存器,是输入捕获和输出比较电路共用的,因为输入捕获和输出比较不能同时使用,所以这里的寄存器是共用的,引脚也是共用的

2.3 高级定时器

 

 

 3.定时器

3.1定时中断基本结构

最重要的PSC(Prescaler) 预分频器、CNT (Counter)计数器、ARR(AutoReloadRegister)自动重装器这三个寄存器构成的时基单元,

  • 下面这里是运行控制,就是控制寄存器的一些位(比如启动停止、向上或向下计数等等),
  • 左边是为时基单元提供时钟的部分,这里可以选择RCC提供的内部时钟,也可以选择ETR引脚提供的外部时钟模式2,当然还可以选择这里的触发输入当做外部时钟,即外部时钟模式1,对应的有ETR外部时钟、ITRx其他定时器、T输入捕获通道,这些就是定时器的所有可选的时钟源了,
  • 右边这里,就是计时时间到,产生更新中断后的信号去向(如果是高级定时器的话,还会多一个重复计数器),那这里中断信号会先在状态寄存器里置一个中断标志位,这个标志位会通过中断输出控制,到NMIC申请中断,这个中断输出控制就是一个中断输出的允许位,如果要需要某个中断,就记得允许一下

初始化定时器

第一步:RCC开启时钟;

第二步:选择时基单元的时钟源。对于定时中断,我们就选择内部时钟源;

第三步:配置时基单元。包括预分频器、自动重装器、计数模式等等;

第四步:配置输出中断控制,允许更新中断输出到NVIC;

第五步:配置NVIC,在NVIC中打开定时器中断的通道,并分配一个优先级;

第六步:运行控制

 

3.2 预分频时序

 

  • CK_PSC,预分频器的输入时钟,选内部时钟的话一般是72MHz
  • CNT_EN,计数器使能,高电平计数器正常运行,低电平计数器停止
  • 定时器时钟CK_CNT,既是预分频器的时钟输出,也是计数器的时钟输入

开始时,计数器未使能,计数器时钟不运行,然后使能后,前半段,预分频器系数为1,计数器的时钟等于预分频器前的时钟,后半段,预分频器系数变为2,计数器的时钟就也变为预分频器前时钟的一半了,

  • 在计数器时钟的驱动下,下面的计数器寄存器也跟随时钟的上升沿不断自增,在中间的这个位置FC之后,计数值变为0了(可以推断出ARR自动重装值就是FC),当计数值计到和重装值相等,并且下一个时钟来临时,数值才清零
  • 同时,下面这里产生一个更新事件,这就是一个计数周期的工作流程
  • 后三行是预分频存器的一种缓冲机制,也就是这个预分频寄存器实际上是有两个,一个是预分频控制寄存器,供我们读写用的,它并不直接决定分频系数,
  • 另外还有一个预分频缓冲器,缓冲寄存器才是真正起作用的寄存器,【比如我们在某个时刻,把预分频寄存器由0改成了1,如果在此时立刻改变时钟的分频系数,那么就会导致这里,在一个计数周期内,前半部分和后半部分的频率不一样,计数计到一半,计数频率突然就会改变了,改变了分频值,这个变化并不会立刻生效,而是会等到本次计数周期结束时,产生了更新事件,预分频奇存器的值才会被传递到缓冲寄存器里面去,才会生效】所以即使在计数中途改变了预分频值,计数频率仍然会保持为原来的频率,直到本轮计数完成,在下一轮计数时,改变后的分频值才会起作用,【不会被打断】
  • 预分频计数器内部实际上也是靠计数来分频的,当预分频值为0时,计数器就一直为0,直接输出原频率,当预分频值为1时,计数器就0、1、0、1、0、1这样计数,在回到0的时候,输出一个脉冲,
  • 这样输出频率就是输入频率的2分频,预分频器的值和实际的分频系数之间有一个数的偏移
  • 计数器计数频率:CK_CNT = CK_PSC(72MHz) / (PSC + 1)

3.3 计数器时序

  • CK_INT,内部时钟72MHz
  • CNT_EN,计数器使能,高电平计数器正常运行,低电平计数器停止
  • 计数器时钟CK_CNT ,因为分频系数为2,所以这个频率是CK_INT除2,
  • 计数器寄存器在这个时钟每个上升沿自增,当增到0036的时候,发生溢出,那计到36之后,再来一个上升沿,计数器清零
  • 计数器溢出
  • 产生一个更新事件脉冲
  • 另外还会置一个更新中断标志位UIF,这个标志位只要置1了,就会去申请中断,然后中断响应后,需要在中断程序中手动清零
  • 计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)

= CK_PSC / (PSC + 1) / (ARR + 1)【如果想算溢出时间,只需再取个倒数】

3.4 计数器无预装时序

3.5 计数器有预装时序

3.6 RCC时钟树

参考:https://blog.csdn.net/m0_49941630/article/details/132689377

4.实操

 TIM库函数

/*时基单元*/
void TIM_DeInit(TIM_TypeDef* TIMx);
//恢复缺省配置       
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
//时基单元初始化  
 
void TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
//把结构体变量赋默认值
 
/*运行控制*/
void TIM_Cmd(TIM_TypeDef* TIMx(选择定时器), FunctionalState NewState(新的状态  使能/失能));
//使能计数器 
 
/*中断输出控制*/
void TIM_ITConfig(TIM_TypeDef* TIMx(选择定时器), uint16_t TIM_IT(选择配置哪个终端输出), FunctionalState NewState(新的状态  使能/失能));
//使能中断输出信号
 
/*时基单元的时钟选择部分*/
void TIM_InternalClockConfig(TIM_TypeDef* TIMx);
//选择RCC内部时钟
void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx(选择定时器), uint16_t TIM_InputTriggerSource(选择要接入哪个其他的定时器));
//选择ITRx其他定时器
void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx(选择定时器), uint16_t TIM_TIxExternalCLKSource(选择具体的某个引脚),uint16_t TIM_ICPolarity(输入的极性), uint16_t ICFilter(输入的滤波器));
//选择TIx捕获通道的时钟
void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler(外部触发预分频器 对ETR的外部时钟再提前做一个分频), uint16_t TIM_ExtTRGPolarity(输入的极性),uint16_t ExtTRGFilter(输入的滤波器));
//选择ETR外部时钟模式1输入的时钟
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);
//选择ETR外部时钟模式2输入的时钟
void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);
//单独配置ETR引脚的预分频器、极性、滤波器
 
void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler(要写入的预分频值), uint16_t TIM_PSCReloadMode(写入的模式));
//单独写预分频值
void TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode);
//改变计数器的计数模式
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);
//自动重装器预装功能配置
void TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter);
//给计数器写入一个值
void TIM_SetAutoreload(TIM_TypeDef* TIMx, uint16_t Autoreload);
//给自动重装器写入一个值
uint16_t TIM_GetCounter(TIM_TypeDef* TIMx);
//获取当前计数器的值
uint16_t TIM_GetPrescaler(TIM_TypeDef* TIMx);
//获取当前预分频器的值

初始化定时器

第一步:RCC开启时钟;

第二步:选择时基单元的时钟源。对于定时中断,我们就选择内部时钟源;

第三步:配置时基单元。包括预分频器、自动重装器、计数模式等等;

第四步:配置输出中断控制,允许更新中断输出到NVIC;

第五步:配置NVIC,在NVIC中打开定时器中断的通道,并分配一个优先级;

第六步:运行控制

4.1 定时器定时中断

4.1.1 实验现象

 

4.1.2 代码

Timer.c

#include "stm32f10x.h"                  // Device header

void Timer_Init(void)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);//第一步:RCC开启时钟
    
    TIM_InternalClockConfig(TIM2);//第二步:选择时基单元的时钟源
    
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//不分频

    //指定时钟分频,在一个固定的时钟频率进行采样,如果连续N个采样点都为相同的电平,那就代表输入信号稳定了,就把这个值输出出去,反之说明信号抖动
    //采样频率f,可以是由内部时钟直接而来,也可以是由内部时钟加一个时钟分频而来,所以分频多少就是由TIM_ClockDivision决定的

    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_ClearFlag(TIM2, TIM_FLAG_Update);
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);//第四步:配置输出中断控制,允许更新中断输出到NVIC;
    
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//2位抢占,2位响应
    
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;//指定中断通道来开启或关闭,由于TIM2,选择TIM2_IRQn
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//指定中断通道是使能
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;//指定抢占优先级 0~3
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//指定响应优先级 0~3
    NVIC_Init(&NVIC_InitStructure);//第五步:配置NVIC,在NVIC中打开定时器中断的通道,并分配一个优先级;
    
    TIM_Cmd(TIM2, ENABLE);//第六步:运行控制
}

/*
void TIM2_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)//检查中断标志位,更新中断
    {
        
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);//清除标志位
    }
}
*/

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"

uint16_t Num;

int main(void)
{
    OLED_Init();
    Timer_Init();
    
    OLED_ShowString(1, 1, "Num:");
    
    while (1)
    {
        OLED_ShowNum(1, 5, Num, 5);
    }
}

void TIM2_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
    {
        Num ++;
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    }
}

4.2 定时器外部时钟

4.2.1 实验现象

 

4.2.2 代码

Timer.c

#include "stm32f10x.h"                  // Device header

void Timer_Init(void)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//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);
    
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInitStructure.TIM_Period = 10 - 1;
    TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
    
    TIM_ClearFlag(TIM2, TIM_FLAG_Update);
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
    
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_Init(&NVIC_InitStructure);
    
    TIM_Cmd(TIM2, ENABLE);
}

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);
    }
}
*/

main.c