« 上一篇下一篇 »

STM32——SysTick定时器

今天我们来学习STM32中的SysTick定时器。

SysTick被捆绑在NVIC中,用于产生SysTick异常(异常号:15)。SysTick对操作系统来说非常重要,在以前,操作系统以及所有使用了时基的系统,都必须要一个硬件定时器来产生需要的“滴答”中断,作为整个系统的时基。

SysTick定时器能产生中断,CM3为它专门开辟出一个异常类型,并且在向量表中有它的一席之地。它使操作系统和其它系统软件在CM3器件间的移植变得简单多了,因为所有的CM3芯片都带有这个定时器,且所有CM3产品对SysTick的处理方式都是相同的。

根据《CM3权威指南》,我们可以知道与SysTick有关的寄存器有4个,它们分别是

SysTick控制及状态寄存器(STK_CTRL),其说明如图1所示。

图1   SysTick控制及状态寄存器说明

SysTick重装载数值寄存器(STK_LOAD),其说明如图2所示。

图2   SysTick重装载数值寄存器说明

SysTick当前数值寄存器(STK_VAL),其说明如图3所示。

图3   SysTick当前数值寄存器说明

SysTick校准数值寄存器(STK_CALIB),其说明如图4所示。

图4   SysTick校准数值寄存器说明


假设我们要用SysTick定时器来实现精准的延时,先来看看代码。

/**
  * @brief  启动系统滴答定时器 SysTick
  * @param  无
  * @retval 无
  */
void SysTick_Init(void)
{
 /* SystemFrequency / 100000 10us中断一次 */
if (SysTick_Config(SystemCoreClock / 100000))// ST3.5.0库版本
{ 
/* Capture error */ 
while (1);
}
// 滴答定时器不使能
SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk;
}

SystemCoreClock定义如下:

  uint32_t SystemCoreClock         = SYSCLK_FREQ_72MHz;        /*!< System Clock Frequency (Core Clock) */

这个变量值是可以通过前面的宏定义来选择的,这里我们只讨论目前这个定义,再来看下SYSCLK_FREQ_72MHz的定义

#define SYSCLK_FREQ_72MHz  72000000

所以 SystemCoreClock / 100000 = 72000 000 / 100 000 = 720;

即代码变成 if(SysTick_Config(720)),现在来看看SysTick_Config()函数的定义

/**
 * @brief  Initialize and start the SysTick counter and its interrupt.
 *
 * @param   ticks   number of ticks between two interrupts
 * @return  1 = failed, 0 = successful
 *
 * Initialise the system tick timer and its interrupt and start the
 * system tick timer / counter in free running mode to generate 
 * periodical interrupts.
 */
static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{ 
  if (ticks > SysTick_LOAD_RELOAD_Msk)  return (1);            /* Reload value impossible */
                                                               
  SysTick->LOAD  = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;      /* set reload register */
  NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);  /* set Priority for Cortex-M0 System Interrupts */
  SysTick->VAL   = 0;                                          /* Load the SysTick Counter Value */
  SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk | 
                   SysTick_CTRL_TICKINT_Msk   | 
                   SysTick_CTRL_ENABLE_Msk;                    /* Enable SysTick IRQ and SysTick Timer */
  return (0);                                                  /* Function successful */
}

该定义位于<core_cm3.h>头文件中,说明这个函数是stm32的库函数,现在我们来分析它的代码。

  if (ticks > SysTick_LOAD_RELOAD_Msk)  return (1);            /* Reload value impossible */

SysTick_LOAD_RELOAD_Msk是什么呢?

/* SysTick Reload Register Definitions */
#define SysTick_LOAD_RELOAD_Pos                      0     /*!< SysTick LOAD: RELOAD Position */
#define SysTick_LOAD_RELOAD_Msk            (0xFFFFFFul << SysTick_LOAD_RELOAD_Pos)        /*!< SysTick LOAD: RELOAD Mask */

即SysTick_LOAD_RELOAD_Msk = 0xFFFFFFul << 0 = 0xFFFFFF,所以代码变成 if( 720 > 0xFFFFFF )    return(1);

显然,这是个检查输入参数有没有越界的判断,至于为什么是不能大于0xFFFFFF呢?这是有依据的。

如上所示,因为SysTick是一个24位的定时器,所以其计数量最多只有2^24=16777216个,即其赋值范围为0x0000 0001 - 0x00FF FFFF。

很明显上面没有越界,跳过后面跟随的return(1)语句,继续往下执行代码

 SysTick->LOAD  = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;      /* set reload register */

由上面SysTick_LOAD_RELOAD_Msk定义可知 SysTick->LOAD = ( 720 & 0xFFFFFF) - 1 = 0X02D0 - 1 = 719;

这里的SysTick与前面文章所述一样,为一个结构体指针,并指向相关寄存器首地址,具体细节不再赘述,这里即把719赋给重装载值寄存器。继续往下执行

  NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);  /* set Priority for Cortex-M0 System Interrupts */

SysTick_IRQn在<stm32f10x.h>里面被定义为 -1,而__NVIC_PRIO_BITS也在该文件内被定义为4。

  SysTick_IRQn                = -1,     /*!< 15 Cortex-M3 System Tick Interrupt                   */
#define __NVIC_PRIO_BITS          4 /*!< STM32 uses 4 Bits for the Priority Levels    */

所以代码变成 NVIC_SetPriority( -1, (1 << 4) - 1 ),即NVIC_SetPriority( -1, 15);

到这里就要分析NVIC_SetPriority()函数。

/**
 * @brief  Set the priority for an interrupt
 *
 * @param  IRQn      The number of the interrupt for set priority
 * @param  priority  The priority to set
 *
 * Set the priority for the specified interrupt. The interrupt 
 * number can be positive to specify an external (device specific) 
 * interrupt, or negative to specify an internal (core) interrupt.
 *
 * Note: The priority cannot be set for every core interrupt.
 */
static __INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
{
  if(IRQn < 0) {
    SCB->SHP[((uint32_t)(IRQn) & 0xF)-4] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff); } /* set Priority for Cortex-M3 System Interrupts */
  else {
    NVIC->IP[(uint32_t)(IRQn)] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff);    }        /* set Priority for device specific Interrupts  */
}

由上述函数定义可得SCB->SHP[( (uint32_t)(-1) & 0xF ) - 4] = ( (15 << (8 - 4)) & 0xff);

即得 SCB->SHP[ (0xFFFFFFFF & 0xF) - 4] = 0xf0; 这里把(uint32_t)(-1)转换成0xFFFFFFFF,这个转换我们将在后面论述。

即得 SCB->SHP[11] = 0xf0;

这个赋值的意思是什么呢?我们先在《CortexM3 Programming Manual》里面找到SCB的描述。

上面的代码即表示对该寄存器的高8位操作,PRI_15[7:4]全置1,PRI_15[3:0]全置0,配置PRI_15即SysTick异常的优先级

好,继续往下

  SysTick->VAL   = 0;

这句代码的意思是SysTick当前数值寄存器赋值0,再继续往下

  SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk | 
                   SysTick_CTRL_TICKINT_Msk   | 
                   SysTick_CTRL_ENABLE_Msk;                    /* Enable SysTick IRQ and SysTick Timer */

SysTick_CTRL_CLKSOURCE_Msk、SysTick_CTRL_TICKINT_Msk、SysTick_CTRL_ENABLE_Msk的定义分别如下

#define SysTick_CTRL_CLKSOURCE_Pos          2                                             /*!< SysTick CTRL: CLKSOURCE Position */
#define SysTick_CTRL_CLKSOURCE_Msk         (1ul << SysTick_CTRL_CLKSOURCE_Pos)            /*!< SysTick CTRL: CLKSOURCE Mask */
#define SysTick_CTRL_TICKINT_Pos            1                                             /*!< SysTick CTRL: TICKINT Position */
#define SysTick_CTRL_TICKINT_Msk           (1ul << SysTick_CTRL_TICKINT_Pos)              /*!< SysTick CTRL: TICKINT Mask */
#define SysTick_CTRL_ENABLE_Pos             0                                             /*!< SysTick CTRL: ENABLE Position */
#define SysTick_CTRL_ENABLE_Msk            (1ul << SysTick_CTRL_ENABLE_Pos)               /*!< SysTick CTRL: ENABLE Mask */

即SysTick_CTRL_CLKSOURCE_Msk = 0x04,SysTick_CTRL_TICKINT_Msk = 0x02,SysTick_CTRL_ENABLE_Msk = 0x01,三者位或的最终结果为0x07,即

 SysTick->CTRL  = 0x07;由图1可知,SysTick被配置为AHB时钟——72MHz、计数到0便产生一次SysTick异常请求、使能计数器

由此SysTick便被配置为一个可产生中断的定时器,定时周期为720 / 72 000 000 = 1/100 000,即每10us产生一次中断。 

中断配置好了,那怎么利用中断实现精准延时呢?这个就很容易了。

前面已经说过,每次中断间隔为10us,如果我们要实现10ms延时,那就进1000次中断呗,设定一个变量,赋值为1000,每次进入中断就减1,如果没减至0,那么程序就停在这里,如果减到了0,就退出,这样便能实现10ms延时。

关于把(uint32_t)(-1)转换成0xFFFFFFFF

用unsigned把负数转正并不改变变量的内存形态,也就是二进制编码不会改变,而逻辑数值上的变化可通过二进制的补码来计算,等价于[ 2 ^ (变量类型位数) - (负数绝对值) ]。

比如一个32位的负数值为(-N),那么转换成unsigned的值为 (2 ^ 32 - N),所以(uint32_t)(-1) = 2 ^ 32 - 1 = 4 294 967 295 = 0xFFFF FFFF

(该知识点参考自C语言使用unsigned把负数转换成整数是怎么计算的?,非常感谢原作者。)

因作者水平有限,有错误处还请批评指正。


原创作品