STM32——SysTick timer(STK)----系统定时器

        系统定时器是属于Cortex内核中的一个外设,所有Cortex-M内核的单片机都有这个定时器。Systick定时器常用来做延时,或者实时系统的心跳时钟。这样可以节省MCU资源,不用浪费一个定时器。它是一个24位、向下递减的计数器,由以下四个寄存器来控制(具体见Cortex内核手册):

1、SysTick control and status register (STK_CTRL)——控制及状态寄存器

Bits 31:17 Reserved, must be kept cleared.

Bit 16 COUNTFLAG计数器计数到0时该位会被置为1

Bits 15:3 Reserved, must be kept cleared.

Bit 2 CLKSOURCE:时钟源选择位
0: AHB/8
1: Processor clock (AHB)

Bit 1 TICKINT: SysTick 异常请求使能位
0: 计数到0不产生SysTick 异常请求
1: 计数到0产生SysTick 异常请求
Note: 软件可以使用 COUNTFLAG 位来查明、确定 SysTick 是否计数到0.

Bit 0 ENABLE: 计数器使能位
使能计数器。 当 ENABLE 置为1, 计数器就会加载 LOAD 寄存器中 RELOAD 的值然后开始递减,当递减到0时,它会将 COUNTFLAG 位置1根据 TICKINT 的值可选择的产生SysTick 异常请求,然后再次装载 RELOAD 的值并且开始递减计数,重复上述的过程。
0: Counter disabled
1: Counter enabled

2、SysTick reload value register (STK_LOAD)——重装载值寄存器

Bits 31:24 Reserved, must be kept cleared.

Bits 23:0 RELOAD: 重装载值
LOAD 寄存器决定了当寄存器被使能且计数到0时,加载到 STK_VAL 寄存器的值
RELOAD 的值的范围为 0x00000001-0x00FFFFFF。值为0也是合理的,但是没有任何意义。
如何定义值:如果每100个时钟脉冲就需要SysTick定时器产生一个中断,则将RELOAD设置为99。

3、SysTick current value register (STK_VAL)——当前数值寄存器

Bits 31:24 Reserved, must be kept cleared.

Bits 23:0 CURRENT: 当前计数器值
VAL 寄存器包含 SysTick 计数器当前的值,读取它会返回SysTick 计数器当前的值
向 VAL 寄存器写任意的值会将其清0,并且也会将 STK_CTRL 寄存器中的COUNTFLAG 位也清0。

4、与Systick有关的库函数

(1)时钟源选择库函数,在misc.h中有申明,具体函数实现在misc.c

#define SysTick_CLKSource_HCLK_Div8    ((uint32_t)0xFFFFFFFB)
#define SysTick_CLKSource_HCLK         ((uint32_t)0x00000004)
#define IS_SYSTICK_CLK_SOURCE(SOURCE) (((SOURCE) == SysTick_CLKSource_HCLK) || \
                                       ((SOURCE) == SysTick_CLKSource_HCLK_Div8))



void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)
{
  /* Check the parameters */
  assert_param(IS_SYSTICK_CLK_SOURCE(SysTick_CLKSource));
  if (SysTick_CLKSource == SysTick_CLKSource_HCLK)
  {
    SysTick->CTRL |= SysTick_CLKSource_HCLK;
  }
  else
  {
    SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8;
  }
}

通过宏定义以及合法性的判断,我们可以看到,该函数的形参内容只能是SysTick_CLKSource_HCLK_Div8外部时钟,或SysTick_CLKSource_HCLK内部时钟。
当形参是内部时钟,就将CTRL寄存器中的时钟源选择位(CLKSOURE)置为1;
当形参是外部时钟,就将CTRL寄存器中的时钟源选择位(CLKSOURE)清0。

(2)计数初值配置函数

core_m4.h

/** \brief  Structure type to access the System Timer (SysTick).
 */
typedef struct
{
  __IO uint32_t CTRL;                    /*!< Offset: 0x000 (R/W)  SysTick Control and Status Register */
  __IO uint32_t LOAD;                    /*!< Offset: 0x004 (R/W)  SysTick Reload Value Register       */
  __IO uint32_t VAL;                     /*!< Offset: 0x008 (R/W)  SysTick Current Value Register      */
  __I  uint32_t CALIB;                   /*!< Offset: 0x00C (R/ )  SysTick Calibration Register        */
} SysTick_Type;

/* SysTick Control / Status Register Definitions */
#define SysTick_CTRL_COUNTFLAG_Pos         16                                             /*!< SysTick CTRL: COUNTFLAG Position */
#define SysTick_CTRL_COUNTFLAG_Msk         (1UL << SysTick_CTRL_COUNTFLAG_Pos)            /*!< SysTick CTRL: COUNTFLAG Mask */

#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 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 Current Register Definitions */
#define SysTick_VAL_CURRENT_Pos             0                                             /*!< SysTick VAL: CURRENT Position */
#define SysTick_VAL_CURRENT_Msk            (0xFFFFFFUL /*<< SysTick_VAL_CURRENT_Pos*/)    /*!< SysTick VAL: CURRENT Mask */

/* SysTick Calibration Register Definitions */
#define SysTick_CALIB_NOREF_Pos            31                                             /*!< SysTick CALIB: NOREF Position */
#define SysTick_CALIB_NOREF_Msk            (1UL << SysTick_CALIB_NOREF_Pos)               /*!< SysTick CALIB: NOREF Mask */

#define SysTick_CALIB_SKEW_Pos             30                                             /*!< SysTick CALIB: SKEW Position */
#define SysTick_CALIB_SKEW_Msk             (1UL << SysTick_CALIB_SKEW_Pos)                /*!< SysTick CALIB: SKEW Mask */

#define SysTick_CALIB_TENMS_Pos             0                                             /*!< SysTick CALIB: TENMS Position */
#define SysTick_CALIB_TENMS_Msk            (0xFFFFFFUL /*<< SysTick_CALIB_TENMS_Pos*/)    /*!< SysTick CALIB: TENMS Mask */

/*@} end of group CMSIS_SysTick */


__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
  if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk) { return (1UL); }    /* Reload value impossible */

  SysTick->LOAD  = (uint32_t)(ticks - 1UL);                         /* set reload register */
  NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* set Priority for Systick Interrupt */
  SysTick->VAL   = 0UL;                                             /* 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 (0UL);                                                     /* Function successful */
}

#define SysTick_CTRL_COUNTFLAG_Pos         16 

Pos是定义CTRL寄存器中COUNTFLAG位所在的位置

#define SysTick_CTRL_COUNTFLAG_Msk         (1UL << SysTick_CTRL_COUNTFLAG_Pos) 
UL表示32位无符号长整形,Msk指的就是掩码,1UL << SysTick_CTRL_COUNTFLAG_Pos 其实就是遮掩其他位只对我们要操作的位进行相应的操作。

        函数的参数ticks就是计数的初值,首先判断一下在这个Systick时钟周期次数下,计数的初值会不会超出VAL寄存器24位值的大小,如果超了,就返回1。如果没有超出最大值,就给LOAD寄存器赋计时初值,至于赋值的具体内容,后面讲延时函数时说明。
        赋值完计时初值后,将VAL寄存器中的值清0,直接从LOAD寄存器中取计时初值,尽量避免计时误差。
        最后就是选择时钟源(注意在这里它选择的时钟源是不分频的时钟源),设置中断,以及使能Systick定时器:

SysTick_CTRL_CLKSOURCE_Msk :时钟不分频,需要分频就不包含这个
SysTick_CTRL_TICKINT_Msk :使能中断,不需要中断就不包含这个
SysTick_CTRL_ENABLE_Msk :使能定时器

5、延时函数的编写

delay.c

#include "delay.h"
#include "sys.h"

static uint32_t fac_us=0;							//us延时倍乘数			   
static uint32_t fac_ms=0;							//ms延时倍乘数
	   
//延时函数初始化
void delay_init(void)
{
 	SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
	//设置SysTick定时器的时钟,HCLK=SystemCoreClock,选择是使用HCLK的时钟,还是HCLK/8的时钟
	
	fac_us = SystemCoreClock / 8000000;	//计时1us所需要的重装载寄存器值,SystemCoreClock在system_stm32f4xx.c中找到					
	fac_ms = fac_us * 1000;				      //计时1ms所需要的重装载寄存器值   
}								    

//延时nus
//nus为要延时的us数.	
//注意:由于重装载寄存器是2位的,因此nus的值不能大于(2^24/fac_us)
uint32_t delay_us(u32 nus)
{	
  uint32_t ticks;
	
	ticks = nus * fac_us;
  if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk) { return (1UL); }    /* Reload value impossible */

  SysTick->LOAD  = (uint32_t)(ticks - 1UL);                         /* set reload register */
  SysTick->VAL   = 0UL;                                             /* 清空计数器值 */
  SysTick->CTRL  = SysTick_CTRL_ENABLE_Msk;                         /* 使能定时器,不设置中断,时钟分频 */
    		 
	while((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) != SysTick_CTRL_COUNTFLAG_Msk); //检查COUNTFLAG位是否为1,为1就代表计数器计数到0,这里并没有用到定时器的中断
	
	SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;  //定时完了就关闭定时器
	
	return (0UL);
}

//延时nms
//注意:由于重装载寄存器是2位的,因此nms的值不能大于(2^24/fac_ms)
uint32_t delay_nms(u32 nms)
{	 		  	  	   
  uint32_t ticks;
	
	ticks = nms * fac_ms;
  if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk) { return (1UL); }    /* Reload value impossible */

  SysTick->LOAD  = (uint32_t)(ticks - 1UL);                         /* set reload register */
  SysTick->VAL   = 0UL;                                             /* 清空计数器值 */
  SysTick->CTRL  = SysTick_CTRL_ENABLE_Msk;                         /* 使能定时器,不设置中断,时钟分频 */
    		 
	while((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) != SysTick_CTRL_COUNTFLAG_Msk); //检查COUNTFLAG位是否为1,为1就代表计数器计数到0,这里并没有用到定时器的中断
	
	SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;  //定时完了就关闭定时器
	
	return (0UL);
} 

//嵌套循环延时nms 
//nms:0~65535
void delay_ms(u16 nms)
{	 	 
	u8 repeat = nms/500;		
	u16 remain = nms%500;
	while(repeat)
	{
		delay_nms(500);
		repeat--;
	}
	if(remain)delay_nms(remain);
} 

        Systick定时器经常用来作延时使用,它本来的目的就是为了节省资源,节省定时器资源,所以我们在使用它定时的时候也要节省中断资源,采用不通过中断的方式来进行定时。       

        首先在程序中通过时钟源选择函数选择Systick定时器的时钟源,有两种选择,要么是不分频,要么是分频,区别在于不分频能延时的时间短,分频能延时的时间更长,但是要注意如果分频后出现了小数,那么在计算计时1us的重装载寄存器的值会是小数,由于重装载寄存器的值只能是整数,因此会造成误差。

        这里是使用HCLK时钟频率的八分之一作为Systick定时器的时钟源,对于STM32F429来说就是180MHZ/8=22.5MHZ。

1️⃣fac_us:
        它的意义是时间经过1微秒需要递减计数多少次。SYSCLK的值是系统时钟频率,也就180MHZ,在这里把MHz给省去了,直接用180。

(小总结:180MHz的时钟频率,计数1us需要计数180次;72MHz的时钟频率,计数1us需要计数72次;那么a MHz的时钟频率,计数1us需要计数a次)

        因此这里除以8是因为Systick定时器的时钟频率在上面是设置了使用系统时钟频率的八分之,得到的是Systick定时器的时钟频率(当然省去了MHz),那么计数1us需要计数的次数就是这个值。
2️⃣fac_ms:
        因为us与ms的进率是1000,所以fac_ms的值就是在fac_us的值的基础上乘1000。

最大延时时间的计算:

        设SysTick定时器的时钟频率为f MHz,则计数1us需要计数 f 次,重装载值的范围为0~2^24,最大值为2^24,那么最大计数时间为2^24 / f us,2^24 / f / 1000 ms。因此SysTick定时器的时钟频率为越小,所能计数的时间越长。所以我们是将SysTick定时器的时钟频率设为系统时钟频率的八分之一。

        例如168MHz的系统时钟频率,经过8分频为SysTick定时器的时钟频率即21MHz,那么最大计数时间为2^24 / 21 us = 798915us,798ms。

        但是如果是180MHz的系统时钟频率,经过8分频为SysTick定时器的时钟频率即22.5MHz,为小数,这时fac_us取整数为22,那么就会有误差,并且随着设定的时间越长,误差越大,我们又不好直接使用180MHz的频率,因为那样能计数的时间就会很短。因此,我们需要稍微修改一下代码。

#include "delay.h"
 
static uint32_t fac_2us=0;							//us延时倍乘数			   
static uint32_t fac_ms=0;							//ms延时倍乘数
	   
//延时函数初始化
void delay_init(void)
{
 	SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
	//设置SysTick定时器的时钟,HCLK=SystemCoreClock,选择是使用HCLK的时钟,还是HCLK/8的时钟
 
	fac_2us = SystemCoreClock / 4000000;	//计时2us所需要的重装载寄存器值,SystemCoreClock在system_stm32f4xx.c中找到					
	fac_ms = fac_2us * 500;				      //计时1ms所需要的重装载寄存器值   
}									    
 
//延时nus
//nus为要延时的us数.	
//注意:由于重装载寄存器是2位的,因此nus的值不能大于(2^24/fac_us)
uint32_t delay_10us(u32 nus)
{	
  uint32_t ticks;
	
	ticks = nus * fac_2us;
  if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk) { return (1UL); }    /* Reload value impossible */
 
  SysTick->LOAD  = (uint32_t)(ticks - 1UL);                         /* set reload register */
  SysTick->VAL   = 0UL;                                             /* 清空计数器值 */
  SysTick->CTRL  = SysTick_CTRL_ENABLE_Msk;                         /* 使能定时器,不设置中断,时钟分频 */
    		 
	while((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) != SysTick_CTRL_COUNTFLAG_Msk); //检查COUNTFLAG位是否为1,为1就代表计数器计数到0,这里并没有用到定时器的中断
	
	SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;  //定时完了就关闭定时器
	
	return (0UL);
}
 
//延时nms
//注意:由于重装载寄存器是2位的,因此nms的值不能大于(2^24/fac_ms)
uint32_t delay_nms(u32 nms)
{	 		  	  	   
  uint32_t ticks;
	
	ticks = nms * fac_ms;
  if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk) { return (1UL); }    /* Reload value impossible */
 
  SysTick->LOAD  = (uint32_t)(ticks - 1UL);                         /* set reload register */
  SysTick->VAL   = 0UL;                                             /* 清空计数器值 */
  SysTick->CTRL  = SysTick_CTRL_ENABLE_Msk;                         /* 使能定时器,不设置中断,时钟分频 */
    		 
	while((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) != SysTick_CTRL_COUNTFLAG_Msk); //检查COUNTFLAG位是否为1,为1就代表计数器计数到0,这里并没有用到定时器的中断
	
	SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;  //定时完了就关闭定时器
	
	return (0UL);
} 
 
//嵌套循环延时nms 
//nms:0~65535
void delay_ms(u16 nms)
{	 	 
	u8 repeat = nms/500;		
	u16 remain = nms%500;
	while(repeat)
	{
		delay_nms(500);
		repeat--;
	}
	if(remain)delay_nms(remain);
}
						    
 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

彷徨不前@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值