当前位置:   article > 正文

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

stm32 systemcoreclock

        系统定时器是属于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

  1. #define SysTick_CLKSource_HCLK_Div8 ((uint32_t)0xFFFFFFFB)
  2. #define SysTick_CLKSource_HCLK ((uint32_t)0x00000004)
  3. #define IS_SYSTICK_CLK_SOURCE(SOURCE) (((SOURCE) == SysTick_CLKSource_HCLK) || \
  4. ((SOURCE) == SysTick_CLKSource_HCLK_Div8))
  5. void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)
  6. {
  7. /* Check the parameters */
  8. assert_param(IS_SYSTICK_CLK_SOURCE(SysTick_CLKSource));
  9. if (SysTick_CLKSource == SysTick_CLKSource_HCLK)
  10. {
  11. SysTick->CTRL |= SysTick_CLKSource_HCLK;
  12. }
  13. else
  14. {
  15. SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8;
  16. }
  17. }

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

(2)计数初值配置函数

core_m4.h

  1. /** \brief Structure type to access the System Timer (SysTick).
  2. */
  3. typedef struct
  4. {
  5. __IO uint32_t CTRL; /*!< Offset: 0x000 (R/W) SysTick Control and Status Register */
  6. __IO uint32_t LOAD; /*!< Offset: 0x004 (R/W) SysTick Reload Value Register */
  7. __IO uint32_t VAL; /*!< Offset: 0x008 (R/W) SysTick Current Value Register */
  8. __I uint32_t CALIB; /*!< Offset: 0x00C (R/ ) SysTick Calibration Register */
  9. } SysTick_Type;
  10. /* SysTick Control / Status Register Definitions */
  11. #define SysTick_CTRL_COUNTFLAG_Pos 16 /*!< SysTick CTRL: COUNTFLAG Position */
  12. #define SysTick_CTRL_COUNTFLAG_Msk (1UL << SysTick_CTRL_COUNTFLAG_Pos) /*!< SysTick CTRL: COUNTFLAG Mask */
  13. #define SysTick_CTRL_CLKSOURCE_Pos 2 /*!< SysTick CTRL: CLKSOURCE Position */
  14. #define SysTick_CTRL_CLKSOURCE_Msk (1UL << SysTick_CTRL_CLKSOURCE_Pos) /*!< SysTick CTRL: CLKSOURCE Mask */
  15. #define SysTick_CTRL_TICKINT_Pos 1 /*!< SysTick CTRL: TICKINT Position */
  16. #define SysTick_CTRL_TICKINT_Msk (1UL << SysTick_CTRL_TICKINT_Pos) /*!< SysTick CTRL: TICKINT Mask */
  17. #define SysTick_CTRL_ENABLE_Pos 0 /*!< SysTick CTRL: ENABLE Position */
  18. #define SysTick_CTRL_ENABLE_Msk (1UL /*<< SysTick_CTRL_ENABLE_Pos*/) /*!< SysTick CTRL: ENABLE Mask */
  19. /* SysTick Reload Register Definitions */
  20. #define SysTick_LOAD_RELOAD_Pos 0 /*!< SysTick LOAD: RELOAD Position */
  21. #define SysTick_LOAD_RELOAD_Msk (0xFFFFFFUL /*<< SysTick_LOAD_RELOAD_Pos*/) /*!< SysTick LOAD: RELOAD Mask */
  22. /* SysTick Current Register Definitions */
  23. #define SysTick_VAL_CURRENT_Pos 0 /*!< SysTick VAL: CURRENT Position */
  24. #define SysTick_VAL_CURRENT_Msk (0xFFFFFFUL /*<< SysTick_VAL_CURRENT_Pos*/) /*!< SysTick VAL: CURRENT Mask */
  25. /* SysTick Calibration Register Definitions */
  26. #define SysTick_CALIB_NOREF_Pos 31 /*!< SysTick CALIB: NOREF Position */
  27. #define SysTick_CALIB_NOREF_Msk (1UL << SysTick_CALIB_NOREF_Pos) /*!< SysTick CALIB: NOREF Mask */
  28. #define SysTick_CALIB_SKEW_Pos 30 /*!< SysTick CALIB: SKEW Position */
  29. #define SysTick_CALIB_SKEW_Msk (1UL << SysTick_CALIB_SKEW_Pos) /*!< SysTick CALIB: SKEW Mask */
  30. #define SysTick_CALIB_TENMS_Pos 0 /*!< SysTick CALIB: TENMS Position */
  31. #define SysTick_CALIB_TENMS_Msk (0xFFFFFFUL /*<< SysTick_CALIB_TENMS_Pos*/) /*!< SysTick CALIB: TENMS Mask */
  32. /*@} end of group CMSIS_SysTick */
  33. __STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
  34. {
  35. if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk) { return (1UL); } /* Reload value impossible */
  36. SysTick->LOAD = (uint32_t)(ticks - 1UL); /* set reload register */
  37. NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* set Priority for Systick Interrupt */
  38. SysTick->VAL = 0UL; /* Load the SysTick Counter Value */
  39. SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
  40. SysTick_CTRL_TICKINT_Msk |
  41. SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */
  42. return (0UL); /* Function successful */
  43. }

#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

  1. #include "delay.h"
  2. #include "sys.h"
  3. static uint32_t fac_us=0; //us延时倍乘数
  4. static uint32_t fac_ms=0; //ms延时倍乘数
  5. //延时函数初始化
  6. void delay_init(void)
  7. {
  8. SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
  9. //设置SysTick定时器的时钟,HCLK=SystemCoreClock,选择是使用HCLK的时钟,还是HCLK/8的时钟
  10. fac_us = SystemCoreClock / 8000000; //计时1us所需要的重装载寄存器值,SystemCoreClock在system_stm32f4xx.c中找到
  11. fac_ms = fac_us * 1000; //计时1ms所需要的重装载寄存器值
  12. }
  13. //延时nus
  14. //nus为要延时的us数.
  15. //注意:由于重装载寄存器是2位的,因此nus的值不能大于(2^24/fac_us)
  16. uint32_t delay_us(u32 nus)
  17. {
  18. uint32_t ticks;
  19. ticks = nus * fac_us;
  20. if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk) { return (1UL); } /* Reload value impossible */
  21. SysTick->LOAD = (uint32_t)(ticks - 1UL); /* set reload register */
  22. SysTick->VAL = 0UL; /* 清空计数器值 */
  23. SysTick->CTRL = SysTick_CTRL_ENABLE_Msk; /* 使能定时器,不设置中断,时钟分频 */
  24. while((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) != SysTick_CTRL_COUNTFLAG_Msk); //检查COUNTFLAG位是否为1,为1就代表计数器计数到0,这里并没有用到定时器的中断
  25. SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; //定时完了就关闭定时器
  26. return (0UL);
  27. }
  28. //延时nms
  29. //注意:由于重装载寄存器是2位的,因此nms的值不能大于(2^24/fac_ms)
  30. uint32_t delay_nms(u32 nms)
  31. {
  32. uint32_t ticks;
  33. ticks = nms * fac_ms;
  34. if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk) { return (1UL); } /* Reload value impossible */
  35. SysTick->LOAD = (uint32_t)(ticks - 1UL); /* set reload register */
  36. SysTick->VAL = 0UL; /* 清空计数器值 */
  37. SysTick->CTRL = SysTick_CTRL_ENABLE_Msk; /* 使能定时器,不设置中断,时钟分频 */
  38. while((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) != SysTick_CTRL_COUNTFLAG_Msk); //检查COUNTFLAG位是否为1,为1就代表计数器计数到0,这里并没有用到定时器的中断
  39. SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; //定时完了就关闭定时器
  40. return (0UL);
  41. }
  42. //嵌套循环延时nms
  43. //nms:0~65535
  44. void delay_ms(u16 nms)
  45. {
  46. u8 repeat = nms/500;
  47. u16 remain = nms%500;
  48. while(repeat)
  49. {
  50. delay_nms(500);
  51. repeat--;
  52. }
  53. if(remain)delay_nms(remain);
  54. }

        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的频率,因为那样能计数的时间就会很短。因此,我们需要稍微修改一下代码。

  1. #include "delay.h"
  2. static uint32_t fac_2us=0; //us延时倍乘数
  3. static uint32_t fac_ms=0; //ms延时倍乘数
  4. //延时函数初始化
  5. void delay_init(void)
  6. {
  7. SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
  8. //设置SysTick定时器的时钟,HCLK=SystemCoreClock,选择是使用HCLK的时钟,还是HCLK/8的时钟
  9. fac_2us = SystemCoreClock / 4000000; //计时2us所需要的重装载寄存器值,SystemCoreClock在system_stm32f4xx.c中找到
  10. fac_ms = fac_2us * 500; //计时1ms所需要的重装载寄存器值
  11. }
  12. //延时nus
  13. //nus为要延时的us数.
  14. //注意:由于重装载寄存器是2位的,因此nus的值不能大于(2^24/fac_us)
  15. uint32_t delay_10us(u32 nus)
  16. {
  17. uint32_t ticks;
  18. ticks = nus * fac_2us;
  19. if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk) { return (1UL); } /* Reload value impossible */
  20. SysTick->LOAD = (uint32_t)(ticks - 1UL); /* set reload register */
  21. SysTick->VAL = 0UL; /* 清空计数器值 */
  22. SysTick->CTRL = SysTick_CTRL_ENABLE_Msk; /* 使能定时器,不设置中断,时钟分频 */
  23. while((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) != SysTick_CTRL_COUNTFLAG_Msk); //检查COUNTFLAG位是否为1,为1就代表计数器计数到0,这里并没有用到定时器的中断
  24. SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; //定时完了就关闭定时器
  25. return (0UL);
  26. }
  27. //延时nms
  28. //注意:由于重装载寄存器是2位的,因此nms的值不能大于(2^24/fac_ms)
  29. uint32_t delay_nms(u32 nms)
  30. {
  31. uint32_t ticks;
  32. ticks = nms * fac_ms;
  33. if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk) { return (1UL); } /* Reload value impossible */
  34. SysTick->LOAD = (uint32_t)(ticks - 1UL); /* set reload register */
  35. SysTick->VAL = 0UL; /* 清空计数器值 */
  36. SysTick->CTRL = SysTick_CTRL_ENABLE_Msk; /* 使能定时器,不设置中断,时钟分频 */
  37. while((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) != SysTick_CTRL_COUNTFLAG_Msk); //检查COUNTFLAG位是否为1,为1就代表计数器计数到0,这里并没有用到定时器的中断
  38. SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; //定时完了就关闭定时器
  39. return (0UL);
  40. }
  41. //嵌套循环延时nms
  42. //nms:0~65535
  43. void delay_ms(u16 nms)
  44. {
  45. u8 repeat = nms/500;
  46. u16 remain = nms%500;
  47. while(repeat)
  48. {
  49. delay_nms(500);
  50. repeat--;
  51. }
  52. if(remain)delay_nms(remain);
  53. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/314365
推荐阅读
相关标签
  

闽ICP备14008679号