« 上一篇下一篇 »

STM32——库函数分析:TIM相关

今天我们来学习STM32的定时器。以STM32F103为例,一共有8个定时器,其中,高级定时器2个(TIM1、TIM8)、基本定时器2个(TIM6、TIM7)、通用定时器4个(TIM2、TIM3、TIM4、TIM5)。我们以TIM3为例来详细讲述定时器定时功能的配置。

static void TIM3_Mode_Config(void)// 此处static关键字用于修改函数链接属性,表明此函数只能在此源文件中访问
{
  TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
  /* 设置TIM3CLK 时钟为36MHZ */
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); 
  /* 基本定时器配置 */
  /*  APB1 = 36MHz,因为1999 + 1 = 2000分频>1分频,所以由参考手册可知,定时器3频率为36*2 = 72MHz,而72,000,000/2,000 = 36000 ,设计数
   值满3600进一次中断,每10次中断进行一次取反,即每1s对LED1取一次反   */
  TIM_TimeBaseStructure.TIM_Period = 3600;        //当定时器从0计数到255,即为266次,为一个定时周期
  TIM_TimeBaseStructure.TIM_Prescaler = 1999;    //设置预分频:
  TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1 ;//设置时钟分频系数
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //向上计数模式
  TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
  /* TIM3 enable counter */
  TIM_Cmd(TIM3, ENABLE);                   // 使能定时器3
  TIM_ITConfig(TIM3,TIM_IT_Update, ENABLE);// 允许update中断
  NVIC_Config_PWM();// 必须配置中断优先级,使能相关中断
}

上述代码为定时器配置代码。

  TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;

先定义一个结构体TIM_TimeBaseStructure,其类型为TIM_TimeBaseInitTypeDef,其成员包括预分频值、计数模式、周期、时钟分频因子、重复计数器。

  /* 设置TIM3CLK 时钟为36MHZ */
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

再是配置时钟,TIM3挂载于APB1总线上,而APB1总线的时钟最大速率为36MHz,此处即配置TIM3时钟速率为36MHz。

  TIM_TimeBaseStructure.TIM_Period = 3600;        //当定时器从0计数到255,即为266次,为一个定时周期
  TIM_TimeBaseStructure.TIM_Prescaler = 1999;    //设置预分频:
  TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1 ;//设置时钟分频系数
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //向上计数模式
  TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);

然后设置时钟周期为3600,预分频数为1999,根据TIM_CKD_DIV1与TIM_CounterMode_Up定义

#define TIM_CKD_DIV1                       ((uint16_t)0x0000)
#define TIM_CounterMode_Up                 ((uint16_t)0x0000)

即时钟分频因子为0,计数模式值为0。

现在来到库函数

TIM_TimeBaseInit();

/**
  * @brief  Initializes the TIMx Time Base Unit peripheral according to 
  *         the specified parameters in the TIM_TimeBaseInitStruct.
  * @param  TIMx: where x can be 1 to 17 to select the TIM peripheral.
  * @param  TIM_TimeBaseInitStruct: pointer to a TIM_TimeBaseInitTypeDef
  *         structure that contains the configuration information for the 
  *         specified TIM peripheral.
  * @retval None
  */
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;           
}
  tmpcr1 = TIMx->CR1; 
TIM1->CR1暂未进行配置,所以此时tmpcr1 = 0;
  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;
  }

满足判断条件,因为此时tmpcr1为0,所以其与上任何数都为0,所以

tmpcr1 &= (uint16_t)(~((uint16_t)(TIM_CR1_DIR | TIM_CR1_CMS))) = 0;

因为TIM_CounterMode = 0,所以

tmpcr1 |= (uint32_t)TIM_TimeBaseInitStruct->TIM_CounterMode = 0;

  if((TIMx != TIM6) && (TIMx != TIM7))

不满足判断条件,跳过后面花括号内代码。

  TIMx->CR1 = tmpcr1;

TIM3->CR1 = 0,根据控制寄存器1描述

可知,TIM3被配置为边沿对齐模式,计数器向上计数

  /* Set the Autoreload value */
  TIMx->ARR = TIM_TimeBaseInitStruct->TIM_Period ;

TIM3->ARR = 3600,根据自动重装载寄存器描述

可知,3600为将要装载入实际的自动重装载寄存器的值

  /* Set the Prescaler value */
  TIMx->PSC = TIM_TimeBaseInitStruct->TIM_Prescaler;

TIM3->PSC = 1999,根据预分频器描述

及STM32时钟描述

因为APB1 = 36MHz,因为分频数1999>1,所以由上图可知,定时器3时钟频率为36*2 = 72MHz,所以计数器时钟频率为72 000 000 / ( 1999 + 1) = 36 000。

  if ((TIMx == TIM1) || (TIMx == TIM8)|| (TIMx == TIM15)|| (TIMx == TIM16) || (TIMx == TIM17))

不满足判断条件,跳过其后花括号内代码。

  TIMx->EGR = TIM_PSCReloadMode_Immediate;

根据TIM_PSCReloadMode_Immediate定义

#define TIM_PSCReloadMode_Immediate        ((uint16_t)0x0001)

TIM3->EGR = 0x0001,根据事件产生寄存器描述

可知,TIM3被配置为重新初始化计数器,并产生一个更新事件

  /* TIM3 enable counter */
  TIM_Cmd(TIM3, ENABLE);                   
  TIM_ITConfig(TIM3,TIM_IT_Update, ENABLE);


使能TIM3计数器,并使能TIM3更新中断。

最后配置TIM3中断,调用另一个重要的库函数

NVIC_Config_PWM();

static void NVIC_Config_PWM(void)
{
  NVIC_InitTypeDef NVIC_InitStructure;
  
  /* Configure one bit for preemption priority */
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
  
  NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
}

同样先定义一个结构体NVIC_InitStructure,其类型为VIC_InitTypeDef,包含成员:指定中断通道、通道抢占优先级、通道子优先级、通道命令。

  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);

配置中断优先级模式,先来看一下NVIC_PriorityGroupConfig()函数。

/**
  * @brief  Configures the priority grouping: pre-emption priority and subpriority.
  * @param  NVIC_PriorityGroup: specifies the priority grouping bits length. 
  *   This parameter can be one of the following values:
  *     @arg NVIC_PriorityGroup_0: 0 bits for pre-emption priority
  *                                4 bits for subpriority
  *     @arg NVIC_PriorityGroup_1: 1 bits for pre-emption priority
  *                                3 bits for subpriority
  *     @arg NVIC_PriorityGroup_2: 2 bits for pre-emption priority
  *                                2 bits for subpriority
  *     @arg NVIC_PriorityGroup_3: 3 bits for pre-emption priority
  *                                1 bits for subpriority
  *     @arg NVIC_PriorityGroup_4: 4 bits for pre-emption priority
  *                                0 bits for subpriority
  * @retval None
  */
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{
  /* Check the parameters */
  assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));
  
  /* Set the PRIGROUP[10:8] bits according to NVIC_PriorityGroup value */
  SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
}

因为之前NVIC_PriorityGroup_1被定义为

#define NVIC_PriorityGroup_1         ((uint32_t)0x600) /*!< 1 bits for pre-emption priority

而AIRCR_VECTKEY_MASK被定义为

#define AIRCR_VECTKEY_MASK    ((uint32_t)0x05FA0000)

所以SCB->AIRCR = 0x05FA0000 | 0x600 = 0x05FA0600;

下面的东西相对而言比较深入,必须结合手册的相关内容来分析。

先看第1点,由上可知,如果要对SCB_AIRCR寄存器进行写操作,则SCB_AIRCR[31:16]必须写0x5FA,否则写操作无效。

再看第2点,优先级。再分析优先级配置之前,我们首先要知道STM32F103芯片使用了几位来表达优先级。

根据参考手册,我们可知该芯片用了4位来表达优先级。

“原则上,CM3支持3个固定的高优先级和多达256级的可编程优先级,并且支持128级抢占优先级。但是,绝大多数CM3芯片都会精简设计,以致实际上支持的优先级数会更少,如8级,16级,32级等。它们在设计时会裁掉表达优先级的几个低端有效位,以减少优先级的级数。可见,不管使用多少位来表达优先级,都是以MSB对齐的。”    ——摘录自《CM3权威指南》

MSB为最高加权位,即二进制数的最左边一位,即高位对齐。

由上阐述,对AIRCR写0x05FA0600,即允许写AIRCR寄存器,并配置优先级组模式为抢占优先级为1位(2级),子优先级为3位(8级)。

  NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);

NVIC_Init()定义如下

/**
  * @brief  Initializes the NVIC peripheral according to the specified
  *         parameters in the NVIC_InitStruct.
  * @param  NVIC_InitStruct: pointer to a NVIC_InitTypeDef structure that contains
  *         the configuration information for the specified NVIC peripheral.
  * @retval None
  */
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)
{
  uint32_t tmppriority = 0x00, tmppre = 0x00, tmpsub = 0x0F;
  
  /* Check the parameters */
  assert_param(IS_FUNCTIONAL_STATE(NVIC_InitStruct->NVIC_IRQChannelCmd));
  assert_param(IS_NVIC_PREEMPTION_PRIORITY(NVIC_InitStruct->NVIC_IRQChannelPreemptionPriority));  
  assert_param(IS_NVIC_SUB_PRIORITY(NVIC_InitStruct->NVIC_IRQChannelSubPriority));
    
  if (NVIC_InitStruct->NVIC_IRQChannelCmd != DISABLE)
  {
    /* Compute the Corresponding IRQ Priority --------------------------------*/    
    tmppriority = (0x700 - ((SCB->AIRCR) & (uint32_t)0x700))>> 0x08;
    tmppre = (0x4 - tmppriority);
    tmpsub = tmpsub >> tmppriority;
    tmppriority = (uint32_t)NVIC_InitStruct->NVIC_IRQChannelPreemptionPriority << tmppre;
    tmppriority |=  NVIC_InitStruct->NVIC_IRQChannelSubPriority & tmpsub;
    tmppriority = tmppriority << 0x04;
        
    NVIC->IP[NVIC_InitStruct->NVIC_IRQChannel] = tmppriority;
    
    /* Enable the Selected IRQ Channels --------------------------------------*/
    NVIC->ISER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] =
      (uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F);
  }
  else
  {
    /* Disable the Selected IRQ Channels -------------------------------------*/
    NVIC->ICER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] =
      (uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F);
  }
}

因为满足第1个判断语句,所以执行第一个花括号内代码。

    tmppriority = (0x700 - ((SCB->AIRCR) & (uint32_t)0x700))>> 0x08;
    tmppre = (0x4 - tmppriority);
    tmpsub = tmpsub >> tmppriority;

tmppriority = ( 0x700 - ( 0x05FA & 0x700) ) >> 0x08 = ( 0x700 - 0x600 ) >> 0x08 = 0x01;

tmppre = (0x4 - 0x01) = 0x03;

tmpsub = 0x0F >> 0x01 = 0x07;

    tmppriority = (uint32_t)NVIC_InitStruct->NVIC_IRQChannelPreemptionPriority << tmppre;
    tmppriority |=  NVIC_InitStruct->NVIC_IRQChannelSubPriority & tmpsub;
    tmppriority = tmppriority << 0x04;

tmppriority = 0 << 0x03 = 0;

tmppriority = 0 | ( 0 & 0x07 ) = 0 | 0 = 0;

tmppriority = 0 << 0x04 = 0;

    NVIC->IP[NVIC_InitStruct->NVIC_IRQChannel] = tmppriority;

因为TIM3_IRQn被定义为

  TIM3_IRQn                   = 29,     /*!< TIM3 global Interrupt                                */

所以NVIC->IP[29] = 0;

IP数组定义如下

  __IO uint8_t  IP[240];                      /*!< Offset: 0x300  Interrupt Priority Register (8Bit wide) */

即偏移地址相对于NVIC基地址的偏移量是0x300,由《CortexM3 Programming Manual》Table 44

可知,TIM3的优先级为0,即为最高优先级。(注:TIM3中断在向量表中位置为29。具体见下图)

此处请注意:这是我个人的理解,我觉得NVIC->IP[29] = 0表示的就是这个意思,但还是感觉有个地方不能解释,那就是我不能保证IP[29]里面的下标表示的就是其在中断向量表中的位置。

    NVIC->ISER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] =
      (uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F);

即NVIC->ISER[ 29 >> 0x05 ] = 0x01 << ( 29 & 0x1F )

即NVIC->ISER[ 0x1D >> 0x05 ] = 0x01 << ( 0x1D & 0x1F )

NVIC->ISER[0] = 0x01 << ( 0x1D) = 0x2000 0000

因为ISER数组相对于NVIC基地址偏移量为0,由《CortexM3 Programming Manual》Table 44

第29位被置为1,而由前文可知TIM3在中断向量表的位置为29,所以即表示使能TIM3中断。

好了,到这里TIM3的配置就结束,最终它被配置为:72M时钟、1999 + 1 = 2000分频、时钟分频因子为0、向上计数模式、周期为3600,即3600 / ( 72 000 000 / (1999 + 1)) = 0.1秒。

中断配置为:模式为1位用于表示抢占优先级,3位用于表示子优先级、抢占优先级为最高优先级0、子优先级为最高优先级0、使能TIM中断。

总而言之,最后TIM3被配置为每0.1s进入一次中断,中断优先级为最高级。


原创作品