STM32 学习笔记:TIME定时器详解

时间:2021-02-12 18:03:19
STM32 单片机的定时器的确很强大,参考说明书中就占了一百多页,占参考手册1/4 有多了。
STM32的定时器分了好几个类别,各个类别针对功能作用都不大相同。
分有: 一、高级定时器
       二、通用定时器
       三、基本定时器
       四、看门狗定时器
       五、SysTick定时器

      其中看门狗定时器和SysTick定时器本篇笔记阐述,这里主要记下对平时使用定时器作用的计时计数器的一些自己的理解。
按照参考手册中的定义 高级定时器 通用定时器 基本定时器,这三个定时器成上下级的关系,即基本定时器有的功能通用定时器都有,而且还增加了向下、向上/向下计数器、PWM生成、输出比较、输入捕获等等功能;而高级定时器又包含了通用定时器的所有功能,另外还增加了死区互补输出、刹车信号、加入重复计数器等等。(这里等等功能请参考《STM32参考手册》)
      所以学习STM32 定时器实际就是学习一下高级定时器,然后适当的删减后就是后面的两种定时器了。
假若不涉及输出输入,定时器的最基本用法就是计数定时作用了本篇笔记主要针对这部分的理解所写下的。

高级定时器中一共有20个寄存器:

TIMx_CR1、TIMx_CR2、TIMx_SMCR、TIMx_DIER、TIMx_SR、TIMx_EGR、TIMx_CCMR1、TIMx_CCMR2、
TIMx_CCER、TIMx_CNT、TIMx_PSC、TIMx_ARR、TIMx_RCR、TIMx_CCR1、TIMx_CCR2、TIMx_CCR3、
TIMx_CCR4、TIMx_BDTR、TIMx_DCR、TIMx_DMAR
好吧一堆寄存器光看都看到眼花缭乱了,当然不是所有寄存器都涉及到才能让定时器工作的,例如最基本的定时功能所涉及的只有几个与时基功能相关的寄存器,TIMx_CNT(计数器寄存器)、TIMx_PSC(预分频器寄存器)、TIMx_ARR(自动装载寄存器)、TIMx_RCR(重复次数寄存器)。参考手册中有那么 衣服定时器的框图。这几个寄存器的关系如图所示的:

CK_PSC这根时钟线上的时钟源的选择,即给定时器计数计时的时钟源的输入方式,有四种方式,分别是内部时钟,外部时钟模式1,外部时钟模式2,内部触发。这部分日后再说,这里暂且使用最常用的内部时钟方式,既是当内部时钟为72MHz 的内部时钟源。
如图所示的,时钟源首先进入预分频器,然后再进入预先装入自动重装载寄存器的计数器中,当计数器溢出时产生一次中断和一次事件更新。除了多了一个PSC,其他的基本和51单片机很相似,初次看参考手册中的功能描述中出现了好多次“更新事件(UEV)”。这究竟是怎么的一样东西呢? 在这里有个新概念叫“影子寄存器”,在上图中,可以看到PSC、ARR、REP(重复计数器中的低八位)这三个寄存器框框下都有个黑影,每次这三个寄存器就是影子寄存器,如果看到参考手册全图中还可以看到另外还有几个框框下也有阴影部分的,这几个寄存器也是影子寄存器。何谓影子寄存器呢,例如PSC寄存器可以理解为有两个,一个是用户可以访问到的寄存器,可读可写,另一部分就是客户访问不到的但其装载值和实际寄存器是密切关联的,当程序在运行中改写PSC 这时候影子寄存器的作用就体现了,因为立刻写入的值可能会大于或小于目前正在运行的寄存器中的数值,而真实在运行时候的正是这个影子寄存器中的值,而程序写入的是可访问的寄存器,只有当产生一个更新事件的时候影子寄存器才会读入访问寄存器中的值,这样就可以防止突然修改而产生的非正常中断或不会中断等异常问题。当然在控制器CR1中控制这个影子寄存器是否起作用,不起作用的话就是立即写入这个数值到寄存器中。下面两幅是参考手册中的相关时序图:



回头再说一下“更新事件(UEV)” ,当计数器溢出的时候产生一次UEV事件,另外还可以在事件寄存器TMx_EGR中的UG位软件写入产生一次事件更新,当UEV事件来临的时候所有影子寄存器均载入寄存器中的值,从而实现所有带影子寄存器的更新,而不启用影子寄存器的情况下只能实现,写那个寄存器更新那个寄存器而,这可能造成相关联的寄存器产生冲突矛盾,建议还是开启此功能,在下一个溢出周期后产生事件更新。

       (既然说到了影子寄存器也说点自己的猜测,了解了点STM32单片机的都知道几乎所有寄存器都是32位的,唯独TIM寄存器是16位的,是的如果是32位的计数器我们可能还能做更宽广的定时作用。但我们也还是发现即使加入了影子寄存器而整体的寄存器地址依然保持是连续的,这我猜测一种可能性寄存器本身其实还是32位的,但高位提供了影子寄存器的载入功能,所以依然能保持地址连续性,只要设定了高位禁止访问即可。官方资料和搜索中均未有任何确认说法,纯粹本人猜测未得到官方任何证实)

     另外高级定时器中还有RCR重复次数寄存器这个,也是比较简单的事件更新(UEV) 都是在RCR为0的情况下产生计数器溢出而产生的,当RCR中不为0的时候计数器溢出只会使得重复次数寄存器递减而不会产生UEV,这样就可以使得定时器的定时情况得以延长,而相当于有16位的分频器,16位的计数器,再加入16位的重复次数,一共48位的计数定时器。详细看参考手册,这个很好理解。

    基本的基时单元就是上面提及的这几个,下面看看3.0库是如何实习的基本使用。

TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

       
      TIM_DeInit(TIM2);                                           //重新将Timer设置为缺省值
       
      TIM_InternalClockConfig(TIM2);                              //采用内部时钟给TIM2提供时钟源      
      TIM_TimeBaseStructure.TIM_Prescaler = 36000 - 1;            //预分频系数为36000-1,这样计数器时钟为72MHz/36000 = 2kHz       
      TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //设置时钟分割      
      TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //设置计数器模式为向上计数模式       
      TIM_TimeBaseStructure.TIM_Period = 2000 - 1;           //设置计数溢出大小,每计2000个数就产生一个更新事件
      TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure);         //将配置应用到TIM2中
      TIM_ClearFlag(TIM2, TIM_FLAG_Update);                  //清除溢出中断标志  
       
      TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);                //开启TIM2的中断

以上是一个最基本的定时器配置的代码,载自网上被转载无数次的地方……
中断函数自己按照需求写,这里不多说。
在库中的初始化函数和初始化数据类型有3类,TIM_TimeBaseInitTypeDef、TIM_OCInitTypeDef、TIM_ICInitTypeDef
与基时参数相关的数据类型是TIM_TimeBaseInitTypeDef

typedef struct
{
  uint16_t TIM_Prescaler;    

  uint16_t TIM_CounterMode;      
  uint16_t TIM_Period;          

  uint16_t TIM_ClockDivision;   
  uint8_t TIM_RepetitionCounter;
} TIM_TimeBaseInitTypeDef;

以上是从库stm32f10x_tim.h中 截取的代码,整体的数据结构可以中这段注释中得知,不懂E文的要么翻字典要么翻库函数中文翻译
版本(当然这个是2.0的库,有部分会和3.0后的版本很不相同),这部分的数据类型还是很一样的,不多说。 
接着就是TIM_TimeBaseInit()这个函数了,在stm32f10x_tim.c的224行中

void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct)
{
  uint16_t tmpcr1 = 0;

  /* Check the parameters */
  assert_param(IS_TIM_ALL_PERIPH(TIMx)); 
  assert_param(IS_TIM_COUNTER_MODE(TIM_TimeBaseInitStruct->TIM_CounterMode));
  assert_param(IS_TIM_CKD_DIV(TIM_TimeBaseInitStruct->TIM_ClockDivision));

  tmpcr1 = TIMx->CR1;  

  if((TIMx == TIM1) || (TIMx == TIM8)|| (TIMx == TIM2) || (TIMx == TIM3)||
     (TIMx == TIM4) || (TIMx == TIM5)) 
  {
    /* Select the Counter Mode */
    tmpcr1 &= (uint16_t)(~((uint16_t)(TIM_CR1_DIR | TIM_CR1_CMS)));
    tmpcr1 |= (uint32_t)TIM_TimeBaseInitStruct->TIM_CounterMode;
  }
  
  if((TIMx != TIM6) && (TIMx != TIM7))
  {
    /* Set the clock division */
    tmpcr1 &= (uint16_t)(~((uint16_t)TIM_CR1_CKD));
    tmpcr1 |= (uint32_t)TIM_TimeBaseInitStruct->TIM_ClockDivision;
  }

  TIMx->CR1 = tmpcr1;

  /* Set the Autoreload value */
  TIMx->ARR = TIM_TimeBaseInitStruct->TIM_Period ;
  
  /* Set the Prescaler value */
  TIMx->PSC = TIM_TimeBaseInitStruct->TIM_Prescaler;
     
  if ((TIMx == TIM1) || (TIMx == TIM8)|| (TIMx == TIM15)|| (TIMx == TIM16) || (TIMx == TIM17))  
  {
    /* Set the Repetition Counter value */
    TIMx->RCR = TIM_TimeBaseInitStruct->TIM_RepetitionCounter;
  }

  /* Generate an update event to reload the Prescaler and the Repetition counter
     values immediately */
  TIMx->EGR = TIM_PSCReloadMode_Immediate;           
}

可以看3.0后的函数里把所有的TIMx都加入一个函数里面做判断了,不需要和2.0的区分TIM1和TIM 两类函数,比较其基本操作都一样无非就是多了一个两个寄存器而已。