赞
踩
系统定时器是属于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);
- }
-

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。